From 971af154731132155eb753ca8094c3346ba77525 Mon Sep 17 00:00:00 2001 From: "Mael.PHILIPPE" Date: Mon, 18 May 2026 09:54:20 +0200 Subject: [PATCH 01/20] merged eliott's code into the UI --- __init__.py | 10 +- functions/SEBEfiles/Perez_v3_moved.py | 249 +- .../SEBE_2015a_calc_forprocessing.py | 195 +- functions/SEBEfiles/WriteMetaDataSEBE.py | 135 +- functions/SEBEfiles/__init__.py | 2 +- functions/SEBEfiles/get_ders.py | 96 +- functions/SEBEfiles/importdata.py | 181 +- functions/SEBEfiles/sunmapcreator_2015a.py | 117 +- .../SOLWEIGpython/COMFA/CNRRabs_Total.py | 79 +- .../COMFA/CNR_Kinabs_meas_old.py | 85 +- functions/SOLWEIGpython/COMFA/CNR_Kup.py | 29 +- functions/SOLWEIGpython/COMFA/COMFA_BUDGET.py | 292 +- functions/SOLWEIGpython/COMFA/CRT_Acs.py | 25 +- functions/SOLWEIGpython/COMFA/CRT_Acyl.py | 29 +- .../COMFA/ComfaPython20201023.py | 96 +- functions/SOLWEIGpython/COMFA/LinMeas_abs.py | 31 +- functions/SOLWEIGpython/COMFA/LupMeas_abs.py | 34 +- functions/SOLWEIGpython/COMFA/Ratio_Kb.py | 23 +- functions/SOLWEIGpython/COMFA/atm_P.py | 35 +- functions/SOLWEIGpython/COMFA/opt_m.py | 25 +- .../COMFA/radiationfunctionsCOMFA.py | 403 +- functions/SOLWEIGpython/COMFA/solar_ET.py | 27 +- functions/SOLWEIGpython/COMFA/solar_dec.py | 29 +- functions/SOLWEIGpython/COMFA/solar_zenith.py | 65 +- functions/SOLWEIGpython/CirclePlotBar.py | 158 +- functions/SOLWEIGpython/Kside_veg_v2019a.py | 313 +- functions/SOLWEIGpython/Kside_veg_v2022a.py | 590 ++- functions/SOLWEIGpython/Kup_veg_2015a.py | 48 +- functions/SOLWEIGpython/Kvikt_veg.py | 37 +- functions/SOLWEIGpython/Lcyl_v2022a.py | 120 +- functions/SOLWEIGpython/Lside_veg.py | 437 ++ functions/SOLWEIGpython/Lside_veg_v2015a.py | 295 +- functions/SOLWEIGpython/Lside_veg_v2022a.py | 300 +- functions/SOLWEIGpython/Lvikt_veg.py | 67 +- functions/SOLWEIGpython/PET_calculations.py | 207 +- functions/SOLWEIGpython/Perez_v3_moved.py | 255 +- .../Solweig_2021a_calc_forprocessing.py | 488 +- .../Solweig_2022a_calc_forprocessing.py | 545 +- .../Solweig_2025a_calc_forprocessing.py | 607 ++- .../Solweig_2026a_calc_forprocessing.py | 950 ++++ functions/SOLWEIGpython/Solweig_run.py | 1317 +++-- functions/SOLWEIGpython/Tgmaps_v1.py | 40 +- functions/SOLWEIGpython/TsWaveDelay_2015a.py | 16 +- functions/SOLWEIGpython/UTCI_calculations.py | 479 +- .../SOLWEIGpython/WriteMetadataSOLWEIG.py | 347 +- functions/SOLWEIGpython/anisotropic_sky.py | 328 +- functions/SOLWEIGpython/cylindric_wedge.py | 128 +- functions/SOLWEIGpython/daylen.py | 16 +- functions/SOLWEIGpython/emissivity_models.py | 96 +- functions/SOLWEIGpython/ground_surface.py | 691 +++ functions/SOLWEIGpython/gvf_2015a.py | 195 +- functions/SOLWEIGpython/gvf_2018a.py | 100 +- .../SOLWEIGpython/patch_characteristics.py | 446 +- functions/SOLWEIGpython/patch_radiation.py | 422 +- .../SOLWEIGpython/sunlit_shaded_patches.py | 23 +- functions/SOLWEIGpython/sunonsurface_2018a.py | 165 +- functions/SOLWEIGpython/wallOfInterest.py | 124 +- functions/SOLWEIGpython/wall_cover.py | 54 +- .../SOLWEIGpython/wall_surface_temperature.py | 535 +- functions/SOLWEIGpython/wallsAsNetCDF.py | 59 +- functions/TreeGenerator/makevegdems.py | 144 +- .../SOLWEIG1D/Kside1D_veg_v2019a.py | 245 +- .../TreePlanter/SOLWEIG1D/SOLWEIG1D_2023a.py | 188 +- functions/TreePlanter/SOLWEIG1D/SOLWEIG_1D.py | 157 +- .../SOLWEIG1D/Solweig1D_2019a_calc.py | 326 +- .../SOLWEIG1D/Solweig1D_2023a_calc.py | 357 +- .../TreePlanter/SOLWEIG1D/anisotropic_sky.py | 317 +- .../SOLWEIG1D/emissivity_models.py | 96 +- .../TreePlanter/SOLWEIG1D/patch_radiation.py | 410 +- .../SOLWEIG1D/sunlit_shaded_patches.py | 23 +- .../TreeGeneratorTempold/makevegdems.py | 138 +- .../TreePlanter/GreedyAlgorithm.py | 124 +- .../TreePlanter/HillClimberAlgorithm.py | 231 +- .../TreePlanter/StartingPositions.py | 57 +- .../TreePlanter/TreePlanterClasses.py | 410 +- .../TreePlanter/TreePlanterHillClimber.py | 193 +- .../TreePlanter/TreePlanterPrepare.py | 71 +- .../TreePlanter/TreePlanterTreeshade.py | 82 +- .../TreePlanter/TreePlanter/adjustments.py | 272 +- functions/URock/CalculatesIndicators.py | 476 +- functions/URock/DataUtil.py | 365 +- functions/URock/GlobalVariables.py | 63 +- functions/URock/H2gisConnection.py | 332 +- functions/URock/InitWindField.py | 4676 ++++++++++------- functions/URock/MainCalculation.py | 1462 ++++-- functions/URock/Obstacles.py | 764 ++- functions/URock/WindSolver.py | 588 ++- functions/URock/WriteMetadataURock.py | 146 +- functions/URock/Zones.py | 1162 ++-- functions/URock/__init__dep.py | 8 +- functions/URock/loadData.py | 635 ++- functions/URock/saveData.py | 1005 ++-- functions/URock/urock_analyser_functions.py | 606 ++- .../URock/urock_processing_algorithm_dep.py | 605 ++- functions/URock/urock_processing_dep.py | 8 +- .../URock/urock_processing_provider_dep.py | 12 +- functions/dailyshading.py | 185 +- functions/svf_for_voxels.py | 392 +- functions/svf_functions.py | 433 +- functions/wallalgorithms.py | 112 +- plugin_upload.py | 82 +- postprocessor/params_dict.py | 612 ++- postprocessor/solwieganalyzer_algorithm.py | 335 +- postprocessor/spatialtc_algorithm.py | 567 +- postprocessor/suewsanalyzer_algorithm.py | 439 +- postprocessor/targetanalyzer_algorithm.py | 483 +- postprocessor/treeplanter_algorithm.py | 744 ++- postprocessor/urock_analyser_algorithm.py | 317 +- postprocessor/uwganalyzer_algorithm.py | 421 +- preprocessor/copernicusera5_algorithm.py | 208 +- preprocessor/dsm_generator_algorithm.py | 709 ++- preprocessor/imagemorphparms_algorithm.py | 736 ++- .../imagemorphparmspoint_algorithm.py | 521 +- preprocessor/landcoverfraction_algorithm.py | 490 +- .../landcoverfractionpoint_algorithm.py | 399 +- preprocessor/skyviewfactor_algorithm.py | 637 ++- preprocessor/targetprepare_algorithm.py | 356 +- preprocessor/treegenerator_algorithm.py | 394 +- preprocessor/urock_prepare_algorithm.py | 662 ++- preprocessor/uwgprepare_algorithm.py | 470 +- preprocessor/wall_heightaspect_algorithm.py | 161 +- processing_umep.py | 8 +- processing_umep_provider.py | 89 +- processor/configsolweig.ini | 38 +- processor/parametersforsolweig.json | 503 +- processor/sebe_algorithm.py | 488 +- processor/shadow_generator_algorithm.py | 364 +- processor/solweig_algorithm.py | 1385 +++-- processor/solweig_algorithm_old.py | 2185 ++++++-- processor/suews_algorithm.py | 681 ++- processor/target_algorithm.py | 487 +- processor/urock_processing_algorithm.py | 686 ++- processor/uwg_algorithm.py | 474 +- util/RoughnessCalcFunctionV2.py | 461 +- util/SEBESOLWEIGCommonFiles/Perez_v3.py | 252 +- .../Solweig_v2015_metdata_noload.py | 89 +- .../clearnessindex_2013b.py | 69 +- util/SEBESOLWEIGCommonFiles/create_patches.py | 83 +- .../SEBESOLWEIGCommonFiles/diffusefraction.py | 37 +- .../shadowingfunction_wallheight_13.py | 117 +- .../shadowingfunction_wallheight_23.py | 210 +- util/SEBESOLWEIGCommonFiles/sun_distance.py | 15 +- util/SEBESOLWEIGCommonFiles/sun_position.py | 1234 +++-- util/__init__.py | 57 +- util/f90nml/__init__.py | 12 +- util/f90nml/fpy.py | 31 +- util/f90nml/namelist.py | 139 +- util/f90nml/parser.py | 155 +- util/imageMorphometricParms_v2.py | 102 +- util/landCoverFractions_v2.py | 88 +- util/misc.py | 114 +- util/ncWMSConnector.py | 350 +- util/shadowingfunctions.py | 494 +- util/ssParms.py | 156 +- util/umep_installer.py | 31 +- util/umep_solweig_export_component.py | 143 +- util/umep_suewsss_export_component.py | 468 +- util/umep_target_export_component.py | 51 +- util/umep_uwg_export_component.py | 494 +- 159 files changed, 36979 insertions(+), 16770 deletions(-) create mode 100644 functions/SOLWEIGpython/Lside_veg.py create mode 100644 functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py create mode 100644 functions/SOLWEIGpython/ground_surface.py diff --git a/__init__.py b/__init__.py index 73cae5a..b03ee56 100644 --- a/__init__.py +++ b/__init__.py @@ -28,17 +28,18 @@ # This works around https://github.com/qgis/QGIS/issues/55258 import site import sys -import supy as sp +import supy as sp import numba import jaydebeapi import rioxarray import yaml import pydantic + sys.path.insert(0, site.getusersitepackages()) -__author__ = 'Fredrik Lindberg' -__date__ = '2020-04-02' -__copyright__ = '(C) 2020 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2020-04-02" +__copyright__ = "(C) 2020 by Fredrik Lindberg" # noinspection PyPep8Naming @@ -50,4 +51,5 @@ def classFactory(iface): # pylint: disable=invalid-name """ # from .processing_umep import ProcessingUMEPPlugin + return ProcessingUMEPPlugin() diff --git a/functions/SEBEfiles/Perez_v3_moved.py b/functions/SEBEfiles/Perez_v3_moved.py index 6f1d385..bf240cf 100644 --- a/functions/SEBEfiles/Perez_v3_moved.py +++ b/functions/SEBEfiles/Perez_v3_moved.py @@ -8,22 +8,22 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): """ This function calculates distribution of luminance on the skyvault based on Perez luminince distribution model. - + Created by: Fredrik Lindberg 20120527, fredrikl@gvc.gu.se Gothenburg University, Sweden Urban Climte Group - + Input parameters: - zen: Zenith angle of the Sun (in degrees) - azimuth: Azimuth angle of the Sun (in degrees) - radD: Horizontal diffuse radiation (W m-2) - radI: Direct radiation perpendicular to the Sun beam (W m-2) - jday: Day of year - + Output parameters: - lv: Relative luminance map (same dimensions as theta. gamma) - + acoeff=[1.353 -0.258 -0.269 -1.437 -1.222 -0.773 1.415 1.102 @@ -33,7 +33,7 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): -1.016 -0.367 1.008 1.405 -1.000 0.021 0.503 -0.512 -1.050 0.029 0.426 0.359]; - + bcoeff=[-0.767 0.001 1.273 -0.123 -0.205 0.037 -3.913 0.916 0.278 -0.181 -4.500 1.177 @@ -42,7 +42,7 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): 0.288 -0.533 -3.850 3.375 -0.300 0.192 0.702 -1.632 -0.325 0.116 0.778 0.003]; - + ccoeff=[2.800 0.600 1.238 1.000 6.975 0.177 6.448 -0.124 24.22 -13.08 -37.70 34.84 @@ -51,7 +51,7 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): 14.00 -0.999 -7.14 7.547 19.00 -5.000 1.243 -1.91 31.06 -14.50 -46.11 55.37]; - + dcoeff=[1.874 0.630 0.974 0.281 -1.580 -0.508 -1.781 0.108 -5.00 1.522 3.923 -2.62 @@ -60,7 +60,7 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): -3.40 -0.108 -1.075 1.57 -4.00 0.025 0.384 0.266 -7.23 0.405 13.35 0.623]; - + ecoeff=[0.035 -0.125 -0.572 0.994 0.262 0.067 -0.219 -0.428 -0.016 0.160 0.420 -0.556 @@ -79,36 +79,94 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): :return: """ - m_a1 = np.array([1.3525, -1.2219, -1.1000, -0.5484, -0.6000, -1.0156, -1.0000, -1.0500]) - m_a2 = np.array([-0.2576, -0.7730, -0.2515, -0.6654, -0.3566, -0.3670, 0.0211, 0.0289]) - m_a3 = np.array([-0.2690, 1.4148, 0.8952, -0.2672, -2.5000, 1.0078, 0.5025, 0.4260]) - m_a4 = np.array([-1.4366, 1.1016, 0.0156, 0.7117, 2.3250, 1.4051, -0.5119, 0.3590]) - m_b1 = np.array([-0.7670, -0.2054, 0.2782, 0.7234, 0.2937, 0.2875, -0.3000, -0.3250]) - m_b2 = np.array([0.0007, 0.0367, -0.1812, -0.6219, 0.0496, -0.5328, 0.1922, 0.1156]) - m_b3 = np.array([1.2734, -3.9128, -4.5000, -5.6812, -5.6812, -3.8500, 0.7023, 0.7781]) - m_b4 = np.array([-0.1233, 0.9156, 1.1766, 2.6297, 1.8415, 3.3750, -1.6317, 0.0025]) - m_c1 = np.array([2.8000, 6.9750, 24.7219, 33.3389, 21.0000, 14.0000, 19.0000, 31.0625]) - m_c2 = np.array([0.6004, 0.1774, -13.0812, -18.3000, -4.7656, -0.9999, -5.0000, -14.5000]) - m_c3 = np.array([1.2375, 6.4477, -37.7000, -62.2500, -21.5906, -7.1406, 1.2438, -46.1148]) - m_c4 = np.array([1.0000, -0.1239, 34.8438, 52.0781, 7.2492, 7.5469, -1.9094, 55.3750]) - m_d1 = np.array([1.8734, -1.5798, -5.0000, -3.5000, -3.5000, -3.4000, -4.0000, -7.2312]) - m_d2 = np.array([0.6297, -0.5081, 1.5218, 0.0016, -0.1554, -0.1078, 0.0250, 0.4050]) - m_d3 = np.array([0.9738, -1.7812, 3.9229, 1.1477, 1.4062, -1.0750, 0.3844, 13.3500]) - m_d4 = np.array([0.2809, 0.1080, -2.6204, 0.1062, 0.3988, 1.5702, 0.2656, 0.6234]) - m_e1 = np.array([0.0356, 0.2624, -0.0156, 0.4659, 0.0032, -0.0672, 1.0468, 1.5000]) - m_e2 = np.array([-0.1246, 0.0672, 0.1597, -0.3296, 0.0766, 0.4016, -0.3788, -0.6426]) - m_e3 = np.array([-0.5718, -0.2190, 0.4199, -0.0876, -0.0656, 0.3017, -2.4517, 1.8564]) - m_e4 = np.array([0.9938, -0.4285, -0.5562, -0.0329, -0.1294, -0.4844, 1.4656, 0.5636]) - + m_a1 = np.array( + [1.3525, -1.2219, -1.1000, -0.5484, -0.6000, -1.0156, -1.0000, -1.0500] + ) + m_a2 = np.array( + [-0.2576, -0.7730, -0.2515, -0.6654, -0.3566, -0.3670, 0.0211, 0.0289] + ) + m_a3 = np.array( + [-0.2690, 1.4148, 0.8952, -0.2672, -2.5000, 1.0078, 0.5025, 0.4260] + ) + m_a4 = np.array( + [-1.4366, 1.1016, 0.0156, 0.7117, 2.3250, 1.4051, -0.5119, 0.3590] + ) + m_b1 = np.array( + [-0.7670, -0.2054, 0.2782, 0.7234, 0.2937, 0.2875, -0.3000, -0.3250] + ) + m_b2 = np.array( + [0.0007, 0.0367, -0.1812, -0.6219, 0.0496, -0.5328, 0.1922, 0.1156] + ) + m_b3 = np.array( + [1.2734, -3.9128, -4.5000, -5.6812, -5.6812, -3.8500, 0.7023, 0.7781] + ) + m_b4 = np.array( + [-0.1233, 0.9156, 1.1766, 2.6297, 1.8415, 3.3750, -1.6317, 0.0025] + ) + m_c1 = np.array( + [2.8000, 6.9750, 24.7219, 33.3389, 21.0000, 14.0000, 19.0000, 31.0625] + ) + m_c2 = np.array( + [ + 0.6004, + 0.1774, + -13.0812, + -18.3000, + -4.7656, + -0.9999, + -5.0000, + -14.5000, + ] + ) + m_c3 = np.array( + [ + 1.2375, + 6.4477, + -37.7000, + -62.2500, + -21.5906, + -7.1406, + 1.2438, + -46.1148, + ] + ) + m_c4 = np.array( + [1.0000, -0.1239, 34.8438, 52.0781, 7.2492, 7.5469, -1.9094, 55.3750] + ) + m_d1 = np.array( + [1.8734, -1.5798, -5.0000, -3.5000, -3.5000, -3.4000, -4.0000, -7.2312] + ) + m_d2 = np.array( + [0.6297, -0.5081, 1.5218, 0.0016, -0.1554, -0.1078, 0.0250, 0.4050] + ) + m_d3 = np.array( + [0.9738, -1.7812, 3.9229, 1.1477, 1.4062, -1.0750, 0.3844, 13.3500] + ) + m_d4 = np.array( + [0.2809, 0.1080, -2.6204, 0.1062, 0.3988, 1.5702, 0.2656, 0.6234] + ) + m_e1 = np.array( + [0.0356, 0.2624, -0.0156, 0.4659, 0.0032, -0.0672, 1.0468, 1.5000] + ) + m_e2 = np.array( + [-0.1246, 0.0672, 0.1597, -0.3296, 0.0766, 0.4016, -0.3788, -0.6426] + ) + m_e3 = np.array( + [-0.5718, -0.2190, 0.4199, -0.0876, -0.0656, 0.3017, -2.4517, 1.8564] + ) + m_e4 = np.array( + [0.9938, -0.4285, -0.5562, -0.0329, -0.1294, -0.4844, 1.4656, 0.5636] + ) + acoeff = np.transpose(np.atleast_2d([m_a1, m_a2, m_a3, m_a4])) bcoeff = np.transpose(np.atleast_2d([m_b1, m_b2, m_b3, m_b4])) ccoeff = np.transpose(np.atleast_2d([m_c1, m_c2, m_c3, m_c4])) dcoeff = np.transpose(np.atleast_2d([m_d1, m_d2, m_d3, m_d4])) ecoeff = np.transpose(np.atleast_2d([m_e1, m_e2, m_e3, m_e4])) - deg2rad = np.pi/180 - rad2deg = 180/np.pi - altitude = 90-zen + deg2rad = np.pi / 180 + rad2deg = 180 / np.pi + altitude = 90 - zen zen = zen * deg2rad azimuth = azimuth * deg2rad altitude = altitude * deg2rad @@ -117,25 +175,36 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): Ibn = radI # Skyclearness - PerezClearness = ((Idh+Ibn)/(Idh+1.041*np.power(zen, 3)))/(1+1.041*np.power(zen, 3)) + PerezClearness = ((Idh + Ibn) / (Idh + 1.041 * np.power(zen, 3))) / ( + 1 + 1.041 * np.power(zen, 3) + ) # Extra terrestrial radiation - day_angle = jday*2*np.pi/365 - #I0=1367*(1+0.033*np.cos((2*np.pi*jday)/365)) - I0 = 1367*(1.00011+0.034221*np.cos(day_angle) + 0.00128*np.sin(day_angle)+0.000719 * - np.cos(2*day_angle)+0.000077*np.sin(2*day_angle)) # New from robinsson + day_angle = jday * 2 * np.pi / 365 + # I0=1367*(1+0.033*np.cos((2*np.pi*jday)/365)) + I0 = 1367 * ( + 1.00011 + + 0.034221 * np.cos(day_angle) + + 0.00128 * np.sin(day_angle) + + 0.000719 * np.cos(2 * day_angle) + + 0.000077 * np.sin(2 * day_angle) + ) # New from robinsson # Optical air mass # m=1/altitude; old - if altitude >= 10*deg2rad: - AirMass = 1/np.sin(altitude) - elif altitude < 0: # below equation becomes complex - AirMass = 1/np.sin(altitude)+0.50572*np.power(180*complex(altitude)/np.pi+6.07995, -1.6364) + if altitude >= 10 * deg2rad: + AirMass = 1 / np.sin(altitude) + elif altitude < 0: # below equation becomes complex + AirMass = 1 / np.sin(altitude) + 0.50572 * np.power( + 180 * complex(altitude) / np.pi + 6.07995, -1.6364 + ) else: - AirMass = 1/np.sin(altitude)+0.50572*np.power(180*altitude/np.pi+6.07995, -1.6364) + AirMass = 1 / np.sin(altitude) + 0.50572 * np.power( + 180 * altitude / np.pi + 6.07995, -1.6364 + ) # Skybrightness # if altitude*rad2deg+6.07995>=0 - PerezBrightness = (AirMass*radD)/I0 + PerezBrightness = (AirMass * radD) / I0 if Idh <= 10: # m_a=0;m_b=0;m_c=0;m_d=0;m_e=0; PerezBrightness = 0 @@ -143,7 +212,7 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): print("Airmass") print(AirMass) print(PerezBrightness) - + # sky clearness bins if PerezClearness < 1.065: intClearness = 0 @@ -162,37 +231,79 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): if PerezClearness > 6.200: intClearness = 7 - m_a = acoeff[intClearness, 0] + acoeff[intClearness, 1] * zen + PerezBrightness * (acoeff[intClearness, 2] + acoeff[intClearness, 3] * zen) - m_b = bcoeff[intClearness, 0] + bcoeff[intClearness, 1] * zen + PerezBrightness * (bcoeff[intClearness, 2] + bcoeff[intClearness, 3] * zen) - m_e = ecoeff[intClearness, 0] + ecoeff[intClearness, 1] * zen + PerezBrightness * (ecoeff[intClearness, 2] + ecoeff[intClearness, 3] * zen) + m_a = ( + acoeff[intClearness, 0] + + acoeff[intClearness, 1] * zen + + PerezBrightness + * (acoeff[intClearness, 2] + acoeff[intClearness, 3] * zen) + ) + m_b = ( + bcoeff[intClearness, 0] + + bcoeff[intClearness, 1] * zen + + PerezBrightness + * (bcoeff[intClearness, 2] + bcoeff[intClearness, 3] * zen) + ) + m_e = ( + ecoeff[intClearness, 0] + + ecoeff[intClearness, 1] * zen + + PerezBrightness + * (ecoeff[intClearness, 2] + ecoeff[intClearness, 3] * zen) + ) if intClearness > 0: - m_c = ccoeff[intClearness, 0] + ccoeff[intClearness, 1] * zen + PerezBrightness * (ccoeff[intClearness, 2] + ccoeff[intClearness, 3] * zen) - m_d = dcoeff[intClearness, 0] + dcoeff[intClearness, 1] * zen + PerezBrightness * (dcoeff[intClearness, 2] + dcoeff[intClearness, 3] * zen) + m_c = ( + ccoeff[intClearness, 0] + + ccoeff[intClearness, 1] * zen + + PerezBrightness + * (ccoeff[intClearness, 2] + ccoeff[intClearness, 3] * zen) + ) + m_d = ( + dcoeff[intClearness, 0] + + dcoeff[intClearness, 1] * zen + + PerezBrightness + * (dcoeff[intClearness, 2] + dcoeff[intClearness, 3] * zen) + ) else: # different equations for c & d in clearness bin no. 1, from Robinsson - m_c = np.exp(np.power(PerezBrightness * (ccoeff[intClearness, 0] + ccoeff[intClearness, 1] * zen), ccoeff[intClearness, 2]))-1 - m_d = -np.exp(PerezBrightness * (dcoeff[intClearness, 0] + dcoeff[intClearness, 1] * zen)) + dcoeff[intClearness, 2] + \ - PerezBrightness * dcoeff[intClearness, 3] * PerezBrightness + m_c = ( + np.exp( + np.power( + PerezBrightness + * ( + ccoeff[intClearness, 0] + ccoeff[intClearness, 1] * zen + ), + ccoeff[intClearness, 2], + ) + ) + - 1 + ) + m_d = ( + -np.exp( + PerezBrightness + * (dcoeff[intClearness, 0] + dcoeff[intClearness, 1] * zen) + ) + + dcoeff[intClearness, 2] + + PerezBrightness * dcoeff[intClearness, 3] * PerezBrightness + ) skyvaultalt = np.atleast_2d([]) skyvaultazi = np.atleast_2d([]) if patchchoice == 2: # Creating skyvault at one degree intervals - skyvaultalt = np.ones([90, 361])*90 + skyvaultalt = np.ones([90, 361]) * 90 skyvaultazi = np.empty((90, 361)) for j in range(90): - skyvaultalt[j, :] = 91-j + skyvaultalt[j, :] = 91 - j skyvaultazi[j, :] = list(range(361)) - + elif patchchoice == 1: # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) skyvaultaltint = [6, 18, 30, 42, 54, 66, 78] skyvaultaziint = [12, 12, 15, 15, 20, 30, 60] for j in range(7): - for k in range(1, int(360/skyvaultaziint[j]) + 1): + for k in range(1, int(360 / skyvaultaziint[j]) + 1): skyvaultalt = np.append(skyvaultalt, skyvaultaltint[j]) - skyvaultazi = np.append(skyvaultazi, k*skyvaultaziint[j]) + skyvaultazi = np.append(skyvaultazi, k * skyvaultaziint[j]) skyvaultalt = np.append(skyvaultalt, 90) skyvaultazi = np.append(skyvaultazi, 360) @@ -202,12 +313,18 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): skyvaultazi = skyvaultazi * deg2rad # Angular distance from the sun from Robinsson - cosSkySunAngle = np.sin(skyvaultalt) * np.sin(altitude) + \ - np.cos(altitude) * np.cos(skyvaultalt) * np.cos(np.abs(skyvaultazi-azimuth)) + cosSkySunAngle = np.sin(skyvaultalt) * np.sin(altitude) + np.cos( + altitude + ) * np.cos(skyvaultalt) * np.cos(np.abs(skyvaultazi - azimuth)) # Main equation - lv = (1 + m_a * np.exp(m_b / np.cos(skyvaultzen))) * ((1 + m_c * np.exp(m_d * np.arccos(cosSkySunAngle)) + - m_e * cosSkySunAngle * cosSkySunAngle)) + lv = (1 + m_a * np.exp(m_b / np.cos(skyvaultzen))) * ( + ( + 1 + + m_c * np.exp(m_d * np.arccos(cosSkySunAngle)) + + m_e * cosSkySunAngle * cosSkySunAngle + ) + ) # Normalisation lv = lv / np.sum(lv) @@ -222,10 +339,10 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): # pause(1) if patchchoice == 1: - #x = np.atleast_2d([]) - #lv = np.transpose(np.append(np.append(np.append(x, skyvaultalt*rad2deg), skyvaultazi*rad2deg), lv)) - x = np.transpose(np.atleast_2d(skyvaultalt*rad2deg)) - y = np.transpose(np.atleast_2d(skyvaultazi*rad2deg)) + # x = np.atleast_2d([]) + # lv = np.transpose(np.append(np.append(np.append(x, skyvaultalt*rad2deg), skyvaultazi*rad2deg), lv)) + x = np.transpose(np.atleast_2d(skyvaultalt * rad2deg)) + y = np.transpose(np.atleast_2d(skyvaultazi * rad2deg)) z = np.transpose(np.atleast_2d(lv)) lv = np.append(np.append(x, y, axis=1), z, axis=1) - return lv, PerezClearness, PerezBrightness \ No newline at end of file + return lv, PerezClearness, PerezBrightness diff --git a/functions/SEBEfiles/SEBE_2015a_calc_forprocessing.py b/functions/SEBEfiles/SEBE_2015a_calc_forprocessing.py index 168228a..93073cd 100644 --- a/functions/SEBEfiles/SEBE_2015a_calc_forprocessing.py +++ b/functions/SEBEfiles/SEBE_2015a_calc_forprocessing.py @@ -2,14 +2,38 @@ import numpy as np import linecache import sys -from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_13 import shadowingfunction_wallheight_13 -from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_23 import shadowingfunction_wallheight_23 +from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_13 import ( + shadowingfunction_wallheight_13, +) +from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_23 import ( + shadowingfunction_wallheight_23, +) -def SEBE_2015a_calc(a, scale, slope, aspect, voxelheight, sizey, sizex, vegdem, vegdem2, walls, dirwalls, albedo, psi, - radmatI, radmatD, radmatR, usevegdem, feedback, wallmaxheight): + +def SEBE_2015a_calc( + a, + scale, + slope, + aspect, + voxelheight, + sizey, + sizex, + vegdem, + vegdem2, + walls, + dirwalls, + albedo, + psi, + radmatI, + radmatD, + radmatR, + usevegdem, + feedback, + wallmaxheight, +): # Parameters - deg2rad = np.pi/180 + deg2rad = np.pi / 180 Knight = np.zeros((sizex, sizey)) Energyyearroof = np.copy(Knight) @@ -20,23 +44,25 @@ def SEBE_2015a_calc(a, scale, slope, aspect, voxelheight, sizey, sizex, vegdem, amaxvalue = np.maximum(amaxvalue, vegmax) # Elevation vegdsms if buildingDEM includes ground heights - vegdem = vegdem+a + vegdem = vegdem + a vegdem[vegdem == a] = 0 - vegdem2 = vegdem2+a + vegdem2 = vegdem2 + a vegdem2[vegdem2 == a] = 0 - #% Bush separation - bush = np.logical_not((vegdem2*vegdem))*vegdem + # % Bush separation + bush = np.logical_not((vegdem2 * vegdem)) * vegdem else: psi = 1 # Creating wallmatrix (1 meter interval) - wallcol, wallrow = np.where(np.transpose(walls) > 0) # row and col for each wall pixel + wallcol, wallrow = np.where( + np.transpose(walls) > 0 + ) # row and col for each wall pixel wallstot = np.floor(walls * (1 / voxelheight)) * voxelheight # wallsections = np.floor(np.max(walls) * (1 / voxelheight)) # finding tallest wall wallsections = np.floor(wallmaxheight * (1 / voxelheight)) - # feedback.setProgressText('np.max(walls):' + str(np.max(walls))) - # feedback.setProgressText('1 / voxelheight:' + str(1 / voxelheight)) + # feedback.setProgressText('np.max(walls):' + str(np.max(walls))) + # feedback.setProgressText('1 / voxelheight:' + str(1 / voxelheight)) # feedback.setProgressText('voxel:' + str(voxelheight)) # feedback.setProgressText('wallsections:' + str(wallsections)) # feedback.setProgressText('np.shape(wallrow)[0]:' + str(np.shape(wallrow)[0])) @@ -62,34 +88,60 @@ def SEBE_2015a_calc(a, scale, slope, aspect, voxelheight, sizey, sizex, vegdem, for i in range(skyvaultaltint.size): for j in range(aziinterval[i]): - feedback.setProgress(int(index * (100. / 145.))) + feedback.setProgress(int(index * (100.0 / 145.0))) if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") break #################### SOLAR RADIATION POSITIONS ################### - #Solar Incidence angle (Roofs) - suniroof = np.sin(slope) * np.cos(radmatI[index, 0] * deg2rad) * \ - np.cos((radmatI[index, 1]*deg2rad)-aspect) + \ - np.cos(slope) * np.sin((radmatI[index, 0] * deg2rad)) + # Solar Incidence angle (Roofs) + suniroof = np.sin(slope) * np.cos( + radmatI[index, 0] * deg2rad + ) * np.cos((radmatI[index, 1] * deg2rad) - aspect) + np.cos( + slope + ) * np.sin( + (radmatI[index, 0] * deg2rad) + ) suniroof[suniroof < 0] = 0 # Solar Incidence angle (Walls) - suniwall = np.abs(np.sin(np.pi/2) * np.cos(radmatI[index, 0] * deg2rad) * - np.cos((radmatI[index, 1] * deg2rad) - dirwalls*deg2rad) + np.cos(np.pi/2) * - np.sin((radmatI[index, 0] * deg2rad))) + suniwall = np.abs( + np.sin(np.pi / 2) + * np.cos(radmatI[index, 0] * deg2rad) + * np.cos((radmatI[index, 1] * deg2rad) - dirwalls * deg2rad) + + np.cos(np.pi / 2) * np.sin((radmatI[index, 0] * deg2rad)) + ) # Shadow image if usevegdem == 1: - vegsh, sh, _, wallsh, wallsun, wallshve, _, facesun = shadowingfunction_wallheight_23(a, - vegdem, vegdem2, radmatI[index, 1], radmatI[index, 0], scale, amaxvalue, - bush, walls, dirwalls * deg2rad) - shadow = np.copy(sh-(1.-vegsh)*(1.-psi)) + vegsh, sh, _, wallsh, wallsun, wallshve, _, facesun = ( + shadowingfunction_wallheight_23( + a, + vegdem, + vegdem2, + radmatI[index, 1], + radmatI[index, 0], + scale, + amaxvalue, + bush, + walls, + dirwalls * deg2rad, + ) + ) + shadow = np.copy(sh - (1.0 - vegsh) * (1.0 - psi)) else: - sh, wallsh, wallsun, facesh, facesun = shadowingfunction_wallheight_13(a, radmatI[index, 1], - radmatI[index, 0], scale, walls, dirwalls * deg2rad) + sh, wallsh, wallsun, facesh, facesun = ( + shadowingfunction_wallheight_13( + a, + radmatI[index, 1], + radmatI[index, 0], + scale, + walls, + dirwalls * deg2rad, + ) + ) shadow = np.copy(sh) # roof irradiance calculation @@ -101,14 +153,14 @@ def SEBE_2015a_calc(a, scale, slope, aspect, voxelheight, sizey, sizex, vegdem, # roof diffuse and reflected radiation D = radmatD[index, 2] * shadow - R = radmatR[index, 2] * (shadow*-1 + 1) + R = radmatR[index, 2] * (shadow * -1 + 1) - Energyyearroof = np.copy(Energyyearroof+D+R+I) + Energyyearroof = np.copy(Energyyearroof + D + R + I) # WALL IRRADIANCE # direct radiation if radmatI[index, 2] > 0: - Iw = radmatI[index, 2] * suniwall # wall + Iw = radmatI[index, 2] * suniwall # wall else: Iw = np.copy(Knight) @@ -117,25 +169,72 @@ def SEBE_2015a_calc(a, scale, slope, aspect, voxelheight, sizey, sizex, vegdem, Rw = radmatR[index, 2] * facesun # for each wall level (voxelheight interval) - wallsun = np.floor(wallsun*(1/voxelheight)) * voxelheight - wallsh = np.floor(wallsh*(1/voxelheight)) * voxelheight + wallsun = np.floor(wallsun * (1 / voxelheight)) * voxelheight + wallsh = np.floor(wallsh * (1 / voxelheight)) * voxelheight if usevegdem == 1: - wallshve = np.floor(wallshve*(1/voxelheight)) * voxelheight + wallshve = np.floor(wallshve * (1 / voxelheight)) * voxelheight wallmatrix = wallmatrix * 0 for p in range(np.shape(wallmatrix)[0]): - if wallsun[wallrow[p], wallcol[p]] > 0: # Sections in sun - if wallsun[int(wallrow[p]), int(wallcol[p])] == wallstot[int(wallrow[p]), int(wallcol[p])]: # All sections in sun - wallmatrix[p, 0:int(wallstot[int(wallrow[p]), int(wallcol[p])] / voxelheight)] = Iw[wallrow[p], wallcol[p]] + Dw[wallrow[p], wallcol[p]] + Rw[wallrow[p], wallcol[p]] + if wallsun[wallrow[p], wallcol[p]] > 0: # Sections in sun + if ( + wallsun[int(wallrow[p]), int(wallcol[p])] + == wallstot[int(wallrow[p]), int(wallcol[p])] + ): # All sections in sun + wallmatrix[ + p, + 0 : int( + wallstot[int(wallrow[p]), int(wallcol[p])] + / voxelheight + ), + ] = ( + Iw[wallrow[p], wallcol[p]] + + Dw[wallrow[p], wallcol[p]] + + Rw[wallrow[p], wallcol[p]] + ) else: - wallmatrix[p, int((wallstot[wallrow[p], wallcol[p]] - wallsun[wallrow[p], wallcol[p]]) / voxelheight) - 1:int(wallstot[wallrow[p], wallcol[p]] / voxelheight)] = Iw[wallrow[p], wallcol[p]] + Dw[wallrow[p], wallcol[p]] + Rw[wallrow[p], wallcol[p]] + wallmatrix[ + p, + int( + ( + wallstot[wallrow[p], wallcol[p]] + - wallsun[wallrow[p], wallcol[p]] + ) + / voxelheight + ) + - 1 : int( + wallstot[wallrow[p], wallcol[p]] / voxelheight + ), + ] = ( + Iw[wallrow[p], wallcol[p]] + + Dw[wallrow[p], wallcol[p]] + + Rw[wallrow[p], wallcol[p]] + ) - if usevegdem == 1 and wallshve[wallrow[p], wallcol[p]] > 0: # sections in vegetation shade - wallmatrix[p, 0:int((wallshve[int(wallrow[p]), int(wallcol[p])] + wallsh[int(wallrow[p]), int(wallcol[p])]) / voxelheight)] = (Iw[wallrow[p], wallcol[p]] + Dw[wallrow[p], wallcol[p]])*psi + if ( + usevegdem == 1 and wallshve[wallrow[p], wallcol[p]] > 0 + ): # sections in vegetation shade + wallmatrix[ + p, + 0 : int( + ( + wallshve[int(wallrow[p]), int(wallcol[p])] + + wallsh[int(wallrow[p]), int(wallcol[p])] + ) + / voxelheight + ), + ] = ( + Iw[wallrow[p], wallcol[p]] + Dw[wallrow[p], wallcol[p]] + ) * psi - if wallsh[wallrow[p], wallcol[p]] > 0: # sections in building shade - wallmatrix[p, 0:int(wallsh[wallrow[p], wallcol[p]] / voxelheight)] = Rw[wallrow[p], wallcol[p]] + if ( + wallsh[wallrow[p], wallcol[p]] > 0 + ): # sections in building shade + wallmatrix[ + p, + 0 : int(wallsh[wallrow[p], wallcol[p]] / voxelheight), + ] = Rw[wallrow[p], wallcol[p]] Energyyearwall = Energyyearwall + np.copy(wallmatrix) @@ -144,12 +243,20 @@ def SEBE_2015a_calc(a, scale, slope, aspect, voxelheight, sizey, sizex, vegdem, # Including radiation from ground on walls as well as removing pixels high than walls # fix_print_with_import wallmatrixbol = (Energyyearwall > 0).astype(float) - Energyyearwall = (Energyyearwall + (np.sum(radmatR[:, 2]) * albedo)/2) * wallmatrixbol + Energyyearwall = ( + Energyyearwall + (np.sum(radmatR[:, 2]) * albedo) / 2 + ) * wallmatrixbol Energyyearroof /= 1000 Energyyearwall /= 1000 - Energyyearwall = np.transpose(np.vstack((wallrow + 1, wallcol + 1, np.transpose(Energyyearwall)))) # adding 1 to wallrow and wallcol so that the tests pass + Energyyearwall = np.transpose( + np.vstack((wallrow + 1, wallcol + 1, np.transpose(Energyyearwall))) + ) # adding 1 to wallrow and wallcol so that the tests pass - seberesult = {'Energyyearroof': Energyyearroof, 'Energyyearwall': Energyyearwall, 'vegdata': vegdata} + seberesult = { + "Energyyearroof": Energyyearroof, + "Energyyearwall": Energyyearwall, + "vegdata": vegdata, + } - return seberesult \ No newline at end of file + return seberesult diff --git a/functions/SEBEfiles/WriteMetaDataSEBE.py b/functions/SEBEfiles/WriteMetaDataSEBE.py index dc90d73..40ff417 100644 --- a/functions/SEBEfiles/WriteMetaDataSEBE.py +++ b/functions/SEBEfiles/WriteMetaDataSEBE.py @@ -1,67 +1,104 @@ from builtins import str + # This file prints out run information used for each specific run from time import strftime from osgeo import osr -def writeRunInfo(folderPath, filepath_dsm, gdal_dsm, usevegdem, filePath_cdsm, trunkfile, filePath_tdsm, lat, lon, UTC, - filePath_metfile, albedo, onlyglobal, trunkratio, trans, rows, cols): +def writeRunInfo( + folderPath, + filepath_dsm, + gdal_dsm, + usevegdem, + filePath_cdsm, + trunkfile, + filePath_tdsm, + lat, + lon, + UTC, + filePath_metfile, + albedo, + onlyglobal, + trunkratio, + trans, + rows, + cols, +): - with open(folderPath + '/RunInfoSEBE.txt', 'w') as file: - file.write('This file provides run settings for the SEBE run initiated at: ' - + strftime("%a, %d %b %Y %H:%M:%S")) - file.write('\n') - file.write('Version: ' + 'SEBE v2015a') - file.write('\n') - file.write('\n') - file.write('SURFACE DATA') - file.write('\n') - file.write('Digital surface model (DSM): ' + filepath_dsm) - file.write('\n') - file.write('Model domain: rows = ' + str(rows) + ', columns = ' + str(cols)) - file.write('\n') + with open(folderPath + "/RunInfoSEBE.txt", "w") as file: + file.write( + "This file provides run settings for the SEBE run initiated at: " + + strftime("%a, %d %b %Y %H:%M:%S") + ) + file.write("\n") + file.write("Version: " + "SEBE v2015a") + file.write("\n") + file.write("\n") + file.write("SURFACE DATA") + file.write("\n") + file.write("Digital surface model (DSM): " + filepath_dsm) + file.write("\n") + file.write( + "Model domain: rows = " + str(rows) + ", columns = " + str(cols) + ) + file.write("\n") # get CRS prj = gdal_dsm.GetProjection() srs = osr.SpatialReference(wkt=prj) if srs.IsProjected: - file.write('Projected referece system: ' + srs.GetAttrValue('projcs')) - file.write('\n') - file.write('Geographical coordinate system: ' + srs.GetAttrValue('geogcs')) - file.write('\n') - file.write('Latitude: ' + str(lat)) - file.write('\n') - file.write('Longitude: ' + str(lon)) - file.write('\n') - file.write('UTC: ' + str(UTC)) - file.write('\n') + file.write( + "Projected referece system: " + srs.GetAttrValue("projcs") + ) + file.write("\n") + file.write( + "Geographical coordinate system: " + srs.GetAttrValue("geogcs") + ) + file.write("\n") + file.write("Latitude: " + str(lat)) + file.write("\n") + file.write("Longitude: " + str(lon)) + file.write("\n") + file.write("UTC: " + str(UTC)) + file.write("\n") if usevegdem == 1: - file.write('Transmissivity of light through vegetation: ' + str(trans)) - file.write('\n') - file.write('Digital vegetation canopy model (CDSM): ' + filePath_cdsm) - file.write('\n') + file.write( + "Transmissivity of light through vegetation: " + str(trans) + ) + file.write("\n") + file.write( + "Digital vegetation canopy model (CDSM): " + filePath_cdsm + ) + file.write("\n") if trunkfile == 1: - file.write('Digital vegetation zrunk zone model (TDSM): ' + filePath_tdsm) - file.write('\n') + file.write( + "Digital vegetation zrunk zone model (TDSM): " + + filePath_tdsm + ) + file.write("\n") else: - file.write('Trunkzone estimated from CDSM') - file.write('\n') - file.write('Trunkzone as percent of canopy height: ' + str(trunkratio)) - file.write('\n') + file.write("Trunkzone estimated from CDSM") + file.write("\n") + file.write( + "Trunkzone as percent of canopy height: " + str(trunkratio) + ) + file.write("\n") else: - file.write('Vegetation scheme inactive') - file.write('\n') - file.write('\n') - file.write('METEOROLOGICAL FORCING DATA') - file.write('\n') - file.write('Meteorological file: ' + filePath_metfile) - file.write('\n') + file.write("Vegetation scheme inactive") + file.write("\n") + file.write("\n") + file.write("METEOROLOGICAL FORCING DATA") + file.write("\n") + file.write("Meteorological file: " + filePath_metfile) + file.write("\n") if onlyglobal == 1: - file.write('Diffuse and direct shortwave radiation estimated from global radiation') - file.write('\n') + file.write( + "Diffuse and direct shortwave radiation estimated from global radiation" + ) + file.write("\n") - file.write('\n') - file.write('ENVIRONMENTAL PARAMETERS') - file.write('\n') - file.write('Albedo: ' + str(albedo)) - file.write('\n') + file.write("\n") + file.write("ENVIRONMENTAL PARAMETERS") + file.write("\n") + file.write("Albedo: " + str(albedo)) + file.write("\n") file.close() diff --git a/functions/SEBEfiles/__init__.py b/functions/SEBEfiles/__init__.py index fff986e..18e9a90 100644 --- a/functions/SEBEfiles/__init__.py +++ b/functions/SEBEfiles/__init__.py @@ -1 +1 @@ -__author__ = 'dwg' +__author__ = "dwg" diff --git a/functions/SEBEfiles/get_ders.py b/functions/SEBEfiles/get_ders.py index bf09698..36d446c 100644 --- a/functions/SEBEfiles/get_ders.py +++ b/functions/SEBEfiles/get_ders.py @@ -3,11 +3,17 @@ try: from osgeo import gdal, gdal_array - from osgeo.gdalconst import GDT_Float64,GDT_Float32,GDT_UInt32,GDT_UInt16,GDT_Byte + from osgeo.gdalconst import ( + GDT_Float64, + GDT_Float32, + GDT_UInt32, + GDT_UInt16, + GDT_Byte, + ) except: - use_gdal=False + use_gdal = False else: - use_gdal=True + use_gdal = True import numpy import subprocess, os @@ -15,10 +21,11 @@ from math import pi, sin from ..data_io.data_io import read_dem_grid -GDALDEM=r"gdaldem" +GDALDEM = r"gdaldem" + def get_temp_file(suffix=""): - fd,temp_filename=tempfile.mkstemp(suffix=suffix) + fd, temp_filename = tempfile.mkstemp(suffix=suffix) try: os.close(fd) except: @@ -31,52 +38,77 @@ def get_temp_file(suffix=""): print(temp_filename) return temp_filename + def get_slope_aspect_gdal(dem_file): - temp_slope=get_temp_file(".tif") - temp_aspect=get_temp_file(".tif") - slope_proc=subprocess.Popen([GDALDEM,"slope",dem_file,temp_slope,"-compute_edges","-alg","ZevenbergenThorne"]) - aspect_proc=subprocess.Popen([GDALDEM,"aspect",dem_file,temp_aspect,"-zero_for_flat","-compute_edges","-alg","ZevenbergenThorne"]) + temp_slope = get_temp_file(".tif") + temp_aspect = get_temp_file(".tif") + slope_proc = subprocess.Popen( + [ + GDALDEM, + "slope", + dem_file, + temp_slope, + "-compute_edges", + "-alg", + "ZevenbergenThorne", + ] + ) + aspect_proc = subprocess.Popen( + [ + GDALDEM, + "aspect", + dem_file, + temp_aspect, + "-zero_for_flat", + "-compute_edges", + "-alg", + "ZevenbergenThorne", + ] + ) slope_proc.wait() aspect_proc.wait() - slope_array,_,_=read_dem_grid(temp_slope) - slope_array=numpy.deg2rad(slope_array) - aspect_array,_,_=read_dem_grid(temp_aspect) - aspect_array=numpy.deg2rad(aspect_array) + slope_array, _, _ = read_dem_grid(temp_slope) + slope_array = numpy.deg2rad(slope_array) + aspect_array, _, _ = read_dem_grid(temp_aspect) + aspect_array = numpy.deg2rad(aspect_array) os.unlink(temp_slope) os.unlink(temp_aspect) - return slope_array,aspect_array + return slope_array, aspect_array + -def cart2pol(x, y, units='deg'): +def cart2pol(x, y, units="deg"): """Convert from cartesian to polar coordinates - **usage**: - theta, radius = cart2pol(x, y, units='deg') - units refers to the units (rad or deg) for theta that should be returned""" + **usage**: + theta, radius = cart2pol(x, y, units='deg') + units refers to the units (rad or deg) for theta that should be returned""" radius = numpy.sqrt(x**2 + y**2) theta = numpy.arctan2(y, x) - if units in ['deg', 'degs']: + if units in ["deg", "degs"]: theta = theta * 180 / numpy.pi return theta, radius + def get_ders(dem_file): - dem,_,_=read_dem_grid(dem_file) - dx=0.5 - #print "ders dx",dx + dem, _, _ = read_dem_grid(dem_file) + dx = 0.5 + # print "ders dx",dx fy, fx = numpy.gradient(dem, dx, dx) - asp,grad=cart2pol(fy,fx,'rad') - grad=numpy.arctan(grad) #steepest slope - asp=asp*-1 # convert asp to increase going counterclockwise + asp, grad = cart2pol(fy, fx, "rad") + grad = numpy.arctan(grad) # steepest slope + asp = asp * -1 # convert asp to increase going counterclockwise + + # Converting to 0 - 2*pi + asp = asp + (asp < 0) * (pi * 2) + return grad, asp - #Converting to 0 - 2*pi - asp=asp+(asp<0)*(pi*2) - return grad,asp if use_gdal: - read_dem_grid=read_dem_grid + read_dem_grid = read_dem_grid else: - read_dem_grid=read_tiffile + read_dem_grid = read_tiffile if use_gdal: - get_slope_aspect=get_slope_aspect_gdal + get_slope_aspect = get_slope_aspect_gdal else: - get_slope_aspect=get_ders \ No newline at end of file + get_slope_aspect = get_ders diff --git a/functions/SEBEfiles/importdata.py b/functions/SEBEfiles/importdata.py index fccf357..1e67a01 100644 --- a/functions/SEBEfiles/importdata.py +++ b/functions/SEBEfiles/importdata.py @@ -40,6 +40,7 @@ @seealso{textscan, dlmread, csvread, load} @end deftypefn """ + from __future__ import print_function from builtins import str @@ -47,6 +48,7 @@ from PIL import Image import numpy as np + def importdata(*args): """ Imports data. @@ -57,8 +59,8 @@ def importdata(*args): nargin = len(args) # Default values - fileName = '' - delimiter = '' + fileName = "" + delimiter = "" headerRows = -1 output = dict() @@ -69,66 +71,111 @@ def importdata(*args): fileName = args[0] if not isinstance(fileName, str): raise TypeError("importdata: File name needs to be a string.") - if '-pastespecial' in fileName: - raise ValueError('importdata: Option ''-pastespecial'' not implemented.') + if "-pastespecial" in fileName: + raise ValueError( + "importdata: Option " "-pastespecial" " not implemented." + ) if nargin > 1: delimiter = args[1] # Check that the delimiter really is a string if not isinstance(delimiter, str): - raise TypeError('importdata: Delimiter needs to be a character.') - if len(delimiter) > 1 and not delimiter is '\t': - raise ValueError('importdata: Delimiter cannot be longer than 1 character.') - if delimiter is '\\': - delimiter = '\\\\' # if delimiter is "\" change to "\\" + raise TypeError("importdata: Delimiter needs to be a character.") + if len(delimiter) > 1 and not delimiter is "\t": + raise ValueError( + "importdata: Delimiter cannot be longer than 1 character." + ) + if delimiter is "\\": + delimiter = "\\\\" # if delimiter is "\" change to "\\" if nargin > 2: headerRows = args[2] if not isinstance(headerRows, int) or headerRows < 0: - raise TypeError('importdata: Number of header rows needs to be an integer number >= 0.') + raise TypeError( + "importdata: Number of header rows needs to be an integer number >= 0." + ) if nargin > 3: - raise ValueError('importdata: Too many input arguments.') + raise ValueError("importdata: Too many input arguments.") # Check file format # Get the extension from the file name. dir = os.path.dirname(fileName) name, ext = os.path.basename(fileName).split(".") - ext = ext.lower() # Make sure file extension is in lower case. - - if ext in ['.au', '.snd']: - raise ValueError('importdata: Not implemented for file format ' + ext + '.') - elif ext is '.avi': - raise ValueError('importdata: Not implemented for file format ' + ext + '.') - elif ext in ['.bmp', '.cur', '.gif', '.hdf', '.ico', '.jpe', '.jpeg', '.jpg', '.pbm', '.pcx', '.pgm', '.png', '.pnm', '.ppm', '.ras', '.tif', '.tiff', '.xwd']: - delimiter = None + ext = ext.lower() # Make sure file extension is in lower case. + + if ext in [".au", ".snd"]: + raise ValueError( + "importdata: Not implemented for file format " + ext + "." + ) + elif ext is ".avi": + raise ValueError( + "importdata: Not implemented for file format " + ext + "." + ) + elif ext in [ + ".bmp", + ".cur", + ".gif", + ".hdf", + ".ico", + ".jpe", + ".jpeg", + ".jpg", + ".pbm", + ".pcx", + ".pgm", + ".png", + ".pnm", + ".ppm", + ".ras", + ".tif", + ".tiff", + ".xwd", + ]: + delimiter = None headerRows = 0 img = Image.open(fileName) - output['cdata'] = np.array(img) - output['colormap'] = img.mode # TODO: check if this method is euaivalent - output['alpha'] = img.split()[-1] - elif ext is '.mat': + output["cdata"] = np.array(img) + output["colormap"] = ( + img.mode + ) # TODO: check if this method is euaivalent + output["alpha"] = img.split()[-1] + elif ext is ".mat": import scipy.io as sio - delimiter = None + + delimiter = None headerRows = 0 output = sio.loadmat(fileName) - elif ext is '.wk1': - raise ValueError('importdata: Not implemented for file format ' + ext + '.') - elif ext in ['.xls', '.xlsx']: - raise ValueError('importdata: Not implemented for file format ' + ext + '.') - elif ext in ['.wav', '.wave']: - #delimiter = None - #headerRows = 0 - #[output.data, output.fs] = wavread(fileName) - raise ValueError('importdata: Not implemented for file format ' + ext + '.') + elif ext is ".wk1": + raise ValueError( + "importdata: Not implemented for file format " + ext + "." + ) + elif ext in [".xls", ".xlsx"]: + raise ValueError( + "importdata: Not implemented for file format " + ext + "." + ) + elif ext in [".wav", ".wave"]: + # delimiter = None + # headerRows = 0 + # [output.data, output.fs] = wavread(fileName) + raise ValueError( + "importdata: Not implemented for file format " + ext + "." + ) else: # Assume the file is in ascii format. - output, delimiter, headerRows = importdata_ascii(fileName, delimiter, headerRows) - #raise ValueError('importdata: Not implemented for file format: ASCII') + output, delimiter, headerRows = importdata_ascii( + fileName, delimiter, headerRows + ) + # raise ValueError('importdata: Not implemented for file format: ASCII') # If there are any empty elements in the output dict, then remove them if isinstance(output, dict) and len(output) == 1: - for key, val in output.copy().items(): # copy() for py3 compatibility or use items() + for ( + key, + val, + ) in ( + output.copy().items() + ): # copy() for py3 compatibility or use items() if not val: del output[key] @@ -140,6 +187,7 @@ def importdata(*args): return output, delimiter, headerRows + def print_usage(): """ Raises an error @@ -147,20 +195,23 @@ def print_usage(): """ raise IOError("Insufficient/bad arguments for importdata()") + def importdata_ascii(fileName, delimiter, headerRows): output = dict() - output['data'] = np.array([]) - output['textdata'] = list() + output["data"] = np.array([]) + output["textdata"] = list() # Read file into string and count the number of header rows - with open(fileName, 'rb') as txt: + with open(fileName, "rb") as txt: fileContentRows = txt.readlines() # removing \r\n or \n character from each lines - fileContentRows = [line.rstrip('\r\n') for line in fileContentRows] + fileContentRows = [line.rstrip("\r\n") for line in fileContentRows] if not delimiter: - raise ValueError('importdata: Guessing delimiter is not implemented yet, you have to specify it.') + raise ValueError( + "importdata: Guessing delimiter is not implemented yet, you have to specify it." + ) if headerRows < 0: headerRows = 0 @@ -174,48 +225,62 @@ def importdata_ascii(fileName, delimiter, headerRows): # Put the header rows in output.textdata. if headerRows > 0: for i, el in enumerate(fileContentRows[0:headerRows]): - output['textdata'].append(str(el)) # struct in ML is converted to dict in py + output["textdata"].append( + str(el) + ) # struct in ML is converted to dict in py # If space is the delimiter, then remove spaces in the beginning of each data row. - if delimiter is ' ': + if delimiter is " ": # strtrim does not only remove the leading spaces but also the tailing spaces, but that doesn't really matter. - fileContentRows = fileContentRows[:headerRows] + [line.strip() for line in fileContentRows[headerRows:]] + fileContentRows = fileContentRows[:headerRows] + [ + line.strip() for line in fileContentRows[headerRows:] + ] # Remove empty data rows. Go through them backwards so that you wont get out of bounds. # from last element to index=header in reverse - fileContentRows = fileContentRows[:headerRows] + [line for line in fileContentRows[headerRows:] if not len(line) < 1] - # if not line: # more pythonic, needs testing + fileContentRows = fileContentRows[:headerRows] + [ + line for line in fileContentRows[headerRows:] if not len(line) < 1 + ] + # if not line: # more pythonic, needs testing # Count the number of data columns. # If there are different number of columns, use the greatest value. dataColumns = 0 for line in fileContentRows: - num_elements = len([el for el in line.split(delimiter) if el]) # if element is not empty + num_elements = len( + [el for el in line.split(delimiter) if el] + ) # if element is not empty dataColumns = max(dataColumns, num_elements) - #print "headerRows py:", headerRows + # print "headerRows py:", headerRows # Go through the data and put it in either output.data or output.textdata depending on if it is numeric or not. - output['data'] = np.empty((len(fileContentRows)-headerRows, dataColumns)) * np.nan + output["data"] = ( + np.empty((len(fileContentRows) - headerRows, dataColumns)) * np.nan + ) for i, line in enumerate(fileContentRows[headerRows:]): # Only use the row if it contains anything other than white-space characters. if line.replace(" ", ""): rowData = line.split(delimiter) for j, el in enumerate(rowData): try: - output['data'][i, j] = float(el) + output["data"][i, j] = float(el) except ValueError: - output['textdata'].append(str(el)) # using tuple (i,j) as key to dict textdata + output["textdata"].append( + str(el) + ) # using tuple (i,j) as key to dict textdata # Check wether rowheaders or colheaders should be used # fix_print_with_import print("text data") # fix_print_with_import - print(output['textdata']) - if headerRows == dataColumns and len(output['textdata']) == 1: # getting the col size, assuming - # the dict is equivalent of struct in Matlab - output['rowheaders'] = output['textdata'] - elif len(output['textdata']) == dataColumns: # TODO fix this - output['colheaders'] = output['textdata'] + print(output["textdata"]) + if ( + headerRows == dataColumns and len(output["textdata"]) == 1 + ): # getting the col size, assuming + # the dict is equivalent of struct in Matlab + output["rowheaders"] = output["textdata"] + elif len(output["textdata"]) == dataColumns: # TODO fix this + output["colheaders"] = output["textdata"] # making changes to data to fit the Matlab function headerRows = float(headerRows) - return output, delimiter, headerRows \ No newline at end of file + return output, delimiter, headerRows diff --git a/functions/SEBEfiles/sunmapcreator_2015a.py b/functions/SEBEfiles/sunmapcreator_2015a.py index 56e4629..e64cad8 100644 --- a/functions/SEBEfiles/sunmapcreator_2015a.py +++ b/functions/SEBEfiles/sunmapcreator_2015a.py @@ -4,11 +4,15 @@ import numpy as np from ...util.SEBESOLWEIGCommonFiles.diffusefraction import diffusefraction from ...util.SEBESOLWEIGCommonFiles.Perez_v3 import Perez_v3 -from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import clearnessindex_2013b +from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import ( + clearnessindex_2013b, +) from ...util.SEBESOLWEIGCommonFiles.create_patches import create_patches -def sunmapcreator_2015a(met, altitude, azimuth, onlyglobal, output, jday, albedo, location, zen): +def sunmapcreator_2015a( + met, altitude, azimuth, onlyglobal, output, jday, albedo, location, zen +): """ % This function creates a sun map based on hourly values of solar radiation. @@ -21,16 +25,23 @@ def sunmapcreator_2015a(met, altitude, azimuth, onlyglobal, output, jday, albedo :param albedo: :return: """ - np.seterr(over='raise') - np.seterr(invalid='raise') - + np.seterr(over="raise") + np.seterr(invalid="raise") # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) - patch_option = 1 # 145 patches + patch_option = 1 # 145 patches # patch_option = 2 # 153 patches # patch_option = 3 # 306 patches - # patch_option = 4 # 612 patches - skyvaultalt, skyvaultazi, annulino, skyvaultaltint, aziinterval, skyvaultaziint, azistart = create_patches(patch_option) + # patch_option = 4 # 612 patches + ( + skyvaultalt, + skyvaultazi, + annulino, + skyvaultaltint, + aziinterval, + skyvaultaziint, + azistart, + ) = create_patches(patch_option) # skyvaultaltint = np.array([6, 18, 30, 42, 54, 66, 78, 90]) # skyvaultaziint = np.array([12, 12, 15, 15, 20, 30, 60, 360]) @@ -44,7 +55,7 @@ def sunmapcreator_2015a(met, altitude, azimuth, onlyglobal, output, jday, albedo # skyvaultazi = np.append(skyvaultazi, k*skyvaultaziint[j] + azistart[j]) # if skyvaultazi[-1] > 360: # skyvaultazi[-1] = skyvaultazi[-1] - 360 - # index = index + 1 + # index = index + 1 iangle2 = np.array([]) Gyear = 0 @@ -52,11 +63,19 @@ def sunmapcreator_2015a(met, altitude, azimuth, onlyglobal, output, jday, albedo Gmonth = np.zeros([1, 12]) Dmonth = Gmonth for j in range(len(aziinterval)): - iangle2 = np.append(iangle2, skyvaultaltint[j] * np.ones([1, aziinterval[j]])) - - radmatI = np.transpose(np.vstack((iangle2, skyvaultazi, np.zeros((13, len(iangle2)))))) - radmatD = np.transpose(np.vstack((iangle2, skyvaultazi, np.zeros((13, len(iangle2)))))) - radmatR = np.transpose(np.vstack((iangle2, skyvaultazi, np.zeros((13, len(iangle2)))))) + iangle2 = np.append( + iangle2, skyvaultaltint[j] * np.ones([1, aziinterval[j]]) + ) + + radmatI = np.transpose( + np.vstack((iangle2, skyvaultazi, np.zeros((13, len(iangle2))))) + ) + radmatD = np.transpose( + np.vstack((iangle2, skyvaultazi, np.zeros((13, len(iangle2))))) + ) + radmatR = np.transpose( + np.vstack((iangle2, skyvaultazi, np.zeros((13, len(iangle2))))) + ) iazimuth = skyvaultazi # Ta = met[:, 11] @@ -66,17 +85,31 @@ def sunmapcreator_2015a(met, altitude, azimuth, onlyglobal, output, jday, albedo for i in range(len(met[:, 0])): alt = altitude[0, i] azi = azimuth[0, i] - #disp(alt) + # disp(alt) if alt > 2: # Estimation of radD and radI if not measured after Reindl et al. (1990) if onlyglobal: - if met[i, 11] <= -999.00 or met[i, 10] <= -999.00 or np.isnan(met[i, 11]) or np.isnan(met[i, 10]): + if ( + met[i, 11] <= -999.00 + or met[i, 10] <= -999.00 + or np.isnan(met[i, 11]) + or np.isnan(met[i, 10]) + ): met[i, 11] = 15.0 met[i, 10] = 75.0 - I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b(zen[0, i], jday[0, i], met[i, 11], met[i, 10], - met[i, 14], location, -999.0) - I, D = diffusefraction(met[i, 14], altitude[0, i], Kt, met[i, 11], met[i, 10]) + I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( + zen[0, i], + jday[0, i], + met[i, 11], + met[i, 10], + met[i, 14], + location, + -999.0, + ) + I, D = diffusefraction( + met[i, 14], altitude[0, i], Kt, met[i, 11], met[i, 10] + ) else: I = met[i, 22] D = met[i, 21] @@ -84,25 +117,39 @@ def sunmapcreator_2015a(met, altitude, azimuth, onlyglobal, output, jday, albedo G = met[i, 14] # Anisotropic diffuse distribution (Perez et al., 1993/Robinson & Stone, 2004) - lv, _, _ = Perez_v3(90-altitude[0, i], azimuth[0, i], D, I, jday[0, i], 1, patch_option) - - distalt = np.abs(iangle2-alt) + lv, _, _ = Perez_v3( + 90 - altitude[0, i], + azimuth[0, i], + D, + I, + jday[0, i], + 1, + patch_option, + ) + + distalt = np.abs(iangle2 - alt) altlevel = distalt == (np.min(np.abs(distalt))) - distazi = np.abs(iazimuth-azi) + distazi = np.abs(iazimuth - azi) azipos = distazi[altlevel] == (np.min(distazi[altlevel])) azipos2 = np.where(altlevel)[0][0] + np.where(azipos)[0][0] - #azipos2 = np.where(altlevel)[0] + np.where(azipos)[0] - #azipos2 = find(altlevel, 1)-1 + find(azipos, 1) + # azipos2 = np.where(altlevel)[0] + np.where(azipos)[0] + # azipos2 = find(altlevel, 1)-1 + find(azipos, 1) radmatI[azipos2, 2] = radmatI[azipos2, 2] + I - radmatD[:, 2] = radmatD[:, 2] + D*lv[:, 2] - radmatR[:, 2] = radmatR[:, 2] + G*(1/145)*albedo + radmatD[:, 2] = radmatD[:, 2] + D * lv[:, 2] + radmatR[:, 2] = radmatR[:, 2] + G * (1 / 145) * albedo # Gyear=Gyear+(G*sin(altitude(i)*(pi/180))); # Dyear=Dyear+D; - if output['energymonth'] == 1: - radmatI[azipos2, met[i, 1] + 2] = radmatI[azipos2, met[i, 1] + 2] + I - radmatD[:, met[i, 1] + 2] = radmatD[:, met[i, 1] + 2] + D*lv[:, 2] - radmatR[:, met[i, 1] + 2] = radmatR[:, met[i, 1] + 2] + G*(1/145)*albedo + if output["energymonth"] == 1: + radmatI[azipos2, met[i, 1] + 2] = ( + radmatI[azipos2, met[i, 1] + 2] + I + ) + radmatD[:, met[i, 1] + 2] = ( + radmatD[:, met[i, 1] + 2] + D * lv[:, 2] + ) + radmatR[:, met[i, 1] + 2] = ( + radmatR[:, met[i, 1] + 2] + G * (1 / 145) * albedo + ) # Gmonth(met(i,2))=Gmonth(met(i,2))+(G*sin(altitude(i)*(pi/180))); # Dmonth(met(i,2))=Dmonth(met(i,2))+D; @@ -114,10 +161,10 @@ def sunmapcreator_2015a(met, altitude, azimuth, onlyglobal, output, jday, albedo # Adjusting the numbers if multiple years is used if np.shape(met)[0] > 8760: - multiyear = np.shape(met)[0]/8760 - radmatI[:, 2:15] = radmatI[:, 2:15]/multiyear - radmatD[:, 2:15] = radmatD[:, 2:15]/multiyear - radmatR[:, 2:15] = radmatR[:, 2:15]/multiyear + multiyear = np.shape(met)[0] / 8760 + radmatI[:, 2:15] = radmatI[:, 2:15] / multiyear + radmatD[:, 2:15] = radmatD[:, 2:15] / multiyear + radmatR[:, 2:15] = radmatR[:, 2:15] / multiyear # Gyear=Gyear/multiyear; # Dyear=Dyear/multiyear; diff --git a/functions/SOLWEIGpython/COMFA/CNRRabs_Total.py b/functions/SOLWEIGpython/COMFA/CNRRabs_Total.py index 75d7a60..eef0828 100644 --- a/functions/SOLWEIGpython/COMFA/CNRRabs_Total.py +++ b/functions/SOLWEIGpython/COMFA/CNRRabs_Total.py @@ -2,59 +2,76 @@ # from libsmop import * # CNRRabs_Total.m - + # @function -def CNRRabs_Total(alpha=None,L=None,D=None,Lin=None,Lup=None,Kin=None,Kup=None,Atr=None,d=None,t=None,lat=None,A=None,*args,**kwargs): +def CNRRabs_Total( + alpha=None, + L=None, + D=None, + Lin=None, + Lup=None, + Kin=None, + Kup=None, + Atr=None, + d=None, + t=None, + lat=None, + A=None, + *args, + **kwargs +): varargin = CNRRabs_Total.varargin nargin = CNRRabs_Total.nargin - # VERTICAL CYLINDER MODEL + # VERTICAL CYLINDER MODEL # Used to calculate the total radiation absorbed by a cylinder, such as the CRT, with inputs # alpha (albedo of cylinder), Kin (incoming shortwave measured by CNR net # radiometer, W/m2), Kup (reflected shortwave measured by CNR, W/m2), Lin # (CNR incoming longwave radiation, W/m2), Lup (CNR1 outgoing longwave # radition, W/m2), L (length of cylinder, m), D (diamter of cylinder, m). - # - #Also need solar zenith, which is calculated from: lat (latitude, degrees), d (days, Jan 1=1), t (decimal + # + # Also need solar zenith, which is calculated from: lat (latitude, degrees), d (days, Jan 1=1), t (decimal # time, 24h decimal time). - - #Atr - atmospheric transmittance - can calculate for the day/time of day or - #estimate based on sky conditions. - + + # Atr - atmospheric transmittance - can calculate for the day/time of day or + # estimate based on sky conditions. + # LOAD data .... - - #set constants - D=0.01 + + # set constants + D = 0.01 # CNRRabs_Total.m:20 - - L=0.1 + + L = 0.1 # CNRRabs_Total.m:21 - - alpha=0.37 + + alpha = 0.37 # CNRRabs_Total.m:22 - - # E.g., + + # E.g., # Kin = metdata(:,10); #put in column for Kin from metdata file - # Kup = metdata(:,11); + # Kup = metdata(:,11); # Lin = metdata(:,20); - # Lup = metdata(:,21); + # Lup = metdata(:,21); # d = metdata(:,24); - # Atr = 0.70; + # Atr = 0.70; # lat = 33.75; # A = 992; - - Aeff=0.78 + + Aeff = 0.78 # CNRRabs_Total.m:33 - - Kin_abs=CNR_Kinabs_meas(alpha,Kin,L,D,A,lat,d,t,Atr) + + Kin_abs = CNR_Kinabs_meas(alpha, Kin, L, D, A, lat, d, t, Atr) # CNRRabs_Total.m:35 - Kup_abs=CNR_Kup(alpha,Kup,L,D) + Kup_abs = CNR_Kup(alpha, Kup, L, D) # CNRRabs_Total.m:37 - Lin_abs=LinMeas_abs(L,D,Lin) + Lin_abs = LinMeas_abs(L, D, Lin) # CNRRabs_Total.m:39 - Lup_abs=LupMeas_abs(L,D,Lup) + Lup_abs = LupMeas_abs(L, D, Lup) # CNRRabs_Total.m:41 - Acyl=CRT_Acyl(L,D) + Acyl = CRT_Acyl(L, D) # CNRRabs_Total.m:43 - Total_CNRRabs=multiply(Aeff,((Kin_abs + Kup_abs + Lin_abs + Lup_abs) / (Acyl))) - # CNRRabs_Total.m:46 \ No newline at end of file + Total_CNRRabs = multiply( + Aeff, ((Kin_abs + Kup_abs + Lin_abs + Lup_abs) / (Acyl)) + ) + # CNRRabs_Total.m:46 diff --git a/functions/SOLWEIGpython/COMFA/CNR_Kinabs_meas_old.py b/functions/SOLWEIGpython/COMFA/CNR_Kinabs_meas_old.py index 8f73fec..2f9f85d 100644 --- a/functions/SOLWEIGpython/COMFA/CNR_Kinabs_meas_old.py +++ b/functions/SOLWEIGpython/COMFA/CNR_Kinabs_meas_old.py @@ -1,46 +1,61 @@ # Generated with SMOP 0.41 from libsmop import * + # CNR_Kinabs_meas.m - + @function -def CNR_Kinabs_meas(alpha=None,Kin=None,L=None,D=None,A=None,lat=None,d=None,t=None,Atr=None,*args,**kwargs): +def CNR_Kinabs_meas( + alpha=None, + Kin=None, + L=None, + D=None, + A=None, + lat=None, + d=None, + t=None, + Atr=None, + *args, + **kwargs +): varargin = CNR_Kinabs_meas.varargin nargin = CNR_Kinabs_meas.nargin - #VERTICAL CYLINDER MODEL USING THE PERPENDICULAR DIRECT IRRADIANCE AND -#DIFFUSE BEAMS SEPARATELY -#Used to determine the total incoming solar radiation (K) absorbed by the -#human using the measured CNR1 net radiometer readings (W/m2), with inputs alpha (albedo of the cylinder), Kin (incoming solar -#radiation measured by the net radiometer), L (length of the cylinder, cm), -#D (diameter of the cylinder, cm), A is altitude in m, lat (latitude, -#degrees), d (day, Jan 1 =1), and t (time, in decimal 24 hours) - - Kb=Ratio_Kb(Kin,A,lat,d,t,Atr) -# CNR_Kinabs_meas.m:11 - - Kd=Kin - Kb -# CNR_Kinabs_meas.m:13 - - zen=solar_zenith(lat,d,t) -# CNR_Kinabs_meas.m:15 - - Acs=CRT_Acs(L,D) -# CNR_Kinabs_meas.m:17 - - Kb_abs=multiply(multiply((1 - alpha),(multiply(Kb,sind(zen)))),Acs) -# CNR_Kinabs_meas.m:19 - - Acyl=CRT_Acyl(L,D) -# CNR_Kinabs_meas.m:21 - - Kd_abs=multiply(multiply(multiply((1 - alpha),Kd),0.5),Acyl) -# CNR_Kinabs_meas.m:23 - - Kin_abs=(Kb_abs + Kd_abs) + # VERTICAL CYLINDER MODEL USING THE PERPENDICULAR DIRECT IRRADIANCE AND + # DIFFUSE BEAMS SEPARATELY + # Used to determine the total incoming solar radiation (K) absorbed by the + # human using the measured CNR1 net radiometer readings (W/m2), with inputs alpha (albedo of the cylinder), Kin (incoming solar + # radiation measured by the net radiometer), L (length of the cylinder, cm), + # D (diameter of the cylinder, cm), A is altitude in m, lat (latitude, + # degrees), d (day, Jan 1 =1), and t (time, in decimal 24 hours) + + Kb = Ratio_Kb(Kin, A, lat, d, t, Atr) + # CNR_Kinabs_meas.m:11 + + Kd = Kin - Kb + # CNR_Kinabs_meas.m:13 + + zen = solar_zenith(lat, d, t) + # CNR_Kinabs_meas.m:15 + + Acs = CRT_Acs(L, D) + # CNR_Kinabs_meas.m:17 + + Kb_abs = multiply(multiply((1 - alpha), (multiply(Kb, sind(zen)))), Acs) + # CNR_Kinabs_meas.m:19 + + Acyl = CRT_Acyl(L, D) + # CNR_Kinabs_meas.m:21 + + Kd_abs = multiply(multiply(multiply((1 - alpha), Kd), 0.5), Acyl) + # CNR_Kinabs_meas.m:23 + + Kin_abs = Kb_abs + Kd_abs + + # CNR_Kinabs_meas.m:25 - - # Written August, 2006 by Natasha Kenny + +# Written August, 2006 by Natasha Kenny # Revised January, 2007 to break up the diffuse and beam radiation # updated Jan 2016 Aaron Hardin & Jenni Vanos with explanations from Terry -# Gillespie for ratio model \ No newline at end of file +# Gillespie for ratio model diff --git a/functions/SOLWEIGpython/COMFA/CNR_Kup.py b/functions/SOLWEIGpython/COMFA/CNR_Kup.py index 09af71f..e1a3385 100644 --- a/functions/SOLWEIGpython/COMFA/CNR_Kup.py +++ b/functions/SOLWEIGpython/COMFA/CNR_Kup.py @@ -1,23 +1,26 @@ # Generated with SMOP 0.41 from libsmop import * + # CNR_Kup.m - + @function -def CNR_Kup(alpha=None,Kup=None,L=None,D=None,*args,**kwargs): +def CNR_Kup(alpha=None, Kup=None, L=None, D=None, *args, **kwargs): varargin = CNR_Kup.varargin nargin = CNR_Kup.nargin # Used to calculate the total reflected solar radiation (K) absorbed by the -# a cylinder with inputs alpha (cylinder albedo), Kup (measured reflected solar -# radiation by the CNR net radiometer (W/m2), Acyl (area of the cylinder, -# m2) - - Acyl=CRT_Acyl(L,D) -# CNR_Kup.m:8 - - Kup_abs=multiply(multiply(multiply((1 - alpha),Kup),0.5),Acyl) + # a cylinder with inputs alpha (cylinder albedo), Kup (measured reflected solar + # radiation by the CNR net radiometer (W/m2), Acyl (area of the cylinder, + # m2) + + Acyl = CRT_Acyl(L, D) + # CNR_Kup.m:8 + + Kup_abs = multiply(multiply(multiply((1 - alpha), Kup), 0.5), Acyl) + + # CNR_Kup.m:10 - - # Written August, 2006 by Natasha Kenny -# Updated Comments Jan 2016 Jenni Vanos \ No newline at end of file + +# Written August, 2006 by Natasha Kenny +# Updated Comments Jan 2016 Jenni Vanos diff --git a/functions/SOLWEIGpython/COMFA/COMFA_BUDGET.py b/functions/SOLWEIGpython/COMFA/COMFA_BUDGET.py index 4fc0557..c2151a0 100644 --- a/functions/SOLWEIGpython/COMFA/COMFA_BUDGET.py +++ b/functions/SOLWEIGpython/COMFA/COMFA_BUDGET.py @@ -1,5 +1,6 @@ import numpy as np + def COMFA_Mact(weight, height, sex, age, activity, activity_unit): # Weight of kid (kg) # weight = 20 @@ -17,43 +18,44 @@ def COMFA_Mact(weight, height, sex, age, activity, activity_unit): ### METABOLIC HEAT CALCULATION FOR CHILDREN ### # Body surface area of child or adult (different for infant according to Haylock et al. (1978)) BSA = weight**0.5378 * height**0.3964 * 0.0242 - - if activity_unit == 'MET': + + if activity_unit == "MET": if sex == 2: - if ((age >= 3) and (age <= 10)): + if (age >= 3) and (age <= 10): # Resting metabolic rate of a 3-10 year old girl (kJ/day) (Cheng & Brown, 2020) - RMR_J = 16.97*weight + 1.618*height + 371.2 - elif ((age > 10) and (age <= 18)): + RMR_J = 16.97 * weight + 1.618 * height + 371.2 + elif (age > 10) and (age <= 18): # Resting metabolic rate of a 10-18 year old girl (kJ/day) (Cheng & Brown, 2020) - RMR_J = 8.365*weight + 4.65*height + 200 + RMR_J = 8.365 * weight + 4.65 * height + 200 else: - RMR_J = 8.365*weight + 4.65*height + 200 + RMR_J = 8.365 * weight + 4.65 * height + 200 elif sex == 1: - if ((age >= 3) and (age <= 10)): + if (age >= 3) and (age <= 10): # Resting metabolic rate of a 3-10 year old boy (kJ/day) (Cheng & Brown, 2020) - RMR_J = 19.6*weight + 1.033*height + 414.9 - elif ((age > 10) and (age <= 18)): + RMR_J = 19.6 * weight + 1.033 * height + 414.9 + elif (age > 10) and (age <= 18): # Resting metabolic rate of a 10-18 year old boy (kJ/day) (Cheng & Brown, 2020) - RMR_J = 16.25*weight + 1.372*height + 515.5 + RMR_J = 16.25 * weight + 1.372 * height + 515.5 else: - RMR_J = 8.365*weight + 4.65*height + 200 + RMR_J = 8.365 * weight + 4.65 * height + 200 # Convert to per hour. Original (kJ/day) = 24 hours. RMR_J = RMR_J / 24 # Resting metabolic rate in Wm-2 for person - RMR = RMR_J/BSA + RMR = RMR_J / BSA # Total metabolic energy by a person - Mact = RMR * activity # * activity_time + Mact = RMR * activity # * activity_time # Total metabolic energy by a person for PET Mact_PET = Mact * BSA - elif activity_unit == 'W': + elif activity_unit == "W": Mact = activity / BSA Mact_PET = activity return Mact, Mact_PET + def COMFA_e(Ta, RH): - # Tetens equation to calculate saturated vapour pressure (kPa) ata given air temperaure, + # Tetens equation to calculate saturated vapour pressure (kPa) ata given air temperaure, # with inputs Ta (air temperature, degrees C). # From Natasha Kenny and re-written by Jenni Vanos, April 2009, translated from MATLAB to Python by Nils Wallenberg 2022. # Saturated Vapour Pressure (kPa) @@ -68,9 +70,10 @@ def COMFA_e(Ta, RH): return ea -def COMFA_MET(Mact, Ta, RH): + +def COMFA_MET(Mact, Ta, RH): # Ambient vapour pressure - ea = COMFA_e(Ta, RH) + ea = COMFA_e(Ta, RH) # Heat consumed when breathing f = 0.150 - 0.0173 * ea - 0.0014 * Ta @@ -78,11 +81,12 @@ def COMFA_MET(Mact, Ta, RH): return MET + def COMFA_rc(va, rco): - # Calculates the clothing resistance for + # Calculates the clothing resistance for # input for the COMFA energy balance equation with inputs rco (insulation # value of clothing, s/m), va (activity wind velocity in m/s), vw - # (windspeed, m/s). Note that rco must be known based on clothing worn. + # (windspeed, m/s). Note that rco must be known based on clothing worn. if va == 0: rc = rco @@ -93,11 +97,11 @@ def COMFA_rc(va, rco): # This is new equation from Kenny et al. (2009b) - IJB. # Alternative method for standing individual when clothing isn't known: - - # UTCI regression equation (Havenith 2012 IJB). - # - # Calculates the clothing resistance for input for the COMFA energy balance - # equation with inputs of Tair (degrees C) to calculate the Icl (clo)based - # on air temperature from the UTCI equation. See Vanos, 2012b. + # UTCI regression equation (Havenith 2012 IJB). + # + # Calculates the clothing resistance for input for the COMFA energy balance + # equation with inputs of Tair (degrees C) to calculate the Icl (clo)based + # on air temperature from the UTCI equation. See Vanos, 2012b. # For use on standing individuals only. Can be slowly walking or standing. # Written Oct 2009 by Jenni Vanos @@ -105,11 +109,12 @@ def COMFA_rc(va, rco): # Icl = 1.372 - (0.01866*Tair) - (0.0004849*(Tair.^2)) - (0.000009333*(Tair.^3)); %Icl here is in clo. # Icl equation is from UTCI, and more info can be found in Vanos et al. - # (2012b). + # (2012b). # rc = Icl*186.6; %186.6 is the conversion to put a clo into a s/m. return rc + def COMFA_vr(vw, va): # Written Oct 2009 by Jenni Vanos # This equation calculates the relative windspeed (vr) in m/s using the @@ -118,10 +123,11 @@ def COMFA_vr(vw, va): vr = np.sqrt(va**2 + vw**2) # To acount for wind direction and activity direction, see Vanos et al. - # (2012b) using angular equation from ISO for Vr. + # (2012b) using angular equation from ISO for Vr. return vr + def COMFA_ra(vw, va): # COMFA equation input under Convective Heat Loss or Gain (CONV) @@ -139,16 +145,16 @@ def COMFA_ra(vw, va): Pr = 0.71 vr = COMFA_vr(vw, va) - - Re = vr * D / v + + Re = vr * D / v if Re < 4000: A = 0.683 n = 0.466 - elif ((Re >= 4000) and (Re < 40000)): + elif (Re >= 4000) and (Re < 40000): A = 0.193 n = 0.618 - else: # Re >= 40000 + else: # Re >= 40000 A = 0.0266 n = 0.805 @@ -156,24 +162,25 @@ def COMFA_ra(vw, va): # A = Re # n = Re - + # L = Re < 4000 # M = ((Re >= 4000) and (Re < 40000)) # H = Re >= 40000 - - # A[L] = 0.683 - # n[L] = 0.466 + + # A[L] = 0.683 + # n[L] = 0.466 # A[M] = 0.193 # n[M] = 0.618 - + # A[H] = 0.0266 # n[H] = 0.805 - + # Written by N. Kenny April 2006. Translated from MATLAB to Python by Nils Wallenberg 2022. return ra + def COMFA_Es(Mact, Ta, RH, age, kid): # Calculates sensible evaporative heat losses through Perspiration (W/m2) # with inputs Tair, Mact, RH @@ -185,12 +192,12 @@ def COMFA_Es(Mact, Ta, RH, age, kid): else: Es = 0.42 * (MET - 58) - # Equation from Fanger (1970) for "comfortable" persons. - # Es = 0 if Met < 58. + # Equation from Fanger (1970) for "comfortable" persons. + # Es = 0 if Met < 58. # Written by Natasha Kenny, June 2006 for input into the COMFA energy # balance model under Evaporative Heat Loss (EVAP) - # Now used by J. Vanos for Master's project, 2009. + # Now used by J. Vanos for Master's project, 2009. # Based on data presented by Fanger (1972), Brown and # Gillespie (1986) suggest that body tissue resistance @@ -198,8 +205,9 @@ def COMFA_Es(Mact, Ta, RH, age, kid): return Es + def COMFA_rsk(Mact, Ta, RH, age, kid): - # Same as rt (or skin tissue resistance) in papers. + # Same as rt (or skin tissue resistance) in papers. # New equation added to COMFA model, from Kenny et al. (2009b in IJB). # Adapted April 2009 by Jenni Vanos for input into the COMFA equation under # Convective Heat Loss or Gain (CONV) @@ -208,15 +216,16 @@ def COMFA_rsk(Mact, Ta, RH, age, kid): # inputs: rho = density of air = ~1.16kg/m3; Cp = heat capacity of air = # 1000J/kg K), Es = sensible evaporative heat losses. - # Evaporatve heat loss through perspiration. - Es = COMFA_Es(Mact, Ta, RH, age, kid) + # Evaporatve heat loss through perspiration. + Es = COMFA_Es(Mact, Ta, RH, age, kid) # 1212 is volumetric heat capacity (or rho.*Cp) rsk = (1212) / (0.13 * Es + 15) - # See Kenny et al. (2009b) pg 434 for derivation of rsk based on Kerslake 1972. + # See Kenny et al. (2009b) pg 434 for derivation of rsk based on Kerslake 1972. return rsk + def COMFA_Tc(Mact, Ta, RH): # Calculates core body temperature with inputs Mact (Metabolic Activity, W/m2), # Tair (air temperature in C), RH (relative humdity, %) @@ -225,14 +234,15 @@ def COMFA_Tc(Mact, Ta, RH): Tc = 36.5 + (0.0043 * MET) - # Written April 2009 by Jenni Vanos from Natasha Kenny, June 2006, for input into the COMFA energy balance + # Written April 2009 by Jenni Vanos from Natasha Kenny, June 2006, for input into the COMFA energy balance # model under Convective Heat Gain or Loss (CONV). See Kenny et al. (2009b) - # in IJB for details. + # in IJB for details. - # note this is not created for high intensity activity. + # note this is not created for high intensity activity. return Tc + def COMFA_Tsk(Mact, Ta, RH, vw, va, rco, age, kid): # Calculates skin temperature for input into the COMFA energy balance # equation with inputs Mact (metabolic activity, W/m2), Tair (air temp, @@ -240,7 +250,7 @@ def COMFA_Tsk(Mact, Ta, RH, vw, va, rco, age, kid): # velocity m/s) # s m-1 - rsk = COMFA_rsk(Mact, Ta, RH, age, kid); + rsk = COMFA_rsk(Mact, Ta, RH, age, kid) # s m-1 ra = COMFA_ra(vw, va) # degC @@ -248,15 +258,16 @@ def COMFA_Tsk(Mact, Ta, RH, vw, va, rco, age, kid): # s m-1 rc = COMFA_rc(va, rco) # degC - Tsk = (( Tc - Ta ) / (rsk + rc + ra)) * (ra + rc) + Ta + Tsk = ((Tc - Ta) / (rsk + rc + ra)) * (ra + rc) + Ta # all resisteances are in s/m. - + # Equation for Tsk adapted from Kenny et al. (2009b) in IJB. Modified April 2009 by - # Jenni Vanos. + # Jenni Vanos. return Tsk + def COMFA_CONV(Mact, Ta, RH, vw, va, rco, weight, height, age, kid): # Calculates the total heat loss (W/m2) by convection # with inputs Tair (air temperature, degrees C), Mact (metabolic activity, @@ -265,69 +276,72 @@ def COMFA_CONV(Mact, Ta, RH, vw, va, rco, weight, height, age, kid): # activity velocity, and vw = wind velocity # clothing resistance (Input should be va, not Ta as in original code?) - rc = COMFA_rc(va, rco) + rc = COMFA_rc(va, rco) # resistance of boundary layer ra = COMFA_ra(vw, va) # Skin temperature - Tsk = COMFA_Tsk(Mact, Ta, RH, vw, va, rco, age, kid) - # Final convection value + Tsk = COMFA_Tsk(Mact, Ta, RH, vw, va, rco, age, kid) + # Final convection value if kid: # Body to surface area of kid BSA_k = weight**0.5378 * height**0.3964 * 0.0242 - BMI_k = weight/(height/100)**2 + BMI_k = weight / (height / 100) ** 2 # Body-to-surface area of adult (weight = 65 kg and height = 176 cm, according to Cheng & Brown, 2020) adult_weight = 65 adult_height = 176 BSA_a = adult_weight**0.5378 * adult_height**0.3964 * 0.0242 - BMI_a = adult_weight/(adult_height/100)**2 + BMI_a = adult_weight / (adult_height / 100) ** 2 - CONV = (1212 * (Tsk - Ta) / (rc + ra)) * ((BSA_k/weight)/(BSA_a/adult_weight)) + CONV = (1212 * (Tsk - Ta) / (rc + ra)) * ( + (BSA_k / weight) / (BSA_a / adult_weight) + ) else: CONV = 1212 * (Tsk - Ta) / (rc + ra) - # 1212 is just an pre-calculated volumetric heat capapcity of air at 20oC ("rhoCp") of air (J m?3 K?1), + # 1212 is just an pre-calculated volumetric heat capapcity of air at 20oC ("rhoCp") of air (J m?3 K?1), # assuming air density (rho) of ~1.2kg m-3 and Cp of 1000 J kg-1 K-1 at ~20oC, - # which can be adapted based on air temperature - + # which can be adapted based on air temperature + # Initally modified April, 2007 by Natasha Kenny to incorporate revisions of heat flow from surface # temp to air temp, rather than from core temp to air temp. See Kenny et al. - # (2009a) IJB. + # (2009a) IJB. # Adapted Aug 2009 by Jenni Vanos in order to incorporate new Tsk # equation. Additional notes on rhoCp added by JV 2020. return CONV + def COMFA_rcv(vw, va, rcvo): # Written by Jenni Vanos April, 2009 for use in the updated COMFA model, # according to findings and changes made by Kenny et al. (2009). This is - # equation 6 from Kenny et al. (2009b), and has inputs rcvo (or the static - # clothing vapour resistance, which is defined for standardized conditions - # (static body, wind still, i.e. speed < 0.2 m s-1) (ISO, 2007). When air - # movement is present, or when the body moves, this will affect the vapour - # resistance (typically lowering it), in which case it is referred to as the + # equation 6 from Kenny et al. (2009b), and has inputs rcvo (or the static + # clothing vapour resistance, which is defined for standardized conditions + # (static body, wind still, i.e. speed < 0.2 m s-1) (ISO, 2007). When air + # movement is present, or when the body moves, this will affect the vapour + # resistance (typically lowering it), in which case it is referred to as the # resultant or dynamic basic water vapour resistance (ISO, 2007). # rcvo can be calculated for input using the equation: # rcvo = Icl.*0.1555.*0.18.*18400; %ISO (2007) conversion from clo to s/m. - # Where Icl is found in tables for specific clothing ensembles from ISO (2007), - # and 18400 is a conversion factor from Re,cl (vapour resistance, in m2kPaW-1) - # to rcvo, using Lv = 2.5×106 J kg-1, rho = 1.16 kg m-3, and Pa = 98kPa. - # See Kenny et al. (2009b) for further reference. + # Where Icl is found in tables for specific clothing ensembles from ISO (2007), + # and 18400 is a conversion factor from Re,cl (vapour resistance, in m2kPaW-1) + # to rcvo, using Lv = 2.5×106 J kg-1, rho = 1.16 kg m-3, and Pa = 98kPa. + # See Kenny et al. (2009b) for further reference. vr = COMFA_vr(vw, va) - rcv = rcvo * (-0.80 * (1 - np.exp(-vr / 1.095))+1) + rcv = rcvo * (-0.80 * (1 - np.exp(-vr / 1.095)) + 1) # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # Alternative method for standing individual when clothing isn't known: - - # UTCI regression equation (Havenith 2012 IJB). - - # Calculates the clothing resistance for input for the COMFA energy balance - # equation with inputs of Tair (degrees C) to calculate the Icl (clo)based - # on air temperature from the UTCI equation. See Vanos, 2012b. + # UTCI regression equation (Havenith 2012 IJB). + + # Calculates the clothing resistance for input for the COMFA energy balance + # equation with inputs of Tair (degrees C) to calculate the Icl (clo)based + # on air temperature from the UTCI equation. See Vanos, 2012b. # For use on standing individuals only. Can be slowly walking or standing. # Written Oct 2009 by Jenni Vanos @@ -335,14 +349,15 @@ def COMFA_rcv(vw, va, rcvo): # Icl = 1.372 - (0.01866*Tair) - (0.0004849*(Tair.^2)) - (0.000009333*(Tair.^3)); %Icl here is in clo. # Icl equation is from UTCI, and more info can be found in Vanos et al. - # (2012b). + # (2012b). # rcvo = Icl.*0.1555.*0.18.*18400; %ISO (2007) conversion from clo to s/m. # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% return rcv -def COMFA_rav(vw, va): + +def COMFA_rav(vw, va): # Calculates the resistance of the boundary air layer to vapour transport, # with input of relative wind velocity (m/s) @@ -351,24 +366,26 @@ def COMFA_rav(vw, va): rav = 0.92 * ra # Adapated April 2009 by Jenni Vanos to incorporate vr (m/s), which includes va and vw (m/s). - # Units of rav are in s/m. + # Units of rav are in s/m. return rav + def COMFA_qsk(Mact, Ta, RH, vw, va, rco, age, kid): # Calculates the saturation specific humidity (kg/kg) at skin temperature for input into the # COMFA energy balance equation with inputs Mact (metabolic activity), Tair - # (degrees C), RH (%), rco, va and Vr. + # (degrees C), RH (%), rco, va and Vr. # Assuming atmospheric pressure (Pa) is 1013 mb (101.3kPa) Tsk = COMFA_Tsk(Mact, Ta, RH, vw, va, rco, age, kid) - + esk = 0.61365 * np.exp((17.502 * Tsk) / (240.97 + Tsk)) qsk = 0.622 * (esk / (101.3 - esk)) return qsk + def COMFA_qa(Ta, RH): # Calculates saturation specific humidity (kg/kg) at air temperatre for input # into the COMFA energy balance equation, with inputs Tair (air @@ -376,31 +393,32 @@ def COMFA_qa(Ta, RH): # assuming atmospheric pressure is 1013 mb (101.3kPa) e = COMFA_e(Ta, RH) - + qa = 0.622 * e / (101.3 - e) - + # svp = Tetens_svp(Tair) - # + # # qa = svp.*(RH./100) - + # Written July, 2006 by Natasha Kenny, modified to reflect the changes to - # this equation. See usage also in Kenny et al. (2009b) - # Re-written April 2009 by Jenni Vanos. + # this equation. See usage also in Kenny et al. (2009b) + # Re-written April 2009 by Jenni Vanos. return qa + def COMFA_Ei(Mact, Ta, RH, vw, va, rco, rcvo, age, kid): # Calculates insensible evaporative heat losses in the COMFA energy balance # equation with inputs Tair (air temp, degrees C), RH (relative humidity %) - # vw (wind velocity (m/s), va (acivity velocity), Mact (Metabolic activity, W/m2), + # vw (wind velocity (m/s), va (acivity velocity), Mact (Metabolic activity, W/m2), # rco(clothing resistance, s/m), rcvo (clothing vapour resistance s/m) # Density of air in kg/m3 rho = 1.16 # Latent heat of vapourization (J/kg) - Lv = 2.442e+6 + Lv = 2.442e6 # Resistance of skin to vapour transfer (s/m) - Campbell 1977 - rskv = 7.7e+3 + rskv = 7.7e3 # clothing vapour resistance (s/m) rcv = COMFA_rcv(vw, va, rcvo) # Resistance of boundary air layer to vapour transport @@ -408,21 +426,22 @@ def COMFA_Ei(Mact, Ta, RH, vw, va, rco, rcvo, age, kid): # update qsk = COMFA_qsk(Mact, Ta, RH, vw, va, rco, age, kid) # update - qa = COMFA_qa(Ta, RH) + qa = COMFA_qa(Ta, RH) Ei = rho * Lv * ((qsk - qa) / (rcv + rav + rskv)) - + # note Ei is very unimportant. - + # Written July, 2006 by Natasha Kenny, adapted April 2009 by Jenni # Vanos to incorporate changes with vr and rcv from changes made by - # Kenny et al. (2009). + # Kenny et al. (2009). return Ei + def COMFA_Etot(Mact, Ta, RH, vw, va, rco, rcvo, age, kid): # Calculates the total evaporative heat loss for input into the COMFA - # equation with inputs (Tair air temp in C, RH Relative Humidity %, vw, + # equation with inputs (Tair air temp in C, RH Relative Humidity %, vw, # wind velocity (m/s), Mact, metabolic activity W/m2, rco (clothing resistance, s/m), va activity velocity (m/s), rcvo (clothing vapour resistance, s/m) not to be confused with # EVAP which is the final input in the equation @@ -431,33 +450,34 @@ def COMFA_Etot(Mact, Ta, RH, vw, va, rco, rcvo, age, kid): Es = COMFA_Es(Mact, Ta, RH, age, kid) Etot = Ei + Es - + # Written July, 2006 by Natasha Kenny. Adapted April 2009 by Jenni Vanos to - # include vr, and subtract P based on Kenny et al. (2009). + # include vr, and subtract P based on Kenny et al. (2009). return Etot + def COMFA_Em(Mact, Ta, RH, vw, va, rco, rcvo, age, kid): - # Calculates evaporative maximum, Em (W/m2) (where Em = the maximum possible evapoaration + # Calculates evaporative maximum, Em (W/m2) (where Em = the maximum possible evapoaration # possible given the environment and clothing, and assumes a skin wettedness value (w) of 1) # for input into COMFA equation. - + # Inputs vr (relative wind velocity, m/s) (vr = vw when va=0), Mact (metabolic # activity), Tair (air temperature, degrees C), RH (relative humdity, %) - + # Density of air kg m-3 rho = 1.16 # latent heat of vaporization J kg-1 - Lv = 2.442e+6 - + Lv = 2.442e6 + rav = COMFA_rav(vw, va) - + rcv = COMFA_rcv(vw, va, rcvo) - + qsk = COMFA_qsk(Mact, Ta, RH, vw, va, rco, age, kid) - + qa = COMFA_qa(Ta, RH) - + Em = rho * Lv * ((qsk - qa) / (rcv + rav)) # Written by Natasha Kenny, June 2006 for input into the COMFA equation. @@ -466,6 +486,7 @@ def COMFA_Em(Mact, Ta, RH, vw, va, rco, rcvo, age, kid): return Em + def COMFA_EVAP(Mact, Ta, RH, vw, va, rco, rcvo, age, kid): # Calculates the total evaporative heat losses for input into the COMFA @@ -475,24 +496,25 @@ def COMFA_EVAP(Mact, Ta, RH, vw, va, rco, rcvo, age, kid): Etot = COMFA_Etot(Mact, Ta, RH, vw, va, rco, rcvo, age, kid) Em = COMFA_Em(Mact, Ta, RH, vw, va, rco, rcvo, age, kid) - + if Etot <= Em: EVAP = Etot else: EVAP = Em # EVAP = np.zeros((Em.shape[0])) - + # ind = Etot <= Em - + # EVAP[ind] = Etot[ind] # The lower of E or Em is used in the energy budget equation. - - # Initially written by Kenny, modified by Jenni Vanos, April 2009. + + # Initially written by Kenny, modified by Jenni Vanos, April 2009. return EVAP + def COMFA_Ts(Mact, Ta, RH, vw, va, rco, age, kid): # Calculates the average surface temperature of a clothed person (degrees C) with inputs Tair (air # temperature, degrees C), rco (clothing resistance, s/m), vw (air velocity, s/m), Mact (Metabolic Activity, W/m2) @@ -505,73 +527,75 @@ def COMFA_Ts(Mact, Ta, RH, vw, va, rco, age, kid): # Average surface temperature of a person Ts = ((Tsk - Ta) / (rc + ra)) * ra + Ta - # Adapted from Natasha on April 2009 to include vr, va and take out P. + # Adapted from Natasha on April 2009 to include vr, va and take out P. # Modified April, 2007 by Natasha Kenny to revise the heat flow from the skin surface to the # air rather than from the core temperature to the air. return Ts + def COMFA_TREMIT(Mact, Ta, RH, vw, va, rco, age, kid): # Calculates the terrestrial radiation losses from the body for input into the COMFA # energy balance equation with inputs of Tair (air temp, degrees C), rco (clothing # insulation (s/m)), wind velocity (m/s), activity velocity (m/s)), and Mact, # (metabolic activity (w/m2)). - + # surface temperature (C) Ts = COMFA_Ts(Mact, Ta, RH, vw, va, rco, age, kid) # 0.95 is the emissivity of human (Campbell & Norman 1998) # 0.78 is the radiative surface area of a human standing (Kerslake, 1972) # 0.7 for sitting/on bicycle - TREMIT = 0.78 * (0.95 * 5.67e-8 * (Ts + 273.15)**4) + TREMIT = 0.78 * (0.95 * 5.67e-8 * (Ts + 273.15) ** 4) # Adapted April 2009 by J. Vanos to incorporate new vr geomtric equation (vr = # COMFA_vr(va, vw)), and take out P, which is no longer needed in the COMFA model - # (Kenny et al., 2009a,b). + # (Kenny et al., 2009a,b). # modified April, 2007 by Natasha Kenny to incorportate emissivity of 0.95 and radiative - # surface of a human 0.78 (this is for when walking or running). + # surface of a human 0.78 (this is for when walking or running). return TREMIT + def COMFA_BUDGET(Mact, Ta, RH, vw, va, rco, rcvo, weight, height, age, kid): - # Calculates the four energy fluxes MET, CONV, EVAP, and TREMIT in the COMFA energy budget, with inputs + # Calculates the four energy fluxes MET, CONV, EVAP, and TREMIT in the COMFA energy budget, with inputs # Tair (air temperature, degrees C), Metabolic activity (W/m2), Relative Humidty (%), - # Wind Velocity (m/s), activity velocity (va, m s-1), static clothing resistance (rco, sm-1), static clothing vapor reistance (rcvo, s m-1). + # Wind Velocity (m/s), activity velocity (va, m s-1), static clothing resistance (rco, sm-1), static clothing vapor reistance (rcvo, s m-1). # Note can also make Rabs an input. See below. ### This COFMA model is general for people at or near comfort, and is not - ### tested yet on extreme conditions. + ### tested yet on extreme conditions. - # %%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%% + # %%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%% # %%%% CLOTHING - generally can estimate Icl value (clo) and convert them to - # %%%% resistances as follows: + # %%%% resistances as follows: + + # Rco: rco = Icl * 186.6. Or convert from conductivity based on 1 Clo = 0.1555 (m 2K W-1). - # Rco: rco = Icl * 186.6. Or convert from conductivity based on 1 Clo = 0.1555 (m 2K W-1). + # NOTE: Icl values can be found in clo or conductivity values from ISO 9920 2007. - # NOTE: Icl values can be found in clo or conductivity values from ISO 9920 2007. + # Rcvo: Can convert from a conductivity value of Icl, where Icl is found in tables for specific clothing ensembles from ISO (2007). + # 1) convert from Icl to Re,cl (m2 kPa W-1): Re,cl = Icl (m 2K W-1)*0.18 (constant from ISO 9920 pg 12 based on 1 or 2-layer clothing ensembles). + # 2) Convert Re,cl to rcvo: rcvo = Re,cl*18,400, where 18400 is a conversion factor from Re,cl (vapour resistance, in m2kPaW-1) + # to rcvo, using Lv = 2.5*106 J kg-1, rho = 1.16 kg m-3, and Pa = 98kPa. + # %%%%%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%% - # Rcvo: Can convert from a conductivity value of Icl, where Icl is found in tables for specific clothing ensembles from ISO (2007). - # 1) convert from Icl to Re,cl (m2 kPa W-1): Re,cl = Icl (m 2K W-1)*0.18 (constant from ISO 9920 pg 12 based on 1 or 2-layer clothing ensembles). - # 2) Convert Re,cl to rcvo: rcvo = Re,cl*18,400, where 18400 is a conversion factor from Re,cl (vapour resistance, in m2kPaW-1) - # to rcvo, using Lv = 2.5*106 J kg-1, rho = 1.16 kg m-3, and Pa = 98kPa. - # %%%%%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%% - - # W m-2 %%%%random value ... Here you need to link to another dataset that calcs Rabs or bring o link to more doce. + # W m-2 %%%%random value ... Here you need to link to another dataset that calcs Rabs or bring o link to more doce. # Rabs = 400 - + MET = COMFA_MET(Mact, Ta, RH) CONV = COMFA_CONV(Mact, Ta, RH, vw, va, rco, weight, height, age, kid) EVAP = COMFA_EVAP(Mact, Ta, RH, vw, va, rco, rcvo, age, kid) - + TREMIT = COMFA_TREMIT(Mact, Ta, RH, vw, va, rco, age, kid) - + # BUDGET = MET + Rabs - CONV - EVAP - TREMIT # all are in W m-2 # return BUDGET - return MET, CONV, EVAP, TREMIT \ No newline at end of file + return MET, CONV, EVAP, TREMIT diff --git a/functions/SOLWEIGpython/COMFA/CRT_Acs.py b/functions/SOLWEIGpython/COMFA/CRT_Acs.py index 8aa20e0..4d130d3 100644 --- a/functions/SOLWEIGpython/COMFA/CRT_Acs.py +++ b/functions/SOLWEIGpython/COMFA/CRT_Acs.py @@ -1,21 +1,24 @@ # Generated with SMOP 0.41 from libsmop import * + # CRT_Acs.m - + @function -def CRT_Acs(L=None,D=None,*args,**kwargs): +def CRT_Acs(L=None, D=None, *args, **kwargs): varargin = CRT_Acs.varargin nargin = CRT_Acs.nargin # vertical cross sectional area of the cylinder in m - + # Written by Jenni Vanos, Aug 2009, to find the XC area of a cylinder, -# where L = length (m) and D = diameter (m)- #for CRT L = ~0.10m and D = -# 0.01m - ratio is most important part to keep constant for changing -# cylidner size. -# -#Used in Kb_abs = (1-alpha).* (Kb .* sind(zen)).* Acs; - - Acs=multiply(D,L) -# CRT_Acs.m:13 \ No newline at end of file + # where L = length (m) and D = diameter (m)- #for CRT L = ~0.10m and D = + # 0.01m - ratio is most important part to keep constant for changing + # cylidner size. + # + # Used in Kb_abs = (1-alpha).* (Kb .* sind(zen)).* Acs; + + Acs = multiply(D, L) + + +# CRT_Acs.m:13 diff --git a/functions/SOLWEIGpython/COMFA/CRT_Acyl.py b/functions/SOLWEIGpython/COMFA/CRT_Acyl.py index e83e9e9..3228955 100644 --- a/functions/SOLWEIGpython/COMFA/CRT_Acyl.py +++ b/functions/SOLWEIGpython/COMFA/CRT_Acyl.py @@ -1,20 +1,25 @@ # Generated with SMOP 0.41 from libsmop import * + # CRT_Acyl.m - + @function -def CRT_Acyl(L=None,D=None,*args,**kwargs): +def CRT_Acyl(L=None, D=None, *args, **kwargs): varargin = CRT_Acyl.varargin nargin = CRT_Acyl.nargin - #Function written August 2009 by Jenni Vanos for calculating the Area of -#the CRT for use in the CNR Comfa model for finidng Rabs. - - #L = length (m) - #for CRT this is ~0.10m -#D = diamater (m) - #for CRT this is ~0.01m - - pi=3.14 -# CRT_Acyl.m:10 - Acyl=multiply(multiply(2,pi),(D / 2) ** 2) + multiply(multiply(dot(2.0,pi),(D / 2)),L) -# CRT_Acyl.m:12 \ No newline at end of file + # Function written August 2009 by Jenni Vanos for calculating the Area of + # the CRT for use in the CNR Comfa model for finidng Rabs. + + # L = length (m) - #for CRT this is ~0.10m + # D = diamater (m) - #for CRT this is ~0.01m + + pi = 3.14 + # CRT_Acyl.m:10 + Acyl = multiply(multiply(2, pi), (D / 2) ** 2) + multiply( + multiply(dot(2.0, pi), (D / 2)), L + ) + + +# CRT_Acyl.m:12 diff --git a/functions/SOLWEIGpython/COMFA/ComfaPython20201023.py b/functions/SOLWEIGpython/COMFA/ComfaPython20201023.py index bd745a7..8030060 100644 --- a/functions/SOLWEIGpython/COMFA/ComfaPython20201023.py +++ b/functions/SOLWEIGpython/COMFA/ComfaPython20201023.py @@ -2,7 +2,7 @@ """ This code is a translation of radiation related function in the COMFA model -Translated on Fri Oct 23 +Translated on Fri Oct 23 @author: xlinfr """ @@ -10,12 +10,11 @@ from radiationfunctionsCOMFA import Rad_Total_solweig import numpy as np - # Radiation components in w m-2 -Lin = 200. -Lup = 300. -Kin = 800. -Kup = 200. +Lin = 200.0 +Lup = 300.0 +Kin = 800.0 +Kup = 200.0 # Time variables used in solweig version yyyy = 2020 @@ -31,7 +30,7 @@ # minu = 0 # Time vaiables used in original -t=hour + minu/60. # decimal time of day (up to 24) +t = hour + minu / 60.0 # decimal time of day (up to 24) if (yyyy % 4) == 0: if (yyyy % 100) == 0: if (yyyy % 400) == 0: @@ -47,29 +46,66 @@ else: dayspermonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] -doy = np.sum(dayspermonth[0:month - 1]) + day # day of year (Jan 1 = 1) +doy = np.sum(dayspermonth[0 : month - 1]) + day # day of year (Jan 1 = 1) # Lubbock 33.5779,-101.8552 -lat = 33.5779 # latitude -lon = -101.8552 # longitude -alt = 992. # Altitude (m asl) -utc = -6 # standard time lubbock - -Atr=0.7 # atmospheric transmittance -alpha = 0.37 # albedo of the cylinder -emis = 0.95 # emissivity of the cylinder -L=0.1 # length of cylinder (cm) -D=0.01 # Diameter of cylinder (cm) - -Aeff=0.78 # Effective area of body. 0.78 for standing from Campebll and Normal (1998) (0.70 for sitting) - - -Total_CNRRabs_solweig, zensolweig, Kdsolweig = Rad_Total_solweig(L,D,Lin,Lup,Kin,Kup,yyyy,month,day,hour,minu,lat,lon,alt, emis,alpha,Aeff,utc, Kd=None, Ta=None, RH=None) - -Total_CNRRabs, zenCOMFA, kdCOMFA = CNRRabs_Total(alpha, L, D, Lin, Lup, Kin, Kup, Atr, doy, t, np.array([lat]), alt, emis, Aeff) #np.array([lat]) to deal with timeseries? - -print('Total_CNRRabs: ' + str(Total_CNRRabs)) -print('altComfa: ' + str(90-zenCOMFA)) -print('Total_CNRRabs_solweig: ' + str(Total_CNRRabs_solweig)) -print('ZenSolweig: ' + str(90-zensolweig)) +lat = 33.5779 # latitude +lon = -101.8552 # longitude +alt = 992.0 # Altitude (m asl) +utc = -6 # standard time lubbock + +Atr = 0.7 # atmospheric transmittance +alpha = 0.37 # albedo of the cylinder +emis = 0.95 # emissivity of the cylinder +L = 0.1 # length of cylinder (cm) +D = 0.01 # Diameter of cylinder (cm) + +Aeff = 0.78 # Effective area of body. 0.78 for standing from Campebll and Normal (1998) (0.70 for sitting) + + +Total_CNRRabs_solweig, zensolweig, Kdsolweig = Rad_Total_solweig( + L, + D, + Lin, + Lup, + Kin, + Kup, + yyyy, + month, + day, + hour, + minu, + lat, + lon, + alt, + emis, + alpha, + Aeff, + utc, + Kd=None, + Ta=None, + RH=None, +) + +Total_CNRRabs, zenCOMFA, kdCOMFA = CNRRabs_Total( + alpha, + L, + D, + Lin, + Lup, + Kin, + Kup, + Atr, + doy, + t, + np.array([lat]), + alt, + emis, + Aeff, +) # np.array([lat]) to deal with timeseries? + +print("Total_CNRRabs: " + str(Total_CNRRabs)) +print("altComfa: " + str(90 - zenCOMFA)) +print("Total_CNRRabs_solweig: " + str(Total_CNRRabs_solweig)) +print("ZenSolweig: " + str(90 - zensolweig)) diff --git a/functions/SOLWEIGpython/COMFA/LinMeas_abs.py b/functions/SOLWEIGpython/COMFA/LinMeas_abs.py index 54168bc..8ea8179 100644 --- a/functions/SOLWEIGpython/COMFA/LinMeas_abs.py +++ b/functions/SOLWEIGpython/COMFA/LinMeas_abs.py @@ -1,23 +1,26 @@ # Generated with SMOP 0.41 from libsmop import * + # LinMeas_abs.m - + @function -def LinMeas_abs(L=None,D=None,Lin=None,*args,**kwargs): +def LinMeas_abs(L=None, D=None, Lin=None, *args, **kwargs): varargin = LinMeas_abs.varargin nargin = LinMeas_abs.nargin - #Used to Calculate the incoming longwave radiation absorbed by the cylinder with -#inputs epsilon (emissivity of the cylinder, 0.95), Ta (air temp, degreesC) -#L (length of cylinder, cm), D (diameter of cylinder, m) - - epsilon=0.95 -# LinMeas_abs.m:7 - Acyl=CRT_Acyl(L,D) -# LinMeas_abs.m:9 - LinMeas_abs=multiply(multiply(multiply(epsilon,Lin),0.5),Acyl) + # Used to Calculate the incoming longwave radiation absorbed by the cylinder with + # inputs epsilon (emissivity of the cylinder, 0.95), Ta (air temp, degreesC) + # L (length of cylinder, cm), D (diameter of cylinder, m) + + epsilon = 0.95 + # LinMeas_abs.m:7 + Acyl = CRT_Acyl(L, D) + # LinMeas_abs.m:9 + LinMeas_abs = multiply(multiply(multiply(epsilon, Lin), 0.5), Acyl) + + # LinMeas_abs.m:11 - - #Written August, 2006 by Natasha Kenny -#Updated/Commented Dec 2015 Jenni Vanos \ No newline at end of file + +# Written August, 2006 by Natasha Kenny +# Updated/Commented Dec 2015 Jenni Vanos diff --git a/functions/SOLWEIGpython/COMFA/LupMeas_abs.py b/functions/SOLWEIGpython/COMFA/LupMeas_abs.py index d1b3577..cefbbfc 100644 --- a/functions/SOLWEIGpython/COMFA/LupMeas_abs.py +++ b/functions/SOLWEIGpython/COMFA/LupMeas_abs.py @@ -1,25 +1,27 @@ # Generated with SMOP 0.41 from libsmop import * + # LupMeas_abs.m - + @function -def LupMeas_abs(L=None,D=None,Lup=None,*args,**kwargs): +def LupMeas_abs(L=None, D=None, Lup=None, *args, **kwargs): varargin = LupMeas_abs.varargin nargin = LupMeas_abs.nargin - #Used to calculate the estimated outgoing terrestrial longwave absorbed by the -#cylinder for use in the Rabs (Estimation) Model with inputs Ta (air temperature), -#L (length of cylinder, m), D (diameter of cylinder, cm) - - Acyl=CRT_Acyl(L,D) -# LupMeas_abs.m:7 - - epsilon=0.95 -# LupMeas_abs.m:9 - - - LupMeas_abs=multiply(multiply(multiply(epsilon,Lup),0.5),Acyl) + # Used to calculate the estimated outgoing terrestrial longwave absorbed by the + # cylinder for use in the Rabs (Estimation) Model with inputs Ta (air temperature), + # L (length of cylinder, m), D (diameter of cylinder, cm) + + Acyl = CRT_Acyl(L, D) + # LupMeas_abs.m:7 + + epsilon = 0.95 + # LupMeas_abs.m:9 + + LupMeas_abs = multiply(multiply(multiply(epsilon, Lup), 0.5), Acyl) + + # LupMeas_abs.m:11 - #Written August, 2006 by Natasha Kenny -#Updated/Commented Dec 2015 Jenni Vanos \ No newline at end of file +# Written August, 2006 by Natasha Kenny +# Updated/Commented Dec 2015 Jenni Vanos diff --git a/functions/SOLWEIGpython/COMFA/Ratio_Kb.py b/functions/SOLWEIGpython/COMFA/Ratio_Kb.py index be6df49..023ddf4 100644 --- a/functions/SOLWEIGpython/COMFA/Ratio_Kb.py +++ b/functions/SOLWEIGpython/COMFA/Ratio_Kb.py @@ -1,18 +1,23 @@ # Generated with SMOP 0.41 from libsmop import * + # Ratio_Kb.m - + @function -def Ratio_Kb(Kin=None,A=None,lat=None,d=None,t=None,Atr=None,*args,**kwargs): +def Ratio_Kb( + Kin=None, A=None, lat=None, d=None, t=None, Atr=None, *args, **kwargs +): varargin = Ratio_Kb.varargin nargin = Ratio_Kb.nargin # function is used to estimate incoming shortwave diffuse radiation under -# clear sky conditions with inputs A(alititude, m), lat(latitude, degrees), -# d (days, Jan 1 =1), t (time, 24 hours) - - m=opt_m(A,lat,d,t) -# Ratio_Kb.m:8 - Kb=Kin / ((1 + (multiply(0.3,(1 - Atr ** m)))) / (Atr ** m)) -# Ratio_Kb.m:10 \ No newline at end of file + # clear sky conditions with inputs A(alititude, m), lat(latitude, degrees), + # d (days, Jan 1 =1), t (time, 24 hours) + + m = opt_m(A, lat, d, t) + # Ratio_Kb.m:8 + Kb = Kin / ((1 + (multiply(0.3, (1 - Atr**m)))) / (Atr**m)) + + +# Ratio_Kb.m:10 diff --git a/functions/SOLWEIGpython/COMFA/atm_P.py b/functions/SOLWEIGpython/COMFA/atm_P.py index 53b94fb..f6f7084 100644 --- a/functions/SOLWEIGpython/COMFA/atm_P.py +++ b/functions/SOLWEIGpython/COMFA/atm_P.py @@ -1,26 +1,29 @@ # Generated with SMOP 0.41 from libsmop import * + # atm_P.m - + @function -def atm_P(A=None,*args,**kwargs): +def atm_P(A=None, *args, **kwargs): varargin = atm_P.varargin nargin = atm_P.nargin # Written by Jenni Vanos, Aug 2009 for use in COMFA CNR to find Rabs. - - Po=101.3 -# atm_P.m:6 - - Pa=multiply(Po,exp(- A / 8200)) + + Po = 101.3 + # atm_P.m:6 + + Pa = multiply(Po, exp(-A / 8200)) + + # atm_P.m:9 - - #another equation that gives similar results found from website below: - - #Pa=Po.*(1-.0000225577.*A).^5.25588; - - # A = altitude, units don't matter because it cancels with Ao (which is 0 at surface). -#Therefore, it will be exp^-1, and Po is surface pressure, which is 1bar, or 100kPa. - - # equation from: http://www.engineeringtoolbox.com/air-altitude-pressure-d_462.html \ No newline at end of file + +# another equation that gives similar results found from website below: + +# Pa=Po.*(1-.0000225577.*A).^5.25588; + +# A = altitude, units don't matter because it cancels with Ao (which is 0 at surface). +# Therefore, it will be exp^-1, and Po is surface pressure, which is 1bar, or 100kPa. + +# equation from: http://www.engineeringtoolbox.com/air-altitude-pressure-d_462.html diff --git a/functions/SOLWEIGpython/COMFA/opt_m.py b/functions/SOLWEIGpython/COMFA/opt_m.py index 512a1c1..5dcdeee 100644 --- a/functions/SOLWEIGpython/COMFA/opt_m.py +++ b/functions/SOLWEIGpython/COMFA/opt_m.py @@ -1,20 +1,23 @@ # Generated with SMOP 0.41 from libsmop import * + # opt_m.m - + @function -def opt_m(A=None,lat=None,d=None,t=None,*args,**kwargs): +def opt_m(A=None, lat=None, d=None, t=None, *args, **kwargs): varargin = opt_m.varargin nargin = opt_m.nargin - #optical air mass number used for input into the equations which estimate -#incoming shortwave radiation under clear sky conditions - - Pa=atm_P(A) -# opt_m.m:6 - zen=solar_zenith(lat,d,t) -# opt_m.m:8 - m=Pa / (multiply(101.3,cosd(zen))) + # optical air mass number used for input into the equations which estimate + # incoming shortwave radiation under clear sky conditions + + Pa = atm_P(A) + # opt_m.m:6 + zen = solar_zenith(lat, d, t) + # opt_m.m:8 + m = Pa / (multiply(101.3, cosd(zen))) + + # opt_m.m:10 - # written January/07 by Natasha Kenny \ No newline at end of file +# written January/07 by Natasha Kenny diff --git a/functions/SOLWEIGpython/COMFA/radiationfunctionsCOMFA.py b/functions/SOLWEIGpython/COMFA/radiationfunctionsCOMFA.py index fc8cc26..d2e2b98 100644 --- a/functions/SOLWEIGpython/COMFA/radiationfunctionsCOMFA.py +++ b/functions/SOLWEIGpython/COMFA/radiationfunctionsCOMFA.py @@ -2,110 +2,145 @@ """ This code is a translation of radiation related function in the COMFA model -Translated on Fri Oct 23 +Translated on Fri Oct 23 @author: xlinfr """ import numpy as np -from ....util.SEBESOLWEIGCommonFiles import Solweig_v2015_metdata_noload as metload +from ....util.SEBESOLWEIGCommonFiles import ( + Solweig_v2015_metdata_noload as metload, +) from ....util.SEBESOLWEIGCommonFiles.diffusefraction import diffusefraction -def CNRRabs_Total(alpha,L,D,Lin,Lup,Kin,Kup,Atr,d,t,lat,A,emis,Aeff): - # VERTICAL CYLINDER MODEL +def CNRRabs_Total( + alpha, L, D, Lin, Lup, Kin, Kup, Atr, d, t, lat, A, emis, Aeff +): + # VERTICAL CYLINDER MODEL # Used to calculate the total radiation absorbed by a cylinder, such as the CRT, with inputs # alpha (albedo of cylinder), Kin (incoming shortwave measured by CNR net # radiometer, W/m2), Kup (reflected shortwave measured by CNR, W/m2), Lin # (CNR incoming longwave radiation, W/m2), Lup (CNR1 outgoing longwave # radition, W/m2), L (length of cylinder, m), D (diamter of cylinder, m). - # - #Also need solar zenith, which is calculated from: lat (latitude, degrees), d (days, Jan 1=1), t (decimal + # + # Also need solar zenith, which is calculated from: lat (latitude, degrees), d (days, Jan 1=1), t (decimal # time, 24h decimal time). - - #Atr - atmospheric transmittance - can calculate for the day/time of day or - #estimate based on sky conditions. - - # LOAD data .... - - #set constants - # D=0.01 + + # Atr - atmospheric transmittance - can calculate for the day/time of day or + # estimate based on sky conditions. + + # LOAD data .... + + # set constants + # D=0.01 # CNRRabs_Total.m:20 - - # L=0.1 + + # L=0.1 # CNRRabs_Total.m:21 - - # alpha=0.37 + + # alpha=0.37 # CNRRabs_Total.m:22 - - # E.g., + + # E.g., # Kin = metdata(:,10); #put in column for Kin from metdata file - # Kup = metdata(:,11); + # Kup = metdata(:,11); # Lin = metdata(:,20); - # Lup = metdata(:,21); + # Lup = metdata(:,21); # d = metdata(:,24); - # Atr = 0.70; + # Atr = 0.70; # lat = 33.75; # A = 992; - + # Aeff=0.78 # Effective area of body. 0.78 for standing from Campebll and Normal (1998) (0.70 for sitting) Moved to main function Kin_abs, zen, Kd = CNR_Kinabs_meas(alpha, Kin, L, D, A, lat, d, t, Atr) Kup_abs = CNR_Kup(alpha, Kup, L, D) Lin_abs = LinMeas_abs(L, D, Lin, emis) Lup_abs = LupMeas_abs(L, D, Lup, emis) - Acyl = CRT_Acyl(L,D) - Total_CNRRabs = np.dot(Aeff,((Kin_abs + Kup_abs + Lin_abs + Lup_abs) / (Acyl))) + Acyl = CRT_Acyl(L, D) + Total_CNRRabs = np.dot( + Aeff, ((Kin_abs + Kup_abs + Lin_abs + Lup_abs) / (Acyl)) + ) return Total_CNRRabs, zen, Kd -def COMFA_RAD_SPATIAL_TC(L, D, Lin, Lup, Kin, Kup, emis, alpha, Aeff, Kd, metdata, location, utc): + +def COMFA_RAD_SPATIAL_TC( + L, D, Lin, Lup, Kin, Kup, emis, alpha, Aeff, Kd, metdata, location, utc +): """ Same as CNRRabs_Total but using a SPN1 sensor to obtain Kd or Same as CNRRabs_Total but using reindl et al for Kd and sun_position.py """ - deg2rad = np.pi/180. + deg2rad = np.pi / 180.0 # location = {'longitude': lon, 'latitude': lat, 'altitude': alt} - YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax = metload.Solweig_2015a_metdata_noload(metdata, location, utc) + YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax = ( + metload.Solweig_2015a_metdata_noload(metdata, location, utc) + ) Kb = Kin - Kd # Not perpendicular but horrisontal surface in COMFA Kb = (Kin - Kd)/(np.sin(altitude*deg2rad)) - Acs = CRT_Acs(L,D) + Acs = CRT_Acs(L, D) if altitude > 0: # Kdirect perpendicular to the beam - Kp = Kb/np.cos(zen) + Kp = Kb / np.cos(zen) Kb_abs = (1 - alpha) * (Kp * np.sin(zen)) * Acs else: Kb_abs = 0 Kd = 0 # Kb_abs = np.dot(np.dot((1 - alpha),(np.dot(Kb,np.sin(zen)))),Acs) - Acyl = CRT_Acyl(L,D) + Acyl = CRT_Acyl(L, D) # Kd_abs = np.dot(np.dot(np.dot((1 - alpha),Kd),0.5),Acyl) Kd_abs = (1 - alpha) * Kd * 0.5 * Acyl - Kin_abs = (Kb_abs + Kd_abs) + Kin_abs = Kb_abs + Kd_abs # same as original Kup_abs = CNR_Kup(alpha, Kup, L, D) Lin_abs = LinMeas_abs(L, D, Lin, emis) Lup_abs = LupMeas_abs(L, D, Lup, emis) - Acyl = CRT_Acyl(L,D) + Acyl = CRT_Acyl(L, D) # Total_CNRRabs_solweig = np.dot(Aeff,((Kin_abs + Kup_abs + Lin_abs + Lup_abs) / (Acyl))) - Total_CNRRabs_solweig = Aeff * ((Kin_abs + Kup_abs + Lin_abs + Lup_abs) / Acyl) + Total_CNRRabs_solweig = Aeff * ( + (Kin_abs + Kup_abs + Lin_abs + Lup_abs) / Acyl + ) return Total_CNRRabs_solweig -def Rad_Total_solweig(L,D,Lin,Lup,Kin,Kup,yyyy,hour,minu,doy,lat,lon,alt,emis,alpha,Aeff,utc, Kd=None, Ta=None, RH=None): + +def Rad_Total_solweig( + L, + D, + Lin, + Lup, + Kin, + Kup, + yyyy, + hour, + minu, + doy, + lat, + lon, + alt, + emis, + alpha, + Aeff, + utc, + Kd=None, + Ta=None, + RH=None, +): """ Same as CNRRabs_Total but using a SPN1 sensor to obtain Kd or Same as CNRRabs_Total but using reindl et al for Kd and sun_position.py """ - deg2rad = np.pi/180. - metdata = np.zeros((1, 24)) - 999. #TODO: Move out of function + deg2rad = np.pi / 180.0 + metdata = np.zeros((1, 24)) - 999.0 # TODO: Move out of function # doy = day_of_year(yyyy, month, day) metdata[0, 0] = yyyy metdata[0, 1] = doy @@ -116,10 +151,12 @@ def Rad_Total_solweig(L,D,Lin,Lup,Kin,Kup,yyyy,hour,minu,doy,lat,lon,alt,emis,al # metdata[0, 14] = Kin # metdata[0, 21] = Kd - location = {'longitude': lon, 'latitude': lat, 'altitude': alt} - YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax = metload.Solweig_2015a_metdata_noload(metdata, location, utc) + location = {"longitude": lon, "latitude": lat, "altitude": alt} + YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax = ( + metload.Solweig_2015a_metdata_noload(metdata, location, utc) + ) - if Kd == None: # Kd and Kb is estimated with Reindl et al. 1990 + if Kd == None: # Kd and Kb is estimated with Reindl et al. 1990 Itoa = 1370.0 # Effective solar constant D_ = sun_distance(doy) I0et = Itoa * np.cos(zen) * D_ # extra terrestial solar radiation @@ -130,76 +167,82 @@ def Rad_Total_solweig(L,D,Lin,Lup,Kin,Kup,yyyy,hour,minu,doy,lat,lon,alt,emis,al Kb = Kin - Kd # Not perpendicular but horrisontal surface in COMFA Kb = (Kin - Kd)/(np.sin(altitude*deg2rad)) - Acs = CRT_Acs(L,D) + Acs = CRT_Acs(L, D) if altitude > 0: # Kdirect perpendicular to the beam - Kp = Kb/np.cos(zen) + Kp = Kb / np.cos(zen) Kb_abs = (1 - alpha) * (Kp * np.sin(zen)) * Acs else: Kb_abs = 0 Kd = 0 # Kb_abs = np.dot(np.dot((1 - alpha),(np.dot(Kb,np.sin(zen)))),Acs) - Acyl = CRT_Acyl(L,D) + Acyl = CRT_Acyl(L, D) # Kd_abs = np.dot(np.dot(np.dot((1 - alpha),Kd),0.5),Acyl) Kd_abs = (1 - alpha) * Kd * 0.5 * Acyl - Kin_abs = (Kb_abs + Kd_abs) + Kin_abs = Kb_abs + Kd_abs # same as original Kup_abs = CNR_Kup(alpha, Kup, L, D) Lin_abs = LinMeas_abs(L, D, Lin, emis) Lup_abs = LupMeas_abs(L, D, Lup, emis) - Acyl = CRT_Acyl(L,D) + Acyl = CRT_Acyl(L, D) # Total_CNRRabs_solweig = np.dot(Aeff,((Kin_abs + Kup_abs + Lin_abs + Lup_abs) / (Acyl))) - Total_CNRRabs_solweig = Aeff * ((Kin_abs + Kup_abs + Lin_abs + Lup_abs) / Acyl) - - return Total_CNRRabs_solweig, zen/(np.pi/180), Kd - - -def CNR_Kinabs_meas(alpha,Kin,L,D,A,lat,d,t,Atr): - #VERTICAL CYLINDER MODEL USING THE PERPENDICULAR DIRECT IRRADIANCE AND - #DIFFUSE BEAMS SEPARATELY - #Used to determine the total incoming solar radiation (K) absorbed by the - #human using the measured CNR1 net radiometer readings (W/m2), with inputs alpha (albedo of the cylinder), Kin (incoming solar - #radiation measured by the net radiometer), L (length of the cylinder, cm), - #D (diameter of the cylinder, cm), A is altitude in m, lat (latitude, - #degrees), d (day, Jan 1 =1), and t (time, in decimal 24 hours) - - deg2rad = np.pi/180. - Kb=Ratio_Kb(Kin,A,lat,d,t,Atr) - Kd= Kin - Kb - zen=solar_zenith(lat,d,t) - Acs=CRT_Acs(L,D) - Kb_abs=np.dot(np.dot((1 - alpha),(np.dot(Kb,np.sin(zen*deg2rad)))),Acs) - Acyl=CRT_Acyl(L,D) - Kd_abs=np.dot(np.dot(np.dot((1 - alpha),Kd),0.5),Acyl) - Kin_abs=(Kb_abs + Kd_abs) + Total_CNRRabs_solweig = Aeff * ( + (Kin_abs + Kup_abs + Lin_abs + Lup_abs) / Acyl + ) + + return Total_CNRRabs_solweig, zen / (np.pi / 180), Kd + + +def CNR_Kinabs_meas(alpha, Kin, L, D, A, lat, d, t, Atr): + # VERTICAL CYLINDER MODEL USING THE PERPENDICULAR DIRECT IRRADIANCE AND + # DIFFUSE BEAMS SEPARATELY + # Used to determine the total incoming solar radiation (K) absorbed by the + # human using the measured CNR1 net radiometer readings (W/m2), with inputs alpha (albedo of the cylinder), Kin (incoming solar + # radiation measured by the net radiometer), L (length of the cylinder, cm), + # D (diameter of the cylinder, cm), A is altitude in m, lat (latitude, + # degrees), d (day, Jan 1 =1), and t (time, in decimal 24 hours) + + deg2rad = np.pi / 180.0 + Kb = Ratio_Kb(Kin, A, lat, d, t, Atr) + Kd = Kin - Kb + zen = solar_zenith(lat, d, t) + Acs = CRT_Acs(L, D) + Kb_abs = np.dot( + np.dot((1 - alpha), (np.dot(Kb, np.sin(zen * deg2rad)))), Acs + ) + Acyl = CRT_Acyl(L, D) + Kd_abs = np.dot(np.dot(np.dot((1 - alpha), Kd), 0.5), Acyl) + Kin_abs = Kb_abs + Kd_abs return Kin_abs, zen, Kd - + # Written August, 2006 by Natasha Kenny # Revised January, 2007 to break up the diffuse and beam radiation # updated Jan 2016 Aaron Hardin & Jenni Vanos with explanations from Terry # Gillespie for ratio model -def Ratio_Kb(Kin,A,lat,d,t,Atr): + +def Ratio_Kb(Kin, A, lat, d, t, Atr): # function is used to estimate incoming shortwave diffuse radiation under # clear sky conditions with inputs A(alititude, m), lat(latitude, degrees), # d (days, Jan 1 =1), t (time, 24 hours) - - m=opt_m(A,lat,d,t) - Kb=Kin / ((1 + (np.dot(0.3,(1 - Atr ** m)))) / (Atr ** m)) + + m = opt_m(A, lat, d, t) + Kb = Kin / ((1 + (np.dot(0.3, (1 - Atr**m)))) / (Atr**m)) return Kb -def opt_m(A,lat,d,t): - #optical air mass number used for input into the equations which estimate - #incoming shortwave radiation under clear sky conditions - deg2rad = np.pi/180. - Pa=atm_P(A) - zen=solar_zenith(lat,d,t) - m=Pa / (np.dot(101.3,np.cos(zen*deg2rad))) + +def opt_m(A, lat, d, t): + # optical air mass number used for input into the equations which estimate + # incoming shortwave radiation under clear sky conditions + deg2rad = np.pi / 180.0 + Pa = atm_P(A) + zen = solar_zenith(lat, d, t) + m = Pa / (np.dot(101.3, np.cos(zen * deg2rad))) # written January/07 by Natasha Kenny @@ -209,38 +252,45 @@ def opt_m(A,lat,d,t): def atm_P(A): # Written by Jenni Vanos, Aug 2009 for use in COMFA CNR to find Rabs. - - Po=101.3 #TODO move out? Pressure? - Pa=np.dot(Po,np.exp(- A / 8200)) - + + Po = 101.3 # TODO move out? Pressure? + Pa = np.dot(Po, np.exp(-A / 8200)) + return Pa -def solar_zenith(lat,d,t): - #function to calculate the cosine of the zenith angle based on the time of day, the - #latitude of the site and the time of year. t is in hours ranging from 0 to - #24. d is the calender day with J = 1 at January 1. LC is the longitude - #correction (+7.5 to -7.5 either side of standard meridian). - - #Solar declination ranges from +23.45 at summer soltice to -23.45 at winter - #s olstice and is calculated as: - deg2rad = np.pi/180. - #make sure to change latitudinal correction for whatever city you are in. - - LC=-49/60. #TODO Remove since is should vary with location. What us this? - - ET=solar_ET(d) - to=12 - LC - ET - dec=solar_dec(d) - - cos_z=(np.dot(np.sin(lat*deg2rad),np.sin(dec*deg2rad))) + (np.dot(np.dot(np.cos(lat*deg2rad),np.cos(dec*deg2rad)),np.cos(np.dot(15.0,(t - to))*deg2rad))) - zen=np.arccos(cos_z)*(180/np.pi) - zen[zen>90]=90. +def solar_zenith(lat, d, t): + # function to calculate the cosine of the zenith angle based on the time of day, the + # latitude of the site and the time of year. t is in hours ranging from 0 to + # 24. d is the calender day with J = 1 at January 1. LC is the longitude + # correction (+7.5 to -7.5 either side of standard meridian). + + # Solar declination ranges from +23.45 at summer soltice to -23.45 at winter + # s olstice and is calculated as: + deg2rad = np.pi / 180.0 + # make sure to change latitudinal correction for whatever city you are in. + + LC = ( + -49 / 60.0 + ) # TODO Remove since is should vary with location. What us this? + + ET = solar_ET(d) + to = 12 - LC - ET + dec = solar_dec(d) + + cos_z = (np.dot(np.sin(lat * deg2rad), np.sin(dec * deg2rad))) + ( + np.dot( + np.dot(np.cos(lat * deg2rad), np.cos(dec * deg2rad)), + np.cos(np.dot(15.0, (t - to)) * deg2rad), + ) + ) + zen = np.arccos(cos_z) * (180 / np.pi) + zen[zen > 90] = 90.0 # bad=np.where(zen > 90) # zen[bad]=90 - #Written January 2007 by Natasha Kenny + # Written January 2007 by Natasha Kenny return zen @@ -248,9 +298,17 @@ def solar_zenith(lat,d,t): def solar_ET(d): # used to calculate the equation of time for use in calcuating the zenith # angle of the sun. - deg2rad = np.pi/180. - f=279.575 + np.dot(0.9856,d) - ET=(np.dot(- 104.7,np.sin(f*deg2rad)) + np.dot(596.2,np.sin(np.dot(2.0,f)*deg2rad)) + np.dot(4.3,np.sin(np.dot(3.0,f)*deg2rad)) - np.dot(12.7,np.sin(np.dot(4.0,f)*deg2rad)) - np.dot(429.3,np.cos(f*deg2rad)) - np.dot(2.0,np.cos(np.dot(2.0,f)*deg2rad)) + np.dot(19.3,np.cos(np.dot(3.0,f)*deg2rad))) / 3600. + deg2rad = np.pi / 180.0 + f = 279.575 + np.dot(0.9856, d) + ET = ( + np.dot(-104.7, np.sin(f * deg2rad)) + + np.dot(596.2, np.sin(np.dot(2.0, f) * deg2rad)) + + np.dot(4.3, np.sin(np.dot(3.0, f) * deg2rad)) + - np.dot(12.7, np.sin(np.dot(4.0, f) * deg2rad)) + - np.dot(429.3, np.cos(f * deg2rad)) + - np.dot(2.0, np.cos(np.dot(2.0, f) * deg2rad)) + + np.dot(19.3, np.cos(np.dot(3.0, f) * deg2rad)) + ) / 3600.0 return ET @@ -259,100 +317,104 @@ def solar_dec(d): # written to calcuate the solar declination with ranges from +23.45 at # summer solstice to -23.45 at the winter solstice. d is the calendar day # with d = 1 being January 1st. - - #sin_dec = 0.39785 .* sind(278.97 + 0.9856 .* d + 1.9165 .* sind(356.6 + - #0.9856 .* d)); from Campbell and norman pg. 168 - - #dec = asind(sin_dec); - deg2rad = np.pi/180. - dec=np.dot(- 23.4,np.cos((np.dot(360,(d + 10)) / 365)*deg2rad)) + + # sin_dec = 0.39785 .* sind(278.97 + 0.9856 .* d + 1.9165 .* sind(356.6 + + # 0.9856 .* d)); from Campbell and norman pg. 168 + + # dec = asind(sin_dec); + deg2rad = np.pi / 180.0 + dec = np.dot(-23.4, np.cos((np.dot(360, (d + 10)) / 365) * deg2rad)) return dec -def CRT_Acs(L,D): +def CRT_Acs(L, D): # vertical cross sectional area of the cylinder in m - + # Written by Jenni Vanos, Aug 2009, to find the XC area of a cylinder, # where L = length (m) and D = diameter (m)- #for CRT L = ~0.10m and D = # 0.01m - ratio is most important part to keep constant for changing - # cylidner size. + # cylidner size. + + # Used in Kb_abs = (1-alpha).* (Kb .* sind(zen)).* Acs; - #Used in Kb_abs = (1-alpha).* (Kb .* sind(zen)).* Acs; - - Acs=np.dot(D,L) + Acs = np.dot(D, L) return Acs -def CNR_Kup(alpha,Kup,L,D): +def CNR_Kup(alpha, Kup, L, D): # Used to calculate the total reflected solar radiation (K) absorbed by the # a cylinder with inputs alpha (cylinder albedo), Kup (measured reflected solar # radiation by the CNR net radiometer (W/m2), Acyl (area of the cylinder, m2) - - Acyl=CRT_Acyl(L,D) - Kup_abs=np.dot(np.dot(np.dot((1 - alpha),Kup),0.5),Acyl) + + Acyl = CRT_Acyl(L, D) + Kup_abs = np.dot(np.dot(np.dot((1 - alpha), Kup), 0.5), Acyl) return Kup_abs -def CRT_Acyl(L,D): - #Function written August 2009 by Jenni Vanos for calculating the Area of - #the CRT for use in the CNR Comfa model for finidng Rabs. - - #L = length (m) - #for CRT this is ~0.10m - #D = diamater (m) - #for CRT this is ~0.01m - +def CRT_Acyl(L, D): + # Function written August 2009 by Jenni Vanos for calculating the Area of + # the CRT for use in the CNR Comfa model for finidng Rabs. + + # L = length (m) - #for CRT this is ~0.10m + # D = diamater (m) - #for CRT this is ~0.01m + # pi=3.14 - Acyl=np.dot(np.dot(2,np.pi),(D / 2) ** 2) + np.dot(np.dot(np.dot(2.0,np.pi),(D / 2)),L) + Acyl = np.dot(np.dot(2, np.pi), (D / 2) ** 2) + np.dot( + np.dot(np.dot(2.0, np.pi), (D / 2)), L + ) return Acyl -def LinMeas_abs(L,D,Lin,e): - #Used to Calculate the incoming longwave radiation absorbed by the cylinder with - #inputs epsilon (emissivity of the cylinder, 0.95), Ta (air temp, degreesC) - #L (length of cylinder, cm), D (diameter of cylinder, m) - - epsilon= e #0.95 moved to main function - Acyl=CRT_Acyl(L,D) - LinMeas_abs=np.dot(np.dot(np.dot(epsilon,Lin),0.5),Acyl) +def LinMeas_abs(L, D, Lin, e): + # Used to Calculate the incoming longwave radiation absorbed by the cylinder with + # inputs epsilon (emissivity of the cylinder, 0.95), Ta (air temp, degreesC) + # L (length of cylinder, cm), D (diameter of cylinder, m) + + epsilon = e # 0.95 moved to main function + Acyl = CRT_Acyl(L, D) + LinMeas_abs = np.dot(np.dot(np.dot(epsilon, Lin), 0.5), Acyl) return LinMeas_abs def LupMeas_abs(L, D, Lup, e): - #Used to calculate the estimated outgoing terrestrial longwave absorbed by the - #cylinder for use in the Rabs (Estimation) Model with inputs Ta (air temperature), - #L (length of cylinder, m), D (diameter of cylinder, cm) - - Acyl = CRT_Acyl(L,D) - epsilon = e # 0.95 #mover to main function - LupMeas_abs = np.dot(np.dot(np.dot(epsilon,Lup),0.5),Acyl) + # Used to calculate the estimated outgoing terrestrial longwave absorbed by the + # cylinder for use in the Rabs (Estimation) Model with inputs Ta (air temperature), + # L (length of cylinder, m), D (diameter of cylinder, cm) + + Acyl = CRT_Acyl(L, D) + epsilon = e # 0.95 #mover to main function + LupMeas_abs = np.dot(np.dot(np.dot(epsilon, Lup), 0.5), Acyl) return LupMeas_abs + def day_of_year(yyyy, month, day): - if (yyyy % 4) == 0: - if (yyyy % 100) == 0: - if (yyyy % 400) == 0: - leapyear = 1 - else: - leapyear = 0 - else: + if (yyyy % 4) == 0: + if (yyyy % 100) == 0: + if (yyyy % 400) == 0: leapyear = 1 + else: + leapyear = 0 else: - leapyear = 0 + leapyear = 1 + else: + leapyear = 0 - if leapyear == 1: - dayspermonth = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] - else: - dayspermonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + if leapyear == 1: + dayspermonth = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + else: + dayspermonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + + doy = np.sum(dayspermonth[0 : month - 1]) + day - doy = np.sum(dayspermonth[0:month - 1]) + day + return doy - return doy def sun_distance(jday): """ @@ -360,7 +422,14 @@ def sun_distance(jday): #% with day of year as input. #% Partridge and Platt, 1975 """ - b = 2.*np.pi*jday/365. - D = np.sqrt((1.00011+np.dot(0.034221, np.cos(b))+np.dot(0.001280, np.sin(b))+np.dot(0.000719, - np.cos((2.*b)))+np.dot(0.000077, np.sin((2.*b))))) - return D \ No newline at end of file + b = 2.0 * np.pi * jday / 365.0 + D = np.sqrt( + ( + 1.00011 + + np.dot(0.034221, np.cos(b)) + + np.dot(0.001280, np.sin(b)) + + np.dot(0.000719, np.cos((2.0 * b))) + + np.dot(0.000077, np.sin((2.0 * b))) + ) + ) + return D diff --git a/functions/SOLWEIGpython/COMFA/solar_ET.py b/functions/SOLWEIGpython/COMFA/solar_ET.py index 1a03d26..63432a3 100644 --- a/functions/SOLWEIGpython/COMFA/solar_ET.py +++ b/functions/SOLWEIGpython/COMFA/solar_ET.py @@ -1,17 +1,28 @@ # Generated with SMOP 0.41 from libsmop import * + # solar_ET.m - + @function -def solar_ET(d=None,*args,**kwargs): +def solar_ET(d=None, *args, **kwargs): varargin = solar_ET.varargin nargin = solar_ET.nargin # used to calculate the equation of time for use in calcuating the zenith -# angle of the sun. - - f=279.575 + multiply(0.9856,d) -# solar_ET.m:7 - ET=(multiply(- 104.7,sind(f)) + multiply(596.2,sind(dot(2.0,f))) + multiply(4.3,sind(dot(3.0,f))) - multiply(12.7,sind(dot(4.0,f))) - multiply(429.3,cosd(f)) - multiply(2.0,cosd(dot(2.0,f))) + multiply(19.3,cosd(dot(3.0,f)))) / 3600 -# solar_ET.m:9 \ No newline at end of file + # angle of the sun. + + f = 279.575 + multiply(0.9856, d) + # solar_ET.m:7 + ET = ( + multiply(-104.7, sind(f)) + + multiply(596.2, sind(dot(2.0, f))) + + multiply(4.3, sind(dot(3.0, f))) + - multiply(12.7, sind(dot(4.0, f))) + - multiply(429.3, cosd(f)) + - multiply(2.0, cosd(dot(2.0, f))) + + multiply(19.3, cosd(dot(3.0, f))) + ) / 3600 + + +# solar_ET.m:9 diff --git a/functions/SOLWEIGpython/COMFA/solar_dec.py b/functions/SOLWEIGpython/COMFA/solar_dec.py index e469f65..57f1a82 100644 --- a/functions/SOLWEIGpython/COMFA/solar_dec.py +++ b/functions/SOLWEIGpython/COMFA/solar_dec.py @@ -1,23 +1,26 @@ # Generated with SMOP 0.41 from libsmop import * + # solar_dec.m - + @function -def solar_dec(d=None,*args,**kwargs): +def solar_dec(d=None, *args, **kwargs): varargin = solar_dec.varargin nargin = solar_dec.nargin # written to calcuate the solar declination with ranges from +23.45 at -# summer solstice to -23.45 at the winter solstice. d is the calendar day -# with d = 1 being January 1st. - - #sin_dec = 0.39785 .* sind(278.97 + 0.9856 .* d + 1.9165 .* sind(356.6 + -#0.9856 .* d)); from Campbell and norman pg. 168 - - #dec = asind(sin_dec); - - dec=dot(- 23.4,cosd(dot(360,(d + 10)) / 365)) + # summer solstice to -23.45 at the winter solstice. d is the calendar day + # with d = 1 being January 1st. + + # sin_dec = 0.39785 .* sind(278.97 + 0.9856 .* d + 1.9165 .* sind(356.6 + + # 0.9856 .* d)); from Campbell and norman pg. 168 + + # dec = asind(sin_dec); + + dec = dot(-23.4, cosd(dot(360, (d + 10)) / 365)) + + # solar_dec.m:13 - - # written January, 2007 by Natasha Kenny \ No newline at end of file + +# written January, 2007 by Natasha Kenny diff --git a/functions/SOLWEIGpython/COMFA/solar_zenith.py b/functions/SOLWEIGpython/COMFA/solar_zenith.py index 6c673fe..990cae7 100644 --- a/functions/SOLWEIGpython/COMFA/solar_zenith.py +++ b/functions/SOLWEIGpython/COMFA/solar_zenith.py @@ -1,40 +1,43 @@ # Generated with SMOP 0.41 from libsmop import * + # solar_zenith.m - + @function -def solar_zenith(lat=None,d=None,t=None,*args,**kwargs): +def solar_zenith(lat=None, d=None, t=None, *args, **kwargs): varargin = solar_zenith.varargin nargin = solar_zenith.nargin - #function to calculate the cosine of the zenith angle based on the time of day, the -#latitude of the site and the time of year. t is in hours ranging from 0 to -#24. d is the calender day with J = 1 at January 1. LC is the longitude -#correction (+7.5 to -7.5 either side of standard meridian). - - #Solar declination ranges from +23.45 at summer soltice to -23.45 at winter -#solstice and is calculated as: - - #make sure to change latitudinal correction for whatever city you are in. - LC=- 49 / 60 -# solar_zenith.m:12 - - - ET=solar_ET(d) -# solar_zenith.m:14 - to=12 - LC - ET -# solar_zenith.m:16 - dec=solar_dec(d) -# solar_zenith.m:18 - - cos_z=(multiply(sind(lat),sind(dec))) + (multiply(multiply(cosd(lat),cosd(dec)),cosd(dot(15.0,(t - to))))) -# solar_zenith.m:20 - zen=acosd(cos_z) -# solar_zenith.m:22 - bad=find(zen > 90) -# solar_zenith.m:24 - zen[bad]=90 + # function to calculate the cosine of the zenith angle based on the time of day, the + # latitude of the site and the time of year. t is in hours ranging from 0 to + # 24. d is the calender day with J = 1 at January 1. LC is the longitude + # correction (+7.5 to -7.5 either side of standard meridian). + + # Solar declination ranges from +23.45 at summer soltice to -23.45 at winter + # solstice and is calculated as: + + # make sure to change latitudinal correction for whatever city you are in. + LC = -49 / 60 + # solar_zenith.m:12 + + ET = solar_ET(d) + # solar_zenith.m:14 + to = 12 - LC - ET + # solar_zenith.m:16 + dec = solar_dec(d) + # solar_zenith.m:18 + + cos_z = (multiply(sind(lat), sind(dec))) + ( + multiply(multiply(cosd(lat), cosd(dec)), cosd(dot(15.0, (t - to)))) + ) + # solar_zenith.m:20 + zen = acosd(cos_z) + # solar_zenith.m:22 + bad = find(zen > 90) + # solar_zenith.m:24 + zen[bad] = 90 + + # solar_zenith.m:25 - #Written January 2007 by Natasha Kenny - \ No newline at end of file +# Written January 2007 by Natasha Kenny diff --git a/functions/SOLWEIGpython/CirclePlotBar.py b/functions/SOLWEIGpython/CirclePlotBar.py index 261d675..4f17f30 100644 --- a/functions/SOLWEIGpython/CirclePlotBar.py +++ b/functions/SOLWEIGpython/CirclePlotBar.py @@ -6,23 +6,35 @@ import matplotlib.colors as colors import matplotlib -#def PolarBarPlot(lv, radD, outfolder, YYYY, DOY, XH, hours, XM, minu, iStep, idxStep): -def PolarBarPlot(lv, solar_altitude, solar_azimuth, fig_title, filename_out, minrad, maxrad, plot_type): -#def PolarBarPlot(lv, filename_start, outfolder, YYYY, DOY, XH, hours, XM, minu, iStep, minrad, maxrad): - deg2rad = np.pi/180 +# def PolarBarPlot(lv, radD, outfolder, YYYY, DOY, XH, hours, XM, minu, iStep, idxStep): +def PolarBarPlot( + lv, + solar_altitude, + solar_azimuth, + fig_title, + filename_out, + minrad, + maxrad, + plot_type, +): + # def PolarBarPlot(lv, filename_start, outfolder, YYYY, DOY, XH, hours, XM, minu, iStep, minrad, maxrad): + + deg2rad = np.pi / 180 fig = figure() - ax = fig.add_subplot(111, projection = 'polar') + ax = fig.add_subplot(111, projection="polar") # Set zero location and clockwise ax.set_theta_zero_location("N") - if plot_type: - ax.set_theta_direction('clockwise') + if plot_type: + ax.set_theta_direction("clockwise") else: - ax.set_theta_direction('anticlockwise') + ax.set_theta_direction("anticlockwise") # ax.set_theta_direction('clockwise') - skyalt, skyalt_c = np.unique(lv[:, 0], return_counts=True) # Unique altitudes in lv, i.e. unique altitude for the patches + skyalt, skyalt_c = np.unique( + lv[:, 0], return_counts=True + ) # Unique altitudes in lv, i.e. unique altitude for the patches # lvSum = np.sum(lv[:,2]) @@ -33,25 +45,41 @@ def PolarBarPlot(lv, solar_altitude, solar_azimuth, fig_title, filename_out, min # lvMax = np.around(np.max(lv_norm), decimals=3) if plot_type: - jet = cm = plt.get_cmap('jet') - cNorm = colors.Normalize(vmin=minrad, vmax=maxrad) # Watts per square meter Steradian + jet = cm = plt.get_cmap("jet") + cNorm = colors.Normalize( + vmin=minrad, vmax=maxrad + ) # Watts per square meter Steradian scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=jet) else: # patch_category = {1.8:'deepskyblue', 2.5:'forestgreen', 3.3:'yellow', 4.5:'peru', 6.0:'grey'} - patch_category = {1.8:'deepskyblue', 2.5:'forestgreen', 3.3:'yellow', 4.5:'peru'} + patch_category = { + 1.8: "deepskyblue", + 2.5: "forestgreen", + 3.3: "yellow", + 4.5: "peru", + } if lv.shape[0] < 160: - azistart = np.array([0, 4, 2, 5, 8, 0, 10, 0]) # Fredrik/Nils + azistart = np.array([0, 4, 2, 5, 8, 0, 10, 0]) # Fredrik/Nils else: - azistart = np.array([0, 0, 4, 4, 2, 2, 5, 5, 8, 8, 0, 0, 10, 10, 0]) # Nils test - radii = np.ones(np.max(skyalt_c))*1.5 + azistart = np.array( + [0, 0, 4, 4, 2, 2, 5, 5, 8, 8, 0, 0, 10, 10, 0] + ) # Nils test + radii = np.ones(np.max(skyalt_c)) * 1.5 radii_sub = 0 for i in range(skyalt_c.__len__()): clrs = lv_norm[lv_alt == skyalt[i]] # print(clrs) if skyalt_c[i] > 1: - theta_patch = ( np.arange(0, skyalt_c[skyalt == skyalt[i]][0], 1) * (360 / skyalt_c[skyalt == skyalt[i]][0] )) * deg2rad - width_patch = ( np.ones( skyalt_c[skyalt == skyalt[i]][0] ) * ( 360 * deg2rad ) / skyalt_c[skyalt == skyalt[i]][0] ) + theta_patch = ( + np.arange(0, skyalt_c[skyalt == skyalt[i]][0], 1) + * (360 / skyalt_c[skyalt == skyalt[i]][0]) + ) * deg2rad + width_patch = ( + np.ones(skyalt_c[skyalt == skyalt[i]][0]) + * (360 * deg2rad) + / skyalt_c[skyalt == skyalt[i]][0] + ) # if plot_type: # patch_order_range = range(skyalt_c[skyalt == skyalt[i]][0]) @@ -61,7 +89,7 @@ def PolarBarPlot(lv, solar_altitude, solar_azimuth, fig_title, filename_out, min patch_order_range = range(skyalt_c[skyalt == skyalt[i]][0]) for j in patch_order_range: - # for j in range(skyalt_c[skyalt == skyalt[i]][0]): + # for j in range(skyalt_c[skyalt == skyalt[i]][0]): if plot_type: patch_color = scalarMap.to_rgba(clrs[j]) # bars = ax.bar(theta_patch[j] + (azistart[i] * deg2rad), radii[j]-radii_sub, width=width_patch[j], bottom=0.0, edgecolor='black', facecolor=patch_color) @@ -69,14 +97,23 @@ def PolarBarPlot(lv, solar_altitude, solar_azimuth, fig_title, filename_out, min patch_color = patch_category[clrs[j]] # bars = ax.bar((np.pi*2 - theta_patch[j]) + (azistart[i] * deg2rad), radii[j]-radii_sub, width=width_patch[j], bottom=0.0, edgecolor='black', facecolor=patch_color) # bars = ax.bar(theta_patch[j] + (azistart[i] * deg2rad) - width_patch[j], radii[j]-radii_sub, width=width_patch[j], bottom=0.0, edgecolor='black', facecolor=patch_color) - bars = ax.bar(theta_patch[j] + (azistart[i] * deg2rad), radii[j]-radii_sub, width=width_patch[j], bottom=0.0, edgecolor='black', facecolor=patch_color) + bars = ax.bar( + theta_patch[j] + (azistart[i] * deg2rad), + radii[j] - radii_sub, + width=width_patch[j], + bottom=0.0, + edgecolor="black", + facecolor=patch_color, + ) if lv.shape[0] < 160: radii_sub += 0.2 else: - radii_sub += 0.1 # Nils test + radii_sub += 0.1 # Nils test else: # Create a circle for the center of the plot - my_circle=Circle( (0,0), 0.1, edgecolor='black', transform=ax.transData._b) + my_circle = Circle( + (0, 0), 0.1, edgecolor="black", transform=ax.transData._b + ) if plot_type: patch_color = scalarMap.to_rgba(clrs[0]) else: @@ -85,10 +122,27 @@ def PolarBarPlot(lv, solar_altitude, solar_azimuth, fig_title, filename_out, min ax.add_artist(my_circle) # Adding sun's position - if ((plot_type == 0) & (solar_altitude > 0)): - sun_position = plt.scatter((solar_azimuth * deg2rad), 1.5 - (1.5 * (solar_altitude/90)), s=(70 * (solar_altitude/90)), color='gold') - sun_position_star1 = plt.scatter((solar_azimuth * deg2rad), 1.5 - (1.5 * (solar_altitude/90)), s=(400 * (solar_altitude/90)), color='gold', marker='1') - sun_position_star2 = plt.scatter((solar_azimuth * deg2rad), 1.5 - (1.5 * (solar_altitude/90)), s=(400 * (solar_altitude/90)), color='gold', marker='2') + if (plot_type == 0) & (solar_altitude > 0): + sun_position = plt.scatter( + (solar_azimuth * deg2rad), + 1.5 - (1.5 * (solar_altitude / 90)), + s=(70 * (solar_altitude / 90)), + color="gold", + ) + sun_position_star1 = plt.scatter( + (solar_azimuth * deg2rad), + 1.5 - (1.5 * (solar_altitude / 90)), + s=(400 * (solar_altitude / 90)), + color="gold", + marker="1", + ) + sun_position_star2 = plt.scatter( + (solar_azimuth * deg2rad), + 1.5 - (1.5 * (solar_altitude / 90)), + s=(400 * (solar_altitude / 90)), + color="gold", + marker="2", + ) # sun_position = plt.scatter(np.pi * 2 - (solar_azimuth * deg2rad), 1.5 - (1.5 * (solar_altitude/90)), s=(70 * (solar_altitude/90)), color='gold') # sun_position_star1 = plt.scatter(np.pi * 2 - (solar_azimuth * deg2rad), 1.5 - (1.5 * (solar_altitude/90)), s=(400 * (solar_altitude/90)), color='gold', marker='1') # sun_position_star2 = plt.scatter(np.pi * 2 - (solar_azimuth * deg2rad), 1.5 - (1.5 * (solar_altitude/90)), s=(400 * (solar_altitude/90)), color='gold', marker='2') @@ -99,34 +153,50 @@ def PolarBarPlot(lv, solar_altitude, solar_azimuth, fig_title, filename_out, min # Remove grids and tick labels ax.set_yticklabels([]) ax.grid(False) - ax.spines['polar'].set_visible(False) + ax.spines["polar"].set_visible(False) if plot_type: # Adding colorbar - sm = plt.cm.ScalarMappable(cmap=plt.cm.jet, norm=plt.Normalize(vmin=minrad, vmax=maxrad)) # Watts per square meter Steradian + sm = plt.cm.ScalarMappable( + cmap=plt.cm.jet, norm=plt.Normalize(vmin=minrad, vmax=maxrad) + ) # Watts per square meter Steradian sm._A = [] cbaxes = fig.add_axes([0.87, 0.1, 0.03, 0.8]) - cb = plt.colorbar(sm, cax = cbaxes) - cb.ax.set_title(r'$W/m^2$ $sr^{-1}$', fontsize=12, fontweight='bold') # Watts per square meter Steradian + cb = plt.colorbar(sm, cax=cbaxes) + cb.ax.set_title( + r"$W/m^2$ $sr^{-1}$", fontsize=12, fontweight="bold" + ) # Watts per square meter Steradian else: # legend_colors = {'Shaded building wall':'peru', 'Sunlit building wall':'yellow', 'Sky':'deepskyblue', 'Trees':'forestgreen', 'Roof':'black'} # legend_colors = {'Building wall':'peru', 'Building roof':'grey', 'Trees':'forestgreen', 'Sky':'deepskyblue'} - legend_colors = {'Building wall':'peru', 'Trees':'forestgreen', 'Sky':'deepskyblue'} + legend_colors = { + "Building wall": "peru", + "Trees": "forestgreen", + "Sky": "deepskyblue", + } legend_labels = list(legend_colors.keys()) - legend_handles = [plt.Rectangle((0,0),1,1, color=legend_colors[legend_label]) for legend_label in legend_labels] + legend_handles = [ + plt.Rectangle((0, 0), 1, 1, color=legend_colors[legend_label]) + for legend_label in legend_labels + ] if solar_altitude > 0: - legend_handles.extend([(sun_position, sun_position_star1, sun_position_star2)]) - legend_labels.extend(['Sun']) - plt.legend(legend_handles, legend_labels, - title='Categories', - loc='lower left', - bbox_to_anchor=(0.95, 0, 0.5, 1), - fontsize=7, - frameon=False, - title_fontsize=7, - markerscale=0.7) - - ax.set_title(fig_title, weight='bold', fontsize=10) + legend_handles.extend( + [(sun_position, sun_position_star1, sun_position_star2)] + ) + legend_labels.extend(["Sun"]) + plt.legend( + legend_handles, + legend_labels, + title="Categories", + loc="lower left", + bbox_to_anchor=(0.95, 0, 0.5, 1), + fontsize=7, + frameon=False, + title_fontsize=7, + markerscale=0.7, + ) + + ax.set_title(fig_title, weight="bold", fontsize=10) fig.savefig(filename_out, dpi=300) - plt.close('all') \ No newline at end of file + plt.close("all") diff --git a/functions/SOLWEIGpython/Kside_veg_v2019a.py b/functions/SOLWEIGpython/Kside_veg_v2019a.py index 5e8bc1d..9a9eeec 100644 --- a/functions/SOLWEIGpython/Kside_veg_v2019a.py +++ b/functions/SOLWEIGpython/Kside_veg_v2019a.py @@ -2,53 +2,106 @@ import numpy as np from .Kvikt_veg import Kvikt_veg -def Kside_veg_v2019a(radI,radD,radG,shadow,svfS,svfW,svfN,svfE,svfEveg,svfSveg,svfWveg,svfNveg,azimuth,altitude,psi,t,albedo,F_sh,KupE,KupS,KupW,KupN,cyl,lv,ani,diffsh,rows,cols): + +def Kside_veg_v2019a( + radI, + radD, + radG, + shadow, + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + azimuth, + altitude, + psi, + t, + albedo, + F_sh, + KupE, + KupS, + KupW, + KupN, + cyl, + lv, + ani, + diffsh, + rows, + cols, +): # New reflection equation 2012-05-25 - vikttot=4.4897 - aziE=azimuth+t - aziS=azimuth-90+t - aziW=azimuth-180+t - aziN=azimuth-270+t - deg2rad=np.pi/180 + vikttot = 4.4897 + aziE = azimuth + t + aziS = azimuth - 90 + t + aziW = azimuth - 180 + t + aziN = azimuth - 270 + t + deg2rad = np.pi / 180 KsideD = np.zeros((rows, cols)) ### Direct radiation ### - if cyl == 1: ### Kside with cylinder ### - KsideI=shadow*radI*np.cos(altitude*deg2rad) - KeastI=0;KsouthI=0;KwestI=0;KnorthI=0 - else: ### Kside with weights ### - if azimuth > (360-t) or azimuth <= (180-t): - KeastI=radI*shadow*np.cos(altitude*deg2rad)*np.sin(aziE*deg2rad) + if cyl == 1: ### Kside with cylinder ### + KsideI = shadow * radI * np.cos(altitude * deg2rad) + KeastI = 0 + KsouthI = 0 + KwestI = 0 + KnorthI = 0 + else: ### Kside with weights ### + if azimuth > (360 - t) or azimuth <= (180 - t): + KeastI = ( + radI + * shadow + * np.cos(altitude * deg2rad) + * np.sin(aziE * deg2rad) + ) else: - KeastI=0 - if azimuth > (90-t) and azimuth <= (270-t): - KsouthI=radI*shadow*np.cos(altitude*deg2rad)*np.sin(aziS*deg2rad) + KeastI = 0 + if azimuth > (90 - t) and azimuth <= (270 - t): + KsouthI = ( + radI + * shadow + * np.cos(altitude * deg2rad) + * np.sin(aziS * deg2rad) + ) else: - KsouthI=0 - if azimuth > (180-t) and azimuth <= (360-t): - KwestI=radI*shadow*np.cos(altitude*deg2rad)*np.sin(aziW*deg2rad) + KsouthI = 0 + if azimuth > (180 - t) and azimuth <= (360 - t): + KwestI = ( + radI + * shadow + * np.cos(altitude * deg2rad) + * np.sin(aziW * deg2rad) + ) else: - KwestI=0 - if azimuth <= (90-t) or azimuth > (270-t): - KnorthI=radI*shadow*np.cos(altitude*deg2rad)*np.sin(aziN*deg2rad) + KwestI = 0 + if azimuth <= (90 - t) or azimuth > (270 - t): + KnorthI = ( + radI + * shadow + * np.cos(altitude * deg2rad) + * np.sin(aziN * deg2rad) + ) else: - KnorthI=0 + KnorthI = 0 + + KsideI = shadow * 0 - KsideI=shadow*0 - ### Diffuse and reflected radiation ### - [viktveg,viktwall]=Kvikt_veg(svfE,svfEveg,vikttot) - svfviktbuvegE=(viktwall+(viktveg)*(1-psi)) + [viktveg, viktwall] = Kvikt_veg(svfE, svfEveg, vikttot) + svfviktbuvegE = viktwall + (viktveg) * (1 - psi) - [viktveg,viktwall]=Kvikt_veg(svfS,svfSveg,vikttot) - svfviktbuvegS=(viktwall+(viktveg)*(1-psi)) + [viktveg, viktwall] = Kvikt_veg(svfS, svfSveg, vikttot) + svfviktbuvegS = viktwall + (viktveg) * (1 - psi) - [viktveg,viktwall]=Kvikt_veg(svfW,svfWveg,vikttot) - svfviktbuvegW=(viktwall+(viktveg)*(1-psi)) + [viktveg, viktwall] = Kvikt_veg(svfW, svfWveg, vikttot) + svfviktbuvegW = viktwall + (viktveg) * (1 - psi) - [viktveg,viktwall]=Kvikt_veg(svfN,svfNveg,vikttot) - svfviktbuvegN=(viktwall+(viktveg)*(1-psi)) + [viktveg, viktwall] = Kvikt_veg(svfN, svfNveg, vikttot) + svfviktbuvegN = viktwall + (viktveg) * (1 - psi) ### Anisotropic Diffuse Radiation after Perez et al. 1993 ### if ani == 1: @@ -61,7 +114,7 @@ def Kside_veg_v2019a(radI,radD,radG,shadow,svfS,svfW,svfN,svfE,svfEveg,svfSveg,s radTot = np.zeros(1) - for ix in range(0, 145): # Azimuth delta + for ix in range(0, 145): # Azimuth delta if ix < 60: aziDel = 12 elif ix >= 60 and ix < 108: @@ -75,76 +128,186 @@ def Kside_veg_v2019a(radI,radD,radG,shadow,svfS,svfW,svfN,svfE,svfEveg,svfSveg,s elif ix == 144: aziDel = 360 - phiVar[ix] = (aziDel * deg2rad) * (np.sin((aniAlt[ix] + 6) * deg2rad) - np.sin((aniAlt[ix] - 6) * deg2rad)) # Solid angle / Steradian + phiVar[ix] = (aziDel * deg2rad) * ( + np.sin((aniAlt[ix] + 6) * deg2rad) + - np.sin((aniAlt[ix] - 6) * deg2rad) + ) # Solid angle / Steradian - radTot = radTot + (aniLum[ix] * phiVar[ix] * np.sin(aniAlt[ix] * deg2rad)) # Radiance fraction normalization + radTot = radTot + ( + aniLum[ix] * phiVar[ix] * np.sin(aniAlt[ix] * deg2rad) + ) # Radiance fraction normalization - lumChi = (aniLum * radD) / radTot # Radiance fraction normalization + lumChi = (aniLum * radD) / radTot # Radiance fraction normalization if cyl == 1: for idx in range(0, 145): - anglIncC = np.cos(aniAlt[idx] * deg2rad) * np.cos(0) * np.sin(np.pi / 2) + np.sin( - aniAlt[idx] * deg2rad) * np.cos(np.pi / 2) # Angle of incidence, np.cos(0) because cylinder - always perpendicular - KsideD = KsideD + diffsh[:, :, idx] * lumChi[idx] * anglIncC * phiVar[idx] # Diffuse vertical radiation - Keast = (albedo * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) + KupE) * 0.5 - Ksouth = (albedo * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) + KupS) * 0.5 - Kwest = (albedo * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + KupW) * 0.5 - Knorth = (albedo * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + KupN) * 0.5 - else: # Box - diffRadE = np.zeros((rows, cols)); diffRadS = np.zeros((rows, cols)); diffRadW = np.zeros((rows, cols)); diffRadN = np.zeros((rows, cols)) + anglIncC = np.cos(aniAlt[idx] * deg2rad) * np.cos(0) * np.sin( + np.pi / 2 + ) + np.sin(aniAlt[idx] * deg2rad) * np.cos( + np.pi / 2 + ) # Angle of incidence, np.cos(0) because cylinder - always perpendicular + KsideD = ( + KsideD + + diffsh[:, :, idx] * lumChi[idx] * anglIncC * phiVar[idx] + ) # Diffuse vertical radiation + Keast = ( + albedo * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) + + KupE + ) * 0.5 + Ksouth = ( + albedo * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) + + KupS + ) * 0.5 + Kwest = ( + albedo * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + + KupW + ) * 0.5 + Knorth = ( + albedo * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + + KupN + ) * 0.5 + else: # Box + diffRadE = np.zeros((rows, cols)) + diffRadS = np.zeros((rows, cols)) + diffRadW = np.zeros((rows, cols)) + diffRadN = np.zeros((rows, cols)) for idx in range(0, 145): if aniAzi[idx] <= (180): - anglIncE = np.cos(aniAlt[idx] * deg2rad) * np.cos((90 - aniAzi[idx]) * deg2rad) * np.sin( - np.pi / 2) + np.sin( - aniAlt[idx] * deg2rad) * np.cos(np.pi / 2) - diffRadE = diffRadE + diffsh[:, :, idx] * lumChi[idx] * anglIncE * phiVar[idx] #* 0.5 + anglIncE = np.cos(aniAlt[idx] * deg2rad) * np.cos( + (90 - aniAzi[idx]) * deg2rad + ) * np.sin(np.pi / 2) + np.sin( + aniAlt[idx] * deg2rad + ) * np.cos( + np.pi / 2 + ) + diffRadE = ( + diffRadE + + diffsh[:, :, idx] + * lumChi[idx] + * anglIncE + * phiVar[idx] + ) # * 0.5 if aniAzi[idx] > (90) and aniAzi[idx] <= (270): - anglIncS = np.cos(aniAlt[idx] * deg2rad) * np.cos((180 - aniAzi[idx]) * deg2rad) * np.sin( - np.pi / 2) + np.sin( - aniAlt[idx] * deg2rad) * np.cos(np.pi / 2) - diffRadS = diffRadS + diffsh[:, :, idx] * lumChi[idx] * anglIncS * phiVar[idx] #* 0.5 + anglIncS = np.cos(aniAlt[idx] * deg2rad) * np.cos( + (180 - aniAzi[idx]) * deg2rad + ) * np.sin(np.pi / 2) + np.sin( + aniAlt[idx] * deg2rad + ) * np.cos( + np.pi / 2 + ) + diffRadS = ( + diffRadS + + diffsh[:, :, idx] + * lumChi[idx] + * anglIncS + * phiVar[idx] + ) # * 0.5 if aniAzi[idx] > (180) and aniAzi[idx] <= (360): - anglIncW = np.cos(aniAlt[idx] * deg2rad) * np.cos((270 - aniAzi[idx]) * deg2rad) * np.sin( - np.pi / 2) + np.sin( - aniAlt[idx] * deg2rad) * np.cos(np.pi / 2) - diffRadW = diffRadW + diffsh[:, :, idx] * lumChi[idx] * anglIncW * phiVar[idx] #* 0.5 + anglIncW = np.cos(aniAlt[idx] * deg2rad) * np.cos( + (270 - aniAzi[idx]) * deg2rad + ) * np.sin(np.pi / 2) + np.sin( + aniAlt[idx] * deg2rad + ) * np.cos( + np.pi / 2 + ) + diffRadW = ( + diffRadW + + diffsh[:, :, idx] + * lumChi[idx] + * anglIncW + * phiVar[idx] + ) # * 0.5 if aniAzi[idx] > (270) or aniAzi[idx] <= (90): - anglIncN = np.cos(aniAlt[idx] * deg2rad) * np.cos((0 - aniAzi[idx]) * deg2rad) * np.sin( - np.pi / 2) + np.sin( - aniAlt[idx] * deg2rad) * np.cos(np.pi / 2) - diffRadN = diffRadN + diffsh[:, :, idx] * lumChi[idx] * anglIncN * phiVar[idx] #* 0.5 + anglIncN = np.cos(aniAlt[idx] * deg2rad) * np.cos( + (0 - aniAzi[idx]) * deg2rad + ) * np.sin(np.pi / 2) + np.sin( + aniAlt[idx] * deg2rad + ) * np.cos( + np.pi / 2 + ) + diffRadN = ( + diffRadN + + diffsh[:, :, idx] + * lumChi[idx] + * anglIncN + * phiVar[idx] + ) # * 0.5 - KeastDG = diffRadE + (albedo * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) + KupE) * 0.5 + KeastDG = ( + diffRadE + + ( + albedo + * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) + + KupE + ) + * 0.5 + ) Keast = KeastI + KeastDG - KsouthDG = diffRadS + (albedo * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) + KupS) * 0.5 + KsouthDG = ( + diffRadS + + ( + albedo + * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) + + KupS + ) + * 0.5 + ) Ksouth = KsouthI + KsouthDG - KwestDG = diffRadW + (albedo * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + KupW) * 0.5 + KwestDG = ( + diffRadW + + ( + albedo + * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + + KupW + ) + * 0.5 + ) Kwest = KwestI + KwestDG - KnorthDG = diffRadN + (albedo * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + KupN) * 0.5 + KnorthDG = ( + diffRadN + + ( + albedo + * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + + KupN + ) + * 0.5 + ) Knorth = KnorthI + KnorthDG else: - KeastDG = (radD * (1 - svfviktbuvegE) + albedo * ( - svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) + KupE) * 0.5 + KeastDG = ( + radD * (1 - svfviktbuvegE) + + albedo * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) + + KupE + ) * 0.5 Keast = KeastI + KeastDG - KsouthDG = (radD * (1 - svfviktbuvegS) + albedo * ( - svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) + KupS) * 0.5 + KsouthDG = ( + radD * (1 - svfviktbuvegS) + + albedo * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) + + KupS + ) * 0.5 Ksouth = KsouthI + KsouthDG - KwestDG = (radD * (1 - svfviktbuvegW) + albedo * ( - svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + KupW) * 0.5 + KwestDG = ( + radD * (1 - svfviktbuvegW) + + albedo * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + + KupW + ) * 0.5 Kwest = KwestI + KwestDG - KnorthDG = (radD * (1 - svfviktbuvegN) + albedo * ( - svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + KupN) * 0.5 + KnorthDG = ( + radD * (1 - svfviktbuvegN) + + albedo * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + + KupN + ) * 0.5 Knorth = KnorthI + KnorthDG - return Keast,Ksouth,Kwest,Knorth,KsideI,KsideD \ No newline at end of file + return Keast, Ksouth, Kwest, Knorth, KsideI, KsideD diff --git a/functions/SOLWEIGpython/Kside_veg_v2022a.py b/functions/SOLWEIGpython/Kside_veg_v2022a.py index 9ad3b76..bc480a3 100644 --- a/functions/SOLWEIGpython/Kside_veg_v2022a.py +++ b/functions/SOLWEIGpython/Kside_veg_v2022a.py @@ -3,20 +3,49 @@ from .Kvikt_veg import Kvikt_veg from . import sunlit_shaded_patches -def Kside_veg_v2022a(radI,radD,radG, - shadow,svfS,svfW,svfN,svfE,svfEveg,svfSveg,svfWveg,svfNveg, - azimuth,altitude,psi,t,albedo,F_sh, - KupE,KupS,KupW,KupN, - cyl,lv,anisotropic_diffuse,diffsh,rows,cols,asvf, - shmat, vegshmat, vbshvegshmat): + +def Kside_veg_v2022a( + radI, + radD, + radG, + shadow, + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + azimuth, + altitude, + psi, + t, + albedo, + F_sh, + KupE, + KupS, + KupW, + KupN, + cyl, + lv, + anisotropic_diffuse, + diffsh, + rows, + cols, + asvf, + shmat, + vegshmat, + vbshvegshmat, +): # New reflection equation 2012-05-25 - vikttot=4.4897 - aziE=azimuth+t - aziS=azimuth-90+t - aziW=azimuth-180+t - aziN=azimuth-270+t - deg2rad=np.pi/180 + vikttot = 4.4897 + aziE = azimuth + t + aziS = azimuth - 90 + t + aziW = azimuth - 180 + t + aziN = azimuth - 270 + t + deg2rad = np.pi / 180 KsideD = np.zeros((rows, cols)) Kref_sun = np.zeros((rows, cols)) Kref_sh = np.zeros((rows, cols)) @@ -38,45 +67,74 @@ def Kside_veg_v2022a(radI,radD,radG, Kref_sun_e = np.zeros((rows, cols)) Kref_sun_w = np.zeros((rows, cols)) - KeastRef = np.zeros((rows, cols)); KwestRef = np.zeros((rows, cols)); KnorthRef = np.zeros((rows, cols)); KsouthRef = np.zeros((rows, cols)) - diffRadE = np.zeros((rows, cols)); diffRadS = np.zeros((rows, cols)); diffRadW = np.zeros((rows, cols)); diffRadN = np.zeros((rows, cols)) + KeastRef = np.zeros((rows, cols)) + KwestRef = np.zeros((rows, cols)) + KnorthRef = np.zeros((rows, cols)) + KsouthRef = np.zeros((rows, cols)) + diffRadE = np.zeros((rows, cols)) + diffRadS = np.zeros((rows, cols)) + diffRadW = np.zeros((rows, cols)) + diffRadN = np.zeros((rows, cols)) ### Direct radiation ### - if cyl == 1: ### Kside with cylinder ### - KsideI=shadow*radI*np.cos(altitude*deg2rad) - KeastI=0;KsouthI=0;KwestI=0;KnorthI=0 - else: ### Kside with weights ### - if azimuth > (360-t) or azimuth <= (180-t): - KeastI=radI*shadow*np.cos(altitude*deg2rad)*np.sin(aziE*deg2rad) + if cyl == 1: ### Kside with cylinder ### + KsideI = shadow * radI * np.cos(altitude * deg2rad) + KeastI = 0 + KsouthI = 0 + KwestI = 0 + KnorthI = 0 + else: ### Kside with weights ### + if azimuth > (360 - t) or azimuth <= (180 - t): + KeastI = ( + radI + * shadow + * np.cos(altitude * deg2rad) + * np.sin(aziE * deg2rad) + ) else: - KeastI=0 - if azimuth > (90-t) and azimuth <= (270-t): - KsouthI=radI*shadow*np.cos(altitude*deg2rad)*np.sin(aziS*deg2rad) + KeastI = 0 + if azimuth > (90 - t) and azimuth <= (270 - t): + KsouthI = ( + radI + * shadow + * np.cos(altitude * deg2rad) + * np.sin(aziS * deg2rad) + ) else: - KsouthI=0 - if azimuth > (180-t) and azimuth <= (360-t): - KwestI=radI*shadow*np.cos(altitude*deg2rad)*np.sin(aziW*deg2rad) + KsouthI = 0 + if azimuth > (180 - t) and azimuth <= (360 - t): + KwestI = ( + radI + * shadow + * np.cos(altitude * deg2rad) + * np.sin(aziW * deg2rad) + ) else: - KwestI=0 - if azimuth <= (90-t) or azimuth > (270-t): - KnorthI=radI*shadow*np.cos(altitude*deg2rad)*np.sin(aziN*deg2rad) + KwestI = 0 + if azimuth <= (90 - t) or azimuth > (270 - t): + KnorthI = ( + radI + * shadow + * np.cos(altitude * deg2rad) + * np.sin(aziN * deg2rad) + ) else: - KnorthI=0 + KnorthI = 0 - KsideI=shadow*0 + KsideI = shadow * 0 ### Diffuse and reflected radiation ### - [viktveg,viktwall]=Kvikt_veg(svfE,svfEveg,vikttot) - svfviktbuvegE=(viktwall+(viktveg)*(1-psi)) + [viktveg, viktwall] = Kvikt_veg(svfE, svfEveg, vikttot) + svfviktbuvegE = viktwall + (viktveg) * (1 - psi) - [viktveg,viktwall]=Kvikt_veg(svfS,svfSveg,vikttot) - svfviktbuvegS=(viktwall+(viktveg)*(1-psi)) + [viktveg, viktwall] = Kvikt_veg(svfS, svfSveg, vikttot) + svfviktbuvegS = viktwall + (viktveg) * (1 - psi) - [viktveg,viktwall]=Kvikt_veg(svfW,svfWveg,vikttot) - svfviktbuvegW=(viktwall+(viktveg)*(1-psi)) + [viktveg, viktwall] = Kvikt_veg(svfW, svfWveg, vikttot) + svfviktbuvegW = viktwall + (viktveg) * (1 - psi) - [viktveg,viktwall]=Kvikt_veg(svfN,svfNveg,vikttot) - svfviktbuvegN=(viktwall+(viktveg)*(1-psi)) + [viktveg, viktwall] = Kvikt_veg(svfN, svfNveg, vikttot) + svfviktbuvegN = viktwall + (viktveg) * (1 - psi) ### Anisotropic Diffuse Radiation after Perez et al. 1993 ### if anisotropic_diffuse == 1: @@ -89,7 +147,7 @@ def Kside_veg_v2022a(radI,radD,radG, patch_luminance = lv[:, 2] else: patch_luminance = np.zeros((patch_altitude.shape[0])) - patch_luminance[:] = 1.0/patch_luminance.shape[0] + patch_luminance[:] = 1.0 / patch_luminance.shape[0] # Unique altitudes for patches skyalt, skyalt_c = np.unique(patch_altitude, return_counts=True) @@ -101,152 +159,422 @@ def Kside_veg_v2022a(radI,radD,radG, for i in range(patch_altitude.shape[0]): # If there are more than one patch in a band if skyalt_c[skyalt == patch_altitude[i]] > 1: - steradian[i] = ((360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad) * (np.sin((patch_altitude[i] + patch_altitude[0]) * deg2rad) \ - - np.sin((patch_altitude[i] - patch_altitude[0]) * deg2rad)) + steradian[i] = ( + (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad + ) * ( + np.sin((patch_altitude[i] + patch_altitude[0]) * deg2rad) + - np.sin((patch_altitude[i] - patch_altitude[0]) * deg2rad) + ) # If there is only one patch in band, i.e. 90 degrees else: - steradian[i] = ((360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad) * (np.sin((patch_altitude[i]) * deg2rad) \ - - np.sin((patch_altitude[i-1] + patch_altitude[0]) * deg2rad)) - - radTot += (patch_luminance[i] * steradian[i] * np.sin(patch_altitude[i] * deg2rad)) # Radiance fraction normalization - - lumChi = (patch_luminance * radD) / radTot # Radiance fraction normalization + steradian[i] = ( + (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad + ) * ( + np.sin((patch_altitude[i]) * deg2rad) + - np.sin( + (patch_altitude[i - 1] + patch_altitude[0]) * deg2rad + ) + ) + + radTot += ( + patch_luminance[i] + * steradian[i] + * np.sin(patch_altitude[i] * deg2rad) + ) # Radiance fraction normalization + + lumChi = ( + patch_luminance * radD + ) / radTot # Radiance fraction normalization if cyl == 1: for idx in range(patch_azimuth.shape[0]): # Angle of incidence, np.cos(0) because cylinder - always perpendicular - anglIncC = np.cos(patch_altitude[idx] * deg2rad) * np.cos(0) # * np.sin(np.pi / 2) \ - # + np.sin(patch_altitude[idx] * deg2rad) * np.cos(np.pi / 2) + anglIncC = np.cos(patch_altitude[idx] * deg2rad) * np.cos( + 0 + ) # * np.sin(np.pi / 2) \ + # + np.sin(patch_altitude[idx] * deg2rad) * np.cos(np.pi / 2) # Diffuse vertical radiation - KsideD += diffsh[:, :, idx] * lumChi[idx] * anglIncC * steradian[idx] + KsideD += ( + diffsh[:, :, idx] * lumChi[idx] * anglIncC * steradian[idx] + ) # Shortwave reflected on sunlit surfaces # sunlit_surface = ((albedo * radG) / np.pi) - sunlit_surface = ((albedo * (radI * np.cos(altitude * deg2rad)) + (radD * 0.5)) / np.pi) + sunlit_surface = ( + albedo * (radI * np.cos(altitude * deg2rad)) + (radD * 0.5) + ) / np.pi # Shortwave reflected on shaded surfaces and vegetation - shaded_surface = ((albedo * radD * 0.5) / np.pi) - + shaded_surface = (albedo * radD * 0.5) / np.pi + # Shortwave radiation reflected on vegetation - based on diffuse shortwave radiation - temp_vegsh = ((vegshmat[:,:,idx] == 0) | (vbshvegshmat[:,:,idx] == 0)) - Kref_veg += shaded_surface * temp_vegsh * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) + temp_vegsh = (vegshmat[:, :, idx] == 0) | ( + vbshvegshmat[:, :, idx] == 0 + ) + Kref_veg += ( + shaded_surface + * temp_vegsh + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + ) # Shortwave radiation reflected on buildings (shaded and sunlit) - based on global and diffuse shortwave radiation - temp_vbsh = (1 - shmat[:,:,idx]) * vbshvegshmat[:,:,idx] - temp_sh = (temp_vbsh == 1) # & (vbshvegshmat[:,:,idx] == 1) - - sunlit_patches, shaded_patches = sunlit_shaded_patches.shaded_or_sunlit(altitude, azimuth, patch_altitude[idx], patch_azimuth[idx], asvf) - Kref_sun += sunlit_surface * sunlit_patches * temp_sh * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) - Kref_sh += shaded_surface * shaded_patches * temp_sh * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) + temp_vbsh = (1 - shmat[:, :, idx]) * vbshvegshmat[:, :, idx] + temp_sh = temp_vbsh == 1 # & (vbshvegshmat[:,:,idx] == 1) + + sunlit_patches, shaded_patches = ( + sunlit_shaded_patches.shaded_or_sunlit( + altitude, + azimuth, + patch_altitude[idx], + patch_azimuth[idx], + asvf, + ) + ) + Kref_sun += ( + sunlit_surface + * sunlit_patches + * temp_sh + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + ) + Kref_sh += ( + shaded_surface + * shaded_patches + * temp_sh + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + ) Kside = KsideI + KsideD + Kref_sun + Kref_sh + Kref_veg - Keast = (KupE * 0.5) - Kwest = (KupW * 0.5) - Knorth = (KupN * 0.5) - Ksouth = (KupS * 0.5) + Keast = KupE * 0.5 + Kwest = KupW * 0.5 + Knorth = KupN * 0.5 + Ksouth = KupS * 0.5 # Keast = (albedo * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) + KupE) * 0.5 # Ksouth = (albedo * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) + KupS) * 0.5 # Kwest = (albedo * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + KupW) * 0.5 # Knorth = (albedo * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + KupN) * 0.5 - else: # Box - diffRadE = np.zeros((rows, cols)); diffRadS = np.zeros((rows, cols)); diffRadW = np.zeros((rows, cols)); diffRadN = np.zeros((rows, cols)) + else: # Box + diffRadE = np.zeros((rows, cols)) + diffRadS = np.zeros((rows, cols)) + diffRadW = np.zeros((rows, cols)) + diffRadN = np.zeros((rows, cols)) for idx in range(patch_azimuth.shape[0]): if (patch_azimuth[idx] > 360) or (patch_azimuth[idx] <= 180): - anglIncE = np.cos(patch_altitude[idx] * deg2rad) * np.cos((90 - patch_azimuth[idx] + t) * deg2rad) # * np.sin(np.pi / 2) \ - # + np.sin(patch_altitude[idx] * deg2rad) * np.cos(np.pi / 2) - diffRadE += diffsh[:, :, idx] * lumChi[idx] * anglIncE * steradian[idx] #* 0.5 + anglIncE = np.cos(patch_altitude[idx] * deg2rad) * np.cos( + (90 - patch_azimuth[idx] + t) * deg2rad + ) # * np.sin(np.pi / 2) \ + # + np.sin(patch_altitude[idx] * deg2rad) * np.cos(np.pi / 2) + diffRadE += ( + diffsh[:, :, idx] + * lumChi[idx] + * anglIncE + * steradian[idx] + ) # * 0.5 if (patch_azimuth[idx] > 90) and (patch_azimuth[idx] <= 270): - anglIncS = np.cos(patch_altitude[idx] * deg2rad) * np.cos((180 - patch_azimuth[idx] + t) * deg2rad) # * np.sin(np.pi / 2) \ - # + np.sin(patch_altitude[idx] * deg2rad) * np.cos(np.pi / 2) - diffRadS += diffsh[:, :, idx] * lumChi[idx] * anglIncS * steradian[idx] #* 0.5 + anglIncS = np.cos(patch_altitude[idx] * deg2rad) * np.cos( + (180 - patch_azimuth[idx] + t) * deg2rad + ) # * np.sin(np.pi / 2) \ + # + np.sin(patch_altitude[idx] * deg2rad) * np.cos(np.pi / 2) + diffRadS += ( + diffsh[:, :, idx] + * lumChi[idx] + * anglIncS + * steradian[idx] + ) # * 0.5 if (patch_azimuth[idx] > 180) and (patch_azimuth[idx] <= 360): - anglIncW = np.cos(patch_altitude[idx] * deg2rad) * np.cos((270 - patch_azimuth[idx] + t) * deg2rad) # * np.sin(np.pi / 2) \ - # + np.sin(patch_altitude[idx] * deg2rad) * np.cos(np.pi / 2) - diffRadW += diffsh[:, :, idx] * lumChi[idx] * anglIncW * steradian[idx] #* 0.5 + anglIncW = np.cos(patch_altitude[idx] * deg2rad) * np.cos( + (270 - patch_azimuth[idx] + t) * deg2rad + ) # * np.sin(np.pi / 2) \ + # + np.sin(patch_altitude[idx] * deg2rad) * np.cos(np.pi / 2) + diffRadW += ( + diffsh[:, :, idx] + * lumChi[idx] + * anglIncW + * steradian[idx] + ) # * 0.5 if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] <= 90): - anglIncN = np.cos(patch_altitude[idx] * deg2rad) * np.cos((0 - patch_azimuth[idx] + t) * deg2rad) # * np.sin(np.pi / 2) \ - # + np.sin(patch_altitude[idx] * deg2rad) * np.cos(np.pi / 2) - diffRadN += diffsh[:, :, idx] * lumChi[idx] * anglIncN * steradian[idx] #* 0.5 - + anglIncN = np.cos(patch_altitude[idx] * deg2rad) * np.cos( + (0 - patch_azimuth[idx] + t) * deg2rad + ) # * np.sin(np.pi / 2) \ + # + np.sin(patch_altitude[idx] * deg2rad) * np.cos(np.pi / 2) + diffRadN += ( + diffsh[:, :, idx] + * lumChi[idx] + * anglIncN + * steradian[idx] + ) # * 0.5 + # Shortwave reflected on sunlit surfaces # sunlit_surface = ((albedo * radG) / np.pi) - sunlit_surface = ((albedo * (radI * np.cos(altitude * deg2rad)) + (radD * 0.5)) / np.pi) + sunlit_surface = ( + albedo * (radI * np.cos(altitude * deg2rad)) + (radD * 0.5) + ) / np.pi # Shortwave reflected on shaded surfaces and vegetation - shaded_surface = ((albedo * radD * 0.5) / np.pi) - + shaded_surface = (albedo * radD * 0.5) / np.pi + # Shortwave radiation reflected on vegetation - based on diffuse shortwave radiation - temp_vegsh = ((vegshmat[:,:,idx] == 0) | (vbshvegshmat[:,:,idx] == 0)) - Kref_veg += shaded_surface * temp_vegsh * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) + temp_vegsh = (vegshmat[:, :, idx] == 0) | ( + vbshvegshmat[:, :, idx] == 0 + ) + Kref_veg += ( + shaded_surface + * temp_vegsh + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + ) if (patch_azimuth[idx] > 360) or (patch_azimuth[idx] < 180): - Kref_veg_e += shaded_surface * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_vegsh * np.cos((90 - patch_azimuth[idx] + t) * deg2rad) + Kref_veg_e += ( + shaded_surface + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_vegsh + * np.cos((90 - patch_azimuth[idx] + t) * deg2rad) + ) if (patch_azimuth[idx] > 90) and (patch_azimuth[idx] < 270): - Kref_veg_s += shaded_surface * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_vegsh * np.cos((180 - patch_azimuth[idx] + t) * deg2rad) + Kref_veg_s += ( + shaded_surface + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_vegsh + * np.cos((180 - patch_azimuth[idx] + t) * deg2rad) + ) if (patch_azimuth[idx] > 180) and (patch_azimuth[idx] < 360): - Kref_veg_w += shaded_surface * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_vegsh * np.cos((270 - patch_azimuth[idx] + t) * deg2rad) + Kref_veg_w += ( + shaded_surface + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_vegsh + * np.cos((270 - patch_azimuth[idx] + t) * deg2rad) + ) if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] < 90): - Kref_veg_n += shaded_surface * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_vegsh * np.cos((0 - patch_azimuth[idx] + t) * deg2rad) + Kref_veg_n += ( + shaded_surface + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_vegsh + * np.cos((0 - patch_azimuth[idx] + t) * deg2rad) + ) # Shortwave radiation reflected on buildings (shaded and sunlit) - based on global and diffuse shortwave radiation - temp_vbsh = (1 - shmat[:,:,idx]) * vbshvegshmat[:,:,idx] - temp_sh = (temp_vbsh == 1) # & (vbshvegshmat[:,:,idx] == 1) + temp_vbsh = (1 - shmat[:, :, idx]) * vbshvegshmat[:, :, idx] + temp_sh = temp_vbsh == 1 # & (vbshvegshmat[:,:,idx] == 1) azimuth_difference = np.abs(azimuth - patch_azimuth[idx]) - if ((azimuth_difference > 90) and (azimuth_difference < 270)): - sunlit_patches, shaded_patches = sunlit_shaded_patches.shaded_or_sunlit(altitude, azimuth, patch_altitude[idx], patch_azimuth[idx], asvf) - Kref_sun += sunlit_surface * sunlit_patches * temp_sh * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) - Kref_sh += shaded_surface * shaded_patches * temp_sh * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) - - if (patch_azimuth[idx] > 360) or (patch_azimuth[idx] < 180): - Kref_sun_e += sunlit_surface * sunlit_patches * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((90 - patch_azimuth[idx] + t) * deg2rad) - Kref_sh_e += shaded_surface * shaded_patches * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((90 - patch_azimuth[idx] + t) * deg2rad) - if (patch_azimuth[idx] > 90) and (patch_azimuth[idx] < 270): - Kref_sun_s += sunlit_surface * sunlit_patches * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((180 - patch_azimuth[idx] + t) * deg2rad) - Kref_sh_s += shaded_surface * shaded_patches * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((180 - patch_azimuth[idx] + t) * deg2rad) - if (patch_azimuth[idx] > 180) and (patch_azimuth[idx] < 360): - Kref_sun_w += sunlit_surface * sunlit_patches * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((270 - patch_azimuth[idx] + t) * deg2rad) - Kref_sh_w += shaded_surface * shaded_patches * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((270 - patch_azimuth[idx] + t) * deg2rad) + if (azimuth_difference > 90) and (azimuth_difference < 270): + sunlit_patches, shaded_patches = ( + sunlit_shaded_patches.shaded_or_sunlit( + altitude, + azimuth, + patch_altitude[idx], + patch_azimuth[idx], + asvf, + ) + ) + Kref_sun += ( + sunlit_surface + * sunlit_patches + * temp_sh + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + ) + Kref_sh += ( + shaded_surface + * shaded_patches + * temp_sh + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + ) + + if (patch_azimuth[idx] > 360) or ( + patch_azimuth[idx] < 180 + ): + Kref_sun_e += ( + sunlit_surface + * sunlit_patches + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((90 - patch_azimuth[idx] + t) * deg2rad) + ) + Kref_sh_e += ( + shaded_surface + * shaded_patches + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((90 - patch_azimuth[idx] + t) * deg2rad) + ) + if (patch_azimuth[idx] > 90) and ( + patch_azimuth[idx] < 270 + ): + Kref_sun_s += ( + sunlit_surface + * sunlit_patches + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((180 - patch_azimuth[idx] + t) * deg2rad) + ) + Kref_sh_s += ( + shaded_surface + * shaded_patches + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((180 - patch_azimuth[idx] + t) * deg2rad) + ) + if (patch_azimuth[idx] > 180) and ( + patch_azimuth[idx] < 360 + ): + Kref_sun_w += ( + sunlit_surface + * sunlit_patches + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((270 - patch_azimuth[idx] + t) * deg2rad) + ) + Kref_sh_w += ( + shaded_surface + * shaded_patches + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((270 - patch_azimuth[idx] + t) * deg2rad) + ) if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] < 90): - Kref_sun_n += sunlit_surface * sunlit_patches * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((0 - patch_azimuth[idx] + t) * deg2rad) - Kref_sh_n += shaded_surface * shaded_patches * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((0 - patch_azimuth[idx] + t) * deg2rad) + Kref_sun_n += ( + sunlit_surface + * sunlit_patches + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((0 - patch_azimuth[idx] + t) * deg2rad) + ) + Kref_sh_n += ( + shaded_surface + * shaded_patches + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((0 - patch_azimuth[idx] + t) * deg2rad) + ) else: - Kref_sh += shaded_surface * temp_sh * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) - - if (patch_azimuth[idx] > 360) or (patch_azimuth[idx] < 180): - Kref_sh_e += shaded_surface * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((90 - patch_azimuth[idx] + t) * deg2rad) - if (patch_azimuth[idx] > 90) and (patch_azimuth[idx] < 270): - Kref_sh_s += shaded_surface * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((180 - patch_azimuth[idx] + t) * deg2rad) - if (patch_azimuth[idx] > 180) and (patch_azimuth[idx] < 360): - Kref_sh_w += shaded_surface * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((270 - patch_azimuth[idx] + t) * deg2rad) + Kref_sh += ( + shaded_surface + * temp_sh + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + ) + + if (patch_azimuth[idx] > 360) or ( + patch_azimuth[idx] < 180 + ): + Kref_sh_e += ( + shaded_surface + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((90 - patch_azimuth[idx] + t) * deg2rad) + ) + if (patch_azimuth[idx] > 90) and ( + patch_azimuth[idx] < 270 + ): + Kref_sh_s += ( + shaded_surface + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((180 - patch_azimuth[idx] + t) * deg2rad) + ) + if (patch_azimuth[idx] > 180) and ( + patch_azimuth[idx] < 360 + ): + Kref_sh_w += ( + shaded_surface + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((270 - patch_azimuth[idx] + t) * deg2rad) + ) if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] < 90): - Kref_sh_n += shaded_surface * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((0 - patch_azimuth[idx] + t) * deg2rad) - - Keast = KeastI + diffRadE + Kref_sun_e + Kref_sh_e + Kref_veg_e + KupE * 0.5 - Kwest = KwestI + diffRadW + Kref_sun_w + Kref_sh_w + Kref_veg_w + KupW * 0.5 - Knorth = KnorthI + diffRadN + Kref_sun_n + Kref_sh_n + Kref_veg_n + KupN * 0.5 - Ksouth = KsouthI + diffRadS + Kref_sun_s + Kref_sh_s + Kref_veg_s + KupS * 0.5 + Kref_sh_n += ( + shaded_surface + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((0 - patch_azimuth[idx] + t) * deg2rad) + ) + + Keast = ( + KeastI + + diffRadE + + Kref_sun_e + + Kref_sh_e + + Kref_veg_e + + KupE * 0.5 + ) + Kwest = ( + KwestI + + diffRadW + + Kref_sun_w + + Kref_sh_w + + Kref_veg_w + + KupW * 0.5 + ) + Knorth = ( + KnorthI + + diffRadN + + Kref_sun_n + + Kref_sh_n + + Kref_veg_n + + KupN * 0.5 + ) + Ksouth = ( + KsouthI + + diffRadS + + Kref_sun_s + + Kref_sh_s + + Kref_veg_s + + KupS * 0.5 + ) else: - KeastDG = (radD * (1 - svfviktbuvegE) + albedo * ( - svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) + KupE) * 0.5 + KeastDG = ( + radD * (1 - svfviktbuvegE) + + albedo * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) + + KupE + ) * 0.5 Keast = KeastI + KeastDG - KsouthDG = (radD * (1 - svfviktbuvegS) + albedo * ( - svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) + KupS) * 0.5 + KsouthDG = ( + radD * (1 - svfviktbuvegS) + + albedo * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) + + KupS + ) * 0.5 Ksouth = KsouthI + KsouthDG - KwestDG = (radD * (1 - svfviktbuvegW) + albedo * ( - svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + KupW) * 0.5 + KwestDG = ( + radD * (1 - svfviktbuvegW) + + albedo * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + + KupW + ) * 0.5 Kwest = KwestI + KwestDG - KnorthDG = (radD * (1 - svfviktbuvegN) + albedo * ( - svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + KupN) * 0.5 + KnorthDG = ( + radD * (1 - svfviktbuvegN) + + albedo * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + + KupN + ) * 0.5 Knorth = KnorthI + KnorthDG return Keast, Ksouth, Kwest, Knorth, KsideI, KsideD, Kside - # return Keast, Ksouth, Kwest, Knorth, KsideI, KsideD, Kref_sh, Kref_sun, Kref_veg, Kside \ No newline at end of file + # return Keast, Ksouth, Kwest, Knorth, KsideI, KsideD, Kref_sh, Kref_sun, Kref_veg, Kside diff --git a/functions/SOLWEIGpython/Kup_veg_2015a.py b/functions/SOLWEIGpython/Kup_veg_2015a.py index c9bd317..d2c7f69 100644 --- a/functions/SOLWEIGpython/Kup_veg_2015a.py +++ b/functions/SOLWEIGpython/Kup_veg_2015a.py @@ -1,15 +1,49 @@ import numpy as np -def Kup_veg_2015a(radI,radD,radG,altitude,svfbuveg,albedo_b,F_sh,gvfalb,gvfalbE,gvfalbS,gvfalbW,gvfalbN,gvfalbnosh,gvfalbnoshE,gvfalbnoshS,gvfalbnoshW,gvfalbnoshN): - Kup=(gvfalb*radI*np.sin(altitude*(np.pi/180.)))+(radD*svfbuveg+albedo_b*(1-svfbuveg)*(radG*(1-F_sh)+radD*F_sh))*gvfalbnosh +def Kup_veg_2015a( + radI, + radD, + radG, + altitude, + svfbuveg, + albedo_b, + F_sh, + gvfalb, + gvfalbE, + gvfalbS, + gvfalbW, + gvfalbN, + gvfalbnosh, + gvfalbnoshE, + gvfalbnoshS, + gvfalbnoshW, + gvfalbnoshN, +): - KupE=(gvfalbE*radI*np.sin(altitude*(np.pi/180.)))+(radD*svfbuveg+albedo_b*(1-svfbuveg)*(radG*(1-F_sh)+radD*F_sh))*gvfalbnoshE + Kup = (gvfalb * radI * np.sin(altitude * (np.pi / 180.0))) + ( + radD * svfbuveg + + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) + ) * gvfalbnosh - KupS=(gvfalbS*radI*np.sin(altitude*(np.pi/180.)))+(radD*svfbuveg+albedo_b*(1-svfbuveg)*(radG*(1-F_sh)+radD*F_sh))*gvfalbnoshS + KupE = (gvfalbE * radI * np.sin(altitude * (np.pi / 180.0))) + ( + radD * svfbuveg + + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) + ) * gvfalbnoshE - KupW=(gvfalbW*radI*np.sin(altitude*(np.pi/180.)))+(radD*svfbuveg+albedo_b*(1-svfbuveg)*(radG*(1-F_sh)+radD*F_sh))*gvfalbnoshW + KupS = (gvfalbS * radI * np.sin(altitude * (np.pi / 180.0))) + ( + radD * svfbuveg + + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) + ) * gvfalbnoshS - KupN=(gvfalbN*radI*np.sin(altitude*(np.pi/180.)))+(radD*svfbuveg+albedo_b*(1-svfbuveg)*(radG*(1-F_sh)+radD*F_sh))*gvfalbnoshN + KupW = (gvfalbW * radI * np.sin(altitude * (np.pi / 180.0))) + ( + radD * svfbuveg + + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) + ) * gvfalbnoshW - return Kup, KupE, KupS, KupW, KupN \ No newline at end of file + KupN = (gvfalbN * radI * np.sin(altitude * (np.pi / 180.0))) + ( + radD * svfbuveg + + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) + ) * gvfalbnoshN + + return Kup, KupE, KupS, KupW, KupN diff --git a/functions/SOLWEIGpython/Kvikt_veg.py b/functions/SOLWEIGpython/Kvikt_veg.py index 87ff24f..adc6d36 100644 --- a/functions/SOLWEIGpython/Kvikt_veg.py +++ b/functions/SOLWEIGpython/Kvikt_veg.py @@ -1,11 +1,30 @@ - -def Kvikt_veg(svf,svfveg,vikttot): +def Kvikt_veg(svf, svfveg, vikttot): # Least - viktwall=(vikttot-(63.227*svf**6-161.51*svf**5+156.91*svf**4-70.424*svf**3+16.773*svf**2-0.4863*svf))/vikttot - - svfvegbu=(svfveg+svf-1) # Vegetation plus buildings - viktveg=(vikttot-(63.227*svfvegbu**6-161.51*svfvegbu**5+156.91*svfvegbu**4-70.424*svfvegbu**3+16.773*svfvegbu**2-0.4863*svfvegbu))/vikttot - viktveg=viktveg-viktwall - - return viktveg,viktwall \ No newline at end of file + viktwall = ( + vikttot + - ( + 63.227 * svf**6 + - 161.51 * svf**5 + + 156.91 * svf**4 + - 70.424 * svf**3 + + 16.773 * svf**2 + - 0.4863 * svf + ) + ) / vikttot + + svfvegbu = svfveg + svf - 1 # Vegetation plus buildings + viktveg = ( + vikttot + - ( + 63.227 * svfvegbu**6 + - 161.51 * svfvegbu**5 + + 156.91 * svfvegbu**4 + - 70.424 * svfvegbu**3 + + 16.773 * svfvegbu**2 + - 0.4863 * svfvegbu + ) + ) / vikttot + viktveg = viktveg - viktwall + + return viktveg, viktwall diff --git a/functions/SOLWEIGpython/Lcyl_v2022a.py b/functions/SOLWEIGpython/Lcyl_v2022a.py index 80f8103..a7fc5da 100644 --- a/functions/SOLWEIGpython/Lcyl_v2022a.py +++ b/functions/SOLWEIGpython/Lcyl_v2022a.py @@ -3,17 +3,34 @@ from . import emissivity_models from . import patch_characteristics -''' This function combines the method to divide the sky vault into patches (Tregenza (1987) and Robinson & Stone (2004)) +""" This function combines the method to divide the sky vault into patches (Tregenza (1987) and Robinson & Stone (2004)) and the approach by Unsworth & Monteith or Martin & Berdahl (1984) or Bliss (1961) to calculate emissivities of the - different parts of the sky vault. ''' - -def Lcyl_v2022a(esky, sky_patches, Ta, Tgwall, ewall, Lup, shmat, vegshmat, vbshvegshmat, solar_altitude, solar_azimuth, rows, cols, asvf, current_step): + different parts of the sky vault. """ + + +def Lcyl_v2022a( + esky, + sky_patches, + Ta, + Tgwall, + ewall, + Lup, + shmat, + vegshmat, + vbshvegshmat, + solar_altitude, + solar_azimuth, + rows, + cols, + asvf, + current_step, +): # Stefan-Boltzmann's Constant SBC = 5.67051e-8 # Sky longwave radiation from emissivity based on Prata (1996) - Ldown_prata = (esky * SBC * ((Ta + 273.15) ** 4)) + Ldown_prata = esky * SBC * ((Ta + 273.15) ** 4) # Degrees to radians deg2rad = np.pi / 180 @@ -31,25 +48,39 @@ def Lcyl_v2022a(esky, sky_patches, Ta, Tgwall, ewall, Lup, shmat, vegshmat, vbsh # Unsworth & Monteith (1975) if emis_m == 1: - patch_emissivity_normalized, esky_band = emissivity_models.model1(sky_patches, esky, Ta) + patch_emissivity_normalized, esky_band = emissivity_models.model1( + sky_patches, esky, Ta + ) # Martin & Berdahl (1984) elif emis_m == 2: - patch_emissivity_normalized, esky_band = emissivity_models.model2(sky_patches, esky, Ta) + patch_emissivity_normalized, esky_band = emissivity_models.model2( + sky_patches, esky, Ta + ) # Bliss (1961) elif emis_m == 3: - patch_emissivity_normalized, esky_band = emissivity_models.model3(sky_patches, esky, Ta) + patch_emissivity_normalized, esky_band = emissivity_models.model3( + sky_patches, esky, Ta + ) # Calculation of steradian for each patch steradian = np.zeros((patch_altitude.shape[0])) for i in range(patch_altitude.shape[0]): # If there are more than one patch in a band if skyalt_c[skyalt == patch_altitude[i]] > 1: - steradian[i] = ((360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad) * (np.sin((patch_altitude[i] + patch_altitude[0]) * deg2rad) \ - - np.sin((patch_altitude[i] - patch_altitude[0]) * deg2rad)) + steradian[i] = ( + (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad + ) * ( + np.sin((patch_altitude[i] + patch_altitude[0]) * deg2rad) + - np.sin((patch_altitude[i] - patch_altitude[0]) * deg2rad) + ) # If there is only one patch in band, i.e. 90 degrees else: - steradian[i] = ((360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad) * (np.sin((patch_altitude[i]) * deg2rad) \ - - np.sin((patch_altitude[i-1] + patch_altitude[0]) * deg2rad)) + steradian[i] = ( + (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad + ) * ( + np.sin((patch_altitude[i]) * deg2rad) + - np.sin((patch_altitude[i - 1] + patch_altitude[0]) * deg2rad) + ) # True = anisotropic sky, False = isotropic sky anisotropic_sky = True @@ -67,27 +98,62 @@ def Lcyl_v2022a(esky, sky_patches, Ta, Tgwall, ewall, Lup, shmat, vegshmat, vbsh else: temp_emissivity = esky # Estimate longwave radiation on a horizontal surface (Ldown), vertical surface (Lside) and perpendicular (Lnormal) - Ldown[patch_altitude == altitude] = ((temp_emissivity * SBC * ((Ta + 273.15) ** 4)) / np.pi) * steradian[patch_altitude == altitude] * np.sin(altitude * deg2rad) - Lside[patch_altitude == altitude] = ((temp_emissivity * SBC * ((Ta + 273.15) ** 4)) / np.pi) * steradian[patch_altitude == altitude] * np.cos(altitude * deg2rad) - Lnormal[patch_altitude == altitude] = ((temp_emissivity * SBC * ((Ta + 273.15) ** 4)) / np.pi) * steradian[patch_altitude == altitude] + Ldown[patch_altitude == altitude] = ( + ((temp_emissivity * SBC * ((Ta + 273.15) ** 4)) / np.pi) + * steradian[patch_altitude == altitude] + * np.sin(altitude * deg2rad) + ) + Lside[patch_altitude == altitude] = ( + ((temp_emissivity * SBC * ((Ta + 273.15) ** 4)) / np.pi) + * steradian[patch_altitude == altitude] + * np.cos(altitude * deg2rad) + ) + Lnormal[patch_altitude == altitude] = ( + (temp_emissivity * SBC * ((Ta + 273.15) ** 4)) / np.pi + ) * steradian[patch_altitude == altitude] Lsky_normal = deepcopy(sky_patches) Lsky_down = deepcopy(sky_patches) Lsky_side = deepcopy(sky_patches) - Lsky_normal[:,2] = Lnormal - Lsky_down[:,2] = Ldown - Lsky_side[:,2] = Lside + Lsky_normal[:, 2] = Lnormal + Lsky_down[:, 2] = Ldown + Lsky_side[:, 2] = Lside # Estimate longwave radiation in each patch based on patch characteristics, i.e. sky, vegetation or building (shaded or sunlit) - Ldown, Lside, Lside_sky, Lside_veg, Lside_sh, Lside_sun, Lside_ref, \ - Least_, Lwest_, Lnorth_, Lsouth_ = patch_characteristics.define_patch_characteristics(solar_altitude, solar_azimuth, - patch_altitude, patch_azimuth, steradian, - asvf, - shmat, vegshmat, vbshvegshmat, - Lsky_down, Lsky_side, Lsky_normal, Lup, - Ta, Tgwall, ewall, - rows, cols, current_step) + ( + Ldown, + Lside, + Lside_sky, + Lside_veg, + Lside_sh, + Lside_sun, + Lside_ref, + Least_, + Lwest_, + Lnorth_, + Lsouth_, + ) = patch_characteristics.define_patch_characteristics( + solar_altitude, + solar_azimuth, + patch_altitude, + patch_azimuth, + steradian, + asvf, + shmat, + vegshmat, + vbshvegshmat, + Lsky_down, + Lsky_side, + Lsky_normal, + Lup, + Ta, + Tgwall, + ewall, + rows, + cols, + current_step, + ) # print('Lside_sky old = ' + str(Lside_sky.max())) # print('Lside_veg old = ' + str(Lside_veg.max())) @@ -96,4 +162,4 @@ def Lcyl_v2022a(esky, sky_patches, Ta, Tgwall, ewall, Lup, shmat, vegshmat, vbsh # print('Lside_ref old = ' + str(Lside_ref.max())) return Ldown, Lside, Least_, Lwest_, Lnorth_, Lsouth_ - # return Ldown, Lside, Lside_sky, Lside_veg, Lside_sh, Lside_sun, Lside_ref, Lsky_normal, Lsky_down, Lsky_side, Least_, Lwest_, Lnorth_, Lsouth_ \ No newline at end of file + # return Ldown, Lside, Lside_sky, Lside_veg, Lside_sh, Lside_sun, Lside_ref, Lsky_normal, Lsky_down, Lsky_side, Least_, Lwest_, Lnorth_, Lsouth_ diff --git a/functions/SOLWEIGpython/Lside_veg.py b/functions/SOLWEIGpython/Lside_veg.py new file mode 100644 index 0000000..da67dc7 --- /dev/null +++ b/functions/SOLWEIGpython/Lside_veg.py @@ -0,0 +1,437 @@ +from __future__ import absolute_import +import numpy as np +from .Lvikt_veg import Lvikt_veg + + +def Lside_veg_v2022a( + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + azimuth, + altitude, + Ta, + Tw, + SBC, + ewall, + Ldown, + esky, + t, + F_sh, + CI, + LupE, + LupS, + LupW, + LupN, + anisotropic_longwave, +): + + # This m-file is the current one that estimates L from the four cardinal points 20100414 + + # Building height angle from svf + svfalfaE = np.arcsin(np.exp((np.log(1 - svfE)) / 2)) + svfalfaS = np.arcsin(np.exp((np.log(1 - svfS)) / 2)) + svfalfaW = np.arcsin(np.exp((np.log(1 - svfW)) / 2)) + svfalfaN = np.arcsin(np.exp((np.log(1 - svfN)) / 2)) + + vikttot = 4.4897 + aziW = azimuth + t + aziN = azimuth - 90 + t + aziE = azimuth - 180 + t + aziS = azimuth - 270 + t + + F_sh = 2 * F_sh - 1 # (cylindric_wedge scaled 0-1) + + c = 1 - CI + Lsky_allsky = esky * SBC * ((Ta + 273.15) ** 4) * (1 - c) + c * SBC * ( + (Ta + 273.15) ** 4 + ) + + ## Least + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfE, svfEveg, svfEaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = np.arctan(svfalfaE) + betaB = np.arctan(np.tan((svfalfaE) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB + # betasun = np.arctan(0.5*np.tan(svfalfaE)*(1+F_sh)) #TODO This should be considered in future versions + if (azimuth > (180 - t)) and (azimuth <= (360 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziE * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) + else: + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + # Longwave from ground (see Lcyl_v2022a for remaining fluxes) + if anisotropic_longwave == 1: + Lground = LupE * 0.5 + Least = Lground + else: + Lsky = ((svfE + svfEveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupE * 0.5 + Lrefl = (Ldown + LupE) * (viktrefl) * (1 - ewall) * 0.5 + Least = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky + + ## Lsouth + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfS, svfSveg, svfSaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = np.arctan(svfalfaS) + betaB = np.arctan(np.tan((svfalfaS) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB + # betasun = np.arctan(0.5*np.tan(svfalfaS)*(1+F_sh)) + if (azimuth <= (90 - t)) or (azimuth > (270 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziS * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) + else: + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + # Longwave from ground (see Lcyl_v2022a for remaining fluxes) + if anisotropic_longwave == 1: + Lground = LupS * 0.5 + Lsouth = Lground + else: + Lsky = ((svfS + svfSveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupS * 0.5 + Lrefl = (Ldown + LupS) * (viktrefl) * (1 - ewall) * 0.5 + Lsouth = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky + + ## Lwest + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfW, svfWveg, svfWaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = np.arctan(svfalfaW) + betaB = np.arctan(np.tan((svfalfaW) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB + # betasun = np.arctan(0.5*np.tan(svfalfaW)*(1+F_sh)) + if (azimuth > (360 - t)) or (azimuth <= (180 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziW * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) + else: + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + # Longwave from ground (see Lcyl_v2022a for remaining fluxes) + if anisotropic_longwave == 1: + Lground = LupW * 0.5 + Lwest = Lground + else: + Lsky = ((svfW + svfWveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupW * 0.5 + Lrefl = (Ldown + LupW) * (viktrefl) * (1 - ewall) * 0.5 + Lwest = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky + + ## Lnorth + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfN, svfNveg, svfNaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = np.arctan(svfalfaN) + betaB = np.arctan(np.tan((svfalfaN) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB + # betasun = np.arctan(0.5*np.tan(svfalfaN)*(1+F_sh)) + if (azimuth > (90 - t)) and (azimuth <= (270 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziN * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) + else: + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + # Longwave from ground (see Lcyl_v2022a for remaining fluxes) + if anisotropic_longwave == 1: + Lground = LupN * 0.5 + Lnorth = Lground + else: + Lsky = ((svfN + svfNveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupN * 0.5 + Lrefl = (Ldown + LupN) * (viktrefl) * (1 - ewall) * 0.5 + Lnorth = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky + + return Least, Lsouth, Lwest, Lnorth + + +def Lside_veg_v2026( + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + azimuth, + altitude, + Ta, + Tw, + SBC, + ewall, + Ldown, + esky, + t, + F_sh, + CI, + anisotropic_longwave, +): + + # This m-file is the current one that estimates L from the four cardinal points 20100414 + + # Building height angle from svf + svfalfaE = np.arcsin(np.exp((np.log(1 - svfE)) / 2)) + svfalfaS = np.arcsin(np.exp((np.log(1 - svfS)) / 2)) + svfalfaW = np.arcsin(np.exp((np.log(1 - svfW)) / 2)) + svfalfaN = np.arcsin(np.exp((np.log(1 - svfN)) / 2)) + + vikttot = 4.4897 + aziW = azimuth + t + aziN = azimuth - 90 + t + aziE = azimuth - 180 + t + aziS = azimuth - 270 + t + + F_sh = 2 * F_sh - 1 # (cylindric_wedge scaled 0-1) + + c = 1 - CI + Lsky_allsky = esky * SBC * ((Ta + 273.15) ** 4) * (1 - c) + c * SBC * ( + (Ta + 273.15) ** 4 + ) + + ## Least + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfE, svfEveg, svfEaveg, vikttot + ) + + if anisotropic_longwave == 1: + Least = np.zeros_like(Ldown) + Lnorth = np.zeros_like(Ldown) + Lwest = np.zeros_like(Ldown) + Lsouth = np.zeros_like(Ldown) + + return Least, Lsouth, Lwest, Lnorth + else: + if altitude > 0: # daytime + alfaB = np.arctan(svfalfaE) + betaB = np.arctan(np.tan((svfalfaE) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB + # betasun = np.arctan(0.5*np.tan(svfalfaE)*(1+F_sh)) #TODO This should be considered in future versions + if (azimuth > (180 - t)) and (azimuth <= (360 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziE * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) + else: + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + Lsky = ((svfE + svfEveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lrefl = Ldown * (viktrefl) * (1 - ewall) * 0.5 + Least = Lsky + Lwallsun + Lwallsh + Lveg + Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky + + ## Lsouth + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfS, svfSveg, svfSaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = np.arctan(svfalfaS) + betaB = np.arctan(np.tan((svfalfaS) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB + # betasun = np.arctan(0.5*np.tan(svfalfaS)*(1+F_sh)) + if (azimuth <= (90 - t)) or (azimuth > (270 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziS * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) + else: + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + Lsky = ((svfS + svfSveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lrefl = Ldown * (viktrefl) * (1 - ewall) * 0.5 + Lsouth = Lsky + Lwallsun + Lwallsh + Lveg + Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky + + ## Lwest + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfW, svfWveg, svfWaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = np.arctan(svfalfaW) + betaB = np.arctan(np.tan((svfalfaW) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB + # betasun = np.arctan(0.5*np.tan(svfalfaW)*(1+F_sh)) + if (azimuth > (360 - t)) or (azimuth <= (180 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziW * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) + else: + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + Lsky = ((svfW + svfWveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lrefl = Ldown * (viktrefl) * (1 - ewall) * 0.5 + Lwest = Lsky + Lwallsun + Lwallsh + Lveg + Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky + + ## Lnorth + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfN, svfNveg, svfNaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = np.arctan(svfalfaN) + betaB = np.arctan(np.tan((svfalfaN) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB + # betasun = np.arctan(0.5*np.tan(svfalfaN)*(1+F_sh)) + if (azimuth > (90 - t)) and (azimuth <= (270 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziN * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) + else: + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + Lsky = ((svfN + svfNveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lrefl = Ldown * (viktrefl) * (1 - ewall) * 0.5 + Lnorth = Lsky + Lwallsun + Lwallsh + Lveg + Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky + + return Least, Lsouth, Lwest, Lnorth diff --git a/functions/SOLWEIGpython/Lside_veg_v2015a.py b/functions/SOLWEIGpython/Lside_veg_v2015a.py index 3aecfb3..a9604b1 100644 --- a/functions/SOLWEIGpython/Lside_veg_v2015a.py +++ b/functions/SOLWEIGpython/Lside_veg_v2015a.py @@ -2,133 +2,208 @@ import numpy as np from .Lvikt_veg import Lvikt_veg -def Lside_veg_v2015a(svfS,svfW,svfN,svfE,svfEveg,svfSveg,svfWveg,svfNveg,svfEaveg,svfSaveg,svfWaveg,svfNaveg,azimuth,altitude,Ta,Tw,SBC,ewall,Ldown,esky,t,F_sh,CI,LupE,LupS,LupW,LupN): + +def Lside_veg_v2015a( + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + azimuth, + altitude, + Ta, + Tw, + SBC, + ewall, + Ldown, + esky, + t, + F_sh, + CI, + LupE, + LupS, + LupW, + LupN, +): # This m-file is the current one that estimates L from the four cardinal points 20100414 - - #Building height angle from svf - svfalfaE=np.arcsin(np.exp((np.log(1-svfE))/2)) - svfalfaS=np.arcsin(np.exp((np.log(1-svfS))/2)) - svfalfaW=np.arcsin(np.exp((np.log(1-svfW))/2)) - svfalfaN=np.arcsin(np.exp((np.log(1-svfN))/2)) - - vikttot=4.4897 - aziW=azimuth+t - aziN=azimuth-90+t - aziE=azimuth-180+t - aziS=azimuth-270+t - - F_sh = 2*F_sh-1 #(cylindric_wedge scaled 0-1) - - c=1-CI - Lsky_allsky = esky*SBC*((Ta+273.15)**4)*(1-c)+c*SBC*((Ta+273.15)**4) - + + # Building height angle from svf + svfalfaE = np.arcsin(np.exp((np.log(1 - svfE)) / 2)) + svfalfaS = np.arcsin(np.exp((np.log(1 - svfS)) / 2)) + svfalfaW = np.arcsin(np.exp((np.log(1 - svfW)) / 2)) + svfalfaN = np.arcsin(np.exp((np.log(1 - svfN)) / 2)) + + vikttot = 4.4897 + aziW = azimuth + t + aziN = azimuth - 90 + t + aziE = azimuth - 180 + t + aziS = azimuth - 270 + t + + F_sh = 2 * F_sh - 1 # (cylindric_wedge scaled 0-1) + + c = 1 - CI + Lsky_allsky = esky * SBC * ((Ta + 273.15) ** 4) * (1 - c) + c * SBC * ( + (Ta + 273.15) ** 4 + ) + ## Least - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg(svfE, svfEveg, svfEaveg, vikttot) - + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfE, svfEveg, svfEaveg, vikttot + ) + if altitude > 0: # daytime - alfaB=np.arctan(svfalfaE) - betaB=np.arctan(np.tan((svfalfaE)*F_sh)) - betasun=((alfaB-betaB)/2)+betaB + alfaB = np.arctan(svfalfaE) + betaB = np.arctan(np.tan((svfalfaE) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB # betasun = np.arctan(0.5*np.tan(svfalfaE)*(1+F_sh)) #TODO This should be considered in future versions - if (azimuth > (180-t)) and (azimuth <= (360-t)): - Lwallsun=SBC*ewall*((Ta+273.15+Tw*np.sin(aziE*(np.pi/180)))**4)*\ - viktwall*(1-F_sh)*np.cos(betasun)*0.5 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*F_sh*0.5 + if (azimuth > (180 - t)) and (azimuth <= (360 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziE * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) else: - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 - else: #nighttime - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 - - Lsky=((svfE+svfEveg-1)*Lsky_allsky)*viktsky*0.5 - Lveg=SBC*ewall*((Ta+273.15)**4)*viktveg*0.5 - Lground=LupE*0.5 - Lrefl=(Ldown+LupE)*(viktrefl)*(1-ewall)*0.5 - Least=Lsky+Lwallsun+Lwallsh+Lveg+Lground+Lrefl - + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + Lsky = ((svfE + svfEveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupE * 0.5 + Lrefl = (Ldown + LupE) * (viktrefl) * (1 - ewall) * 0.5 + Least = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky - + ## Lsouth - [viktveg,viktwall,viktsky,viktrefl]=Lvikt_veg(svfS,svfSveg,svfSaveg,vikttot) - - if altitude>0: # daytime - alfaB=np.arctan(svfalfaS) - betaB=np.arctan(np.tan((svfalfaS)*F_sh)) - betasun=((alfaB-betaB)/2)+betaB + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfS, svfSveg, svfSaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = np.arctan(svfalfaS) + betaB = np.arctan(np.tan((svfalfaS) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB # betasun = np.arctan(0.5*np.tan(svfalfaS)*(1+F_sh)) - if (azimuth <= (90-t)) or (azimuth > (270-t)): - Lwallsun=SBC*ewall*((Ta+273.15+Tw*np.sin(aziS*(np.pi/180)))**4)*\ - viktwall*(1-F_sh)*np.cos(betasun)*0.5 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*F_sh*0.5 + if (azimuth <= (90 - t)) or (azimuth > (270 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziS * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) else: - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 - else: #nighttime - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 - - Lsky=((svfS+svfSveg-1)*Lsky_allsky)*viktsky*0.5 - Lveg=SBC*ewall*((Ta+273.15)**4)*viktveg*0.5 - Lground=LupS*0.5 - Lrefl=(Ldown+LupS)*(viktrefl)*(1-ewall)*0.5 - Lsouth=Lsky+Lwallsun+Lwallsh+Lveg+Lground+Lrefl - + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + Lsky = ((svfS + svfSveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupS * 0.5 + Lrefl = (Ldown + LupS) * (viktrefl) * (1 - ewall) * 0.5 + Lsouth = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky - + ## Lwest - [viktveg,viktwall,viktsky,viktrefl]=Lvikt_veg(svfW,svfWveg,svfWaveg,vikttot) - - if altitude>0: # daytime - alfaB=np.arctan(svfalfaW) - betaB=np.arctan(np.tan((svfalfaW)*F_sh)) - betasun=((alfaB-betaB)/2)+betaB + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfW, svfWveg, svfWaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = np.arctan(svfalfaW) + betaB = np.arctan(np.tan((svfalfaW) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB # betasun = np.arctan(0.5*np.tan(svfalfaW)*(1+F_sh)) - if (azimuth > (360-t)) or (azimuth <= (180-t)): - Lwallsun=SBC*ewall*((Ta+273.15+Tw*np.sin(aziW*(np.pi/180)))**4)*\ - viktwall*(1-F_sh)*np.cos(betasun)*0.5 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*F_sh*0.5 + if (azimuth > (360 - t)) or (azimuth <= (180 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziW * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) else: - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 - else: #nighttime - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 - - Lsky=((svfW+svfWveg-1)*Lsky_allsky)*viktsky*0.5 - Lveg=SBC*ewall*((Ta+273.15)**4)*viktveg*0.5 - Lground=LupW*0.5 - Lrefl=(Ldown+LupW)*(viktrefl)*(1-ewall)*0.5 - Lwest=Lsky+Lwallsun+Lwallsh+Lveg+Lground+Lrefl - + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + Lsky = ((svfW + svfWveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupW * 0.5 + Lrefl = (Ldown + LupW) * (viktrefl) * (1 - ewall) * 0.5 + Lwest = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky - + ## Lnorth - [viktveg,viktwall,viktsky,viktrefl]=Lvikt_veg(svfN,svfNveg,svfNaveg,vikttot) - - if altitude>0: # daytime - alfaB=np.arctan(svfalfaN) - betaB=np.arctan(np.tan((svfalfaN)*F_sh)) - betasun=((alfaB-betaB)/2)+betaB + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfN, svfNveg, svfNaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = np.arctan(svfalfaN) + betaB = np.arctan(np.tan((svfalfaN) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB # betasun = np.arctan(0.5*np.tan(svfalfaN)*(1+F_sh)) - if (azimuth > (90-t)) and (azimuth <= (270-t)): - Lwallsun=SBC*ewall*((Ta+273.15+Tw*np.sin(aziN*(np.pi/180)))**4)*\ - viktwall*(1-F_sh)*np.cos(betasun)*0.5 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*F_sh*0.5 + if (azimuth > (90 - t)) and (azimuth <= (270 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziN * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) else: - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 - else: #nighttime - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 - - Lsky=((svfN+svfNveg-1)*Lsky_allsky)*viktsky*0.5 - Lveg=SBC*ewall*((Ta+273.15)**4)*viktveg*0.5 - Lground=LupN*0.5 - Lrefl=(Ldown+LupN)*(viktrefl)*(1-ewall)*0.5 - Lnorth=Lsky+Lwallsun+Lwallsh+Lveg+Lground+Lrefl + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + Lsky = ((svfN + svfNveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupN * 0.5 + Lrefl = (Ldown + LupN) * (viktrefl) * (1 - ewall) * 0.5 + Lnorth = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky - - return Least,Lsouth,Lwest,Lnorth \ No newline at end of file + + return Least, Lsouth, Lwest, Lnorth diff --git a/functions/SOLWEIGpython/Lside_veg_v2022a.py b/functions/SOLWEIGpython/Lside_veg_v2022a.py index eb25a03..836b692 100644 --- a/functions/SOLWEIGpython/Lside_veg_v2022a.py +++ b/functions/SOLWEIGpython/Lside_veg_v2022a.py @@ -2,153 +2,229 @@ import numpy as np from .Lvikt_veg import Lvikt_veg -def Lside_veg_v2022a(svfS,svfW,svfN,svfE,svfEveg,svfSveg,svfWveg,svfNveg,svfEaveg,svfSaveg,svfWaveg,svfNaveg,azimuth,altitude,Ta,Tw,SBC,ewall,Ldown,esky,t,F_sh,CI,LupE,LupS,LupW,LupN,anisotropic_longwave): + +def Lside_veg_v2022a( + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + azimuth, + altitude, + Ta, + Tw, + SBC, + ewall, + Ldown, + esky, + t, + F_sh, + CI, + LupE, + LupS, + LupW, + LupN, + anisotropic_longwave, +): # This m-file is the current one that estimates L from the four cardinal points 20100414 - - #Building height angle from svf - svfalfaE=np.arcsin(np.exp((np.log(1-svfE))/2)) - svfalfaS=np.arcsin(np.exp((np.log(1-svfS))/2)) - svfalfaW=np.arcsin(np.exp((np.log(1-svfW))/2)) - svfalfaN=np.arcsin(np.exp((np.log(1-svfN))/2)) - - vikttot=4.4897 - aziW=azimuth+t - aziN=azimuth-90+t - aziE=azimuth-180+t - aziS=azimuth-270+t - - F_sh = 2*F_sh-1 #(cylindric_wedge scaled 0-1) - - c=1-CI - Lsky_allsky = esky*SBC*((Ta+273.15)**4)*(1-c)+c*SBC*((Ta+273.15)**4) - + + # Building height angle from svf + svfalfaE = np.arcsin(np.exp((np.log(1 - svfE)) / 2)) + svfalfaS = np.arcsin(np.exp((np.log(1 - svfS)) / 2)) + svfalfaW = np.arcsin(np.exp((np.log(1 - svfW)) / 2)) + svfalfaN = np.arcsin(np.exp((np.log(1 - svfN)) / 2)) + + vikttot = 4.4897 + aziW = azimuth + t + aziN = azimuth - 90 + t + aziE = azimuth - 180 + t + aziS = azimuth - 270 + t + + F_sh = 2 * F_sh - 1 # (cylindric_wedge scaled 0-1) + + c = 1 - CI + Lsky_allsky = esky * SBC * ((Ta + 273.15) ** 4) * (1 - c) + c * SBC * ( + (Ta + 273.15) ** 4 + ) + ## Least - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg(svfE, svfEveg, svfEaveg, vikttot) - + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfE, svfEveg, svfEaveg, vikttot + ) + if altitude > 0: # daytime - alfaB=np.arctan(svfalfaE) - betaB=np.arctan(np.tan((svfalfaE)*F_sh)) - betasun=((alfaB-betaB)/2)+betaB + alfaB = np.arctan(svfalfaE) + betaB = np.arctan(np.tan((svfalfaE) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB # betasun = np.arctan(0.5*np.tan(svfalfaE)*(1+F_sh)) #TODO This should be considered in future versions - if (azimuth > (180-t)) and (azimuth <= (360-t)): - Lwallsun=SBC*ewall*((Ta+273.15+Tw*np.sin(aziE*(np.pi/180)))**4)*\ - viktwall*(1-F_sh)*np.cos(betasun)*0.5 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*F_sh*0.5 + if (azimuth > (180 - t)) and (azimuth <= (360 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziE * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) else: - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 - else: #nighttime - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 - + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + # Longwave from ground (see Lcyl_v2022a for remaining fluxes) if anisotropic_longwave == 1: - Lground=LupE*0.5 - Least=Lground + Lground = LupE * 0.5 + Least = Lground else: - Lsky=((svfE+svfEveg-1)*Lsky_allsky)*viktsky*0.5 - Lveg=SBC*ewall*((Ta+273.15)**4)*viktveg*0.5 - Lground=LupE*0.5 - Lrefl=(Ldown+LupE)*(viktrefl)*(1-ewall)*0.5 - Least=Lsky+Lwallsun+Lwallsh+Lveg+Lground+Lrefl + Lsky = ((svfE + svfEveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupE * 0.5 + Lrefl = (Ldown + LupE) * (viktrefl) * (1 - ewall) * 0.5 + Least = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky - + ## Lsouth - [viktveg,viktwall,viktsky,viktrefl]=Lvikt_veg(svfS,svfSveg,svfSaveg,vikttot) - - if altitude>0: # daytime - alfaB=np.arctan(svfalfaS) - betaB=np.arctan(np.tan((svfalfaS)*F_sh)) - betasun=((alfaB-betaB)/2)+betaB + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfS, svfSveg, svfSaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = np.arctan(svfalfaS) + betaB = np.arctan(np.tan((svfalfaS) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB # betasun = np.arctan(0.5*np.tan(svfalfaS)*(1+F_sh)) - if (azimuth <= (90-t)) or (azimuth > (270-t)): - Lwallsun=SBC*ewall*((Ta+273.15+Tw*np.sin(aziS*(np.pi/180)))**4)*\ - viktwall*(1-F_sh)*np.cos(betasun)*0.5 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*F_sh*0.5 + if (azimuth <= (90 - t)) or (azimuth > (270 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziS * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) else: - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 - else: #nighttime - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 # Longwave from ground (see Lcyl_v2022a for remaining fluxes) if anisotropic_longwave == 1: - Lground=LupS*0.5 - Lsouth=Lground + Lground = LupS * 0.5 + Lsouth = Lground else: - Lsky=((svfS+svfSveg-1)*Lsky_allsky)*viktsky*0.5 - Lveg=SBC*ewall*((Ta+273.15)**4)*viktveg*0.5 - Lground=LupS*0.5 - Lrefl=(Ldown+LupS)*(viktrefl)*(1-ewall)*0.5 - Lsouth=Lsky+Lwallsun+Lwallsh+Lveg+Lground+Lrefl + Lsky = ((svfS + svfSveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupS * 0.5 + Lrefl = (Ldown + LupS) * (viktrefl) * (1 - ewall) * 0.5 + Lsouth = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky - + ## Lwest - [viktveg,viktwall,viktsky,viktrefl]=Lvikt_veg(svfW,svfWveg,svfWaveg,vikttot) - - if altitude>0: # daytime - alfaB=np.arctan(svfalfaW) - betaB=np.arctan(np.tan((svfalfaW)*F_sh)) - betasun=((alfaB-betaB)/2)+betaB + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfW, svfWveg, svfWaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = np.arctan(svfalfaW) + betaB = np.arctan(np.tan((svfalfaW) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB # betasun = np.arctan(0.5*np.tan(svfalfaW)*(1+F_sh)) - if (azimuth > (360-t)) or (azimuth <= (180-t)): - Lwallsun=SBC*ewall*((Ta+273.15+Tw*np.sin(aziW*(np.pi/180)))**4)*\ - viktwall*(1-F_sh)*np.cos(betasun)*0.5 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*F_sh*0.5 + if (azimuth > (360 - t)) or (azimuth <= (180 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziW * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) else: - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 - else: #nighttime - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 # Longwave from ground (see Lcyl_v2022a for remaining fluxes) if anisotropic_longwave == 1: - Lground=LupW*0.5 - Lwest=Lground + Lground = LupW * 0.5 + Lwest = Lground else: - Lsky=((svfW+svfWveg-1)*Lsky_allsky)*viktsky*0.5 - Lveg=SBC*ewall*((Ta+273.15)**4)*viktveg*0.5 - Lground=LupW*0.5 - Lrefl=(Ldown+LupW)*(viktrefl)*(1-ewall)*0.5 - Lwest=Lsky+Lwallsun+Lwallsh+Lveg+Lground+Lrefl + Lsky = ((svfW + svfWveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupW * 0.5 + Lrefl = (Ldown + LupW) * (viktrefl) * (1 - ewall) * 0.5 + Lwest = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky - + ## Lnorth - [viktveg,viktwall,viktsky,viktrefl]=Lvikt_veg(svfN,svfNveg,svfNaveg,vikttot) - - if altitude>0: # daytime - alfaB=np.arctan(svfalfaN) - betaB=np.arctan(np.tan((svfalfaN)*F_sh)) - betasun=((alfaB-betaB)/2)+betaB + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfN, svfNveg, svfNaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = np.arctan(svfalfaN) + betaB = np.arctan(np.tan((svfalfaN) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB # betasun = np.arctan(0.5*np.tan(svfalfaN)*(1+F_sh)) - if (azimuth > (90-t)) and (azimuth <= (270-t)): - Lwallsun=SBC*ewall*((Ta+273.15+Tw*np.sin(aziN*(np.pi/180)))**4)*\ - viktwall*(1-F_sh)*np.cos(betasun)*0.5 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*F_sh*0.5 + if (azimuth > (90 - t)) and (azimuth <= (270 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziN * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) else: - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 - else: #nighttime - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 # Longwave from ground (see Lcyl_v2022a for remaining fluxes) if anisotropic_longwave == 1: - Lground=LupN*0.5 - Lnorth=Lground + Lground = LupN * 0.5 + Lnorth = Lground else: - Lsky=((svfN+svfNveg-1)*Lsky_allsky)*viktsky*0.5 - Lveg=SBC*ewall*((Ta+273.15)**4)*viktveg*0.5 - Lground=LupN*0.5 - Lrefl=(Ldown+LupN)*(viktrefl)*(1-ewall)*0.5 - Lnorth=Lsky+Lwallsun+Lwallsh+Lveg+Lground+Lrefl + Lsky = ((svfN + svfNveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupN * 0.5 + Lrefl = (Ldown + LupN) * (viktrefl) * (1 - ewall) * 0.5 + Lnorth = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky - - return Least, Lsouth, Lwest, Lnorth \ No newline at end of file + + return Least, Lsouth, Lwest, Lnorth diff --git a/functions/SOLWEIGpython/Lvikt_veg.py b/functions/SOLWEIGpython/Lvikt_veg.py index 56edc03..8ca101c 100644 --- a/functions/SOLWEIGpython/Lvikt_veg.py +++ b/functions/SOLWEIGpython/Lvikt_veg.py @@ -1,16 +1,63 @@ -def Lvikt_veg(svf,svfveg,svfaveg,vikttot): +def Lvikt_veg(svf, svfveg, svfaveg, vikttot): # Least - viktonlywall=(vikttot-(63.227*svf**6-161.51*svf**5+156.91*svf**4-70.424*svf**3+16.773*svf**2-0.4863*svf))/vikttot + viktonlywall = ( + vikttot + - ( + 63.227 * svf**6 + - 161.51 * svf**5 + + 156.91 * svf**4 + - 70.424 * svf**3 + + 16.773 * svf**2 + - 0.4863 * svf + ) + ) / vikttot - viktaveg=(vikttot-(63.227*svfaveg**6-161.51*svfaveg**5+156.91*svfaveg**4-70.424*svfaveg**3+16.773*svfaveg**2-0.4863*svfaveg))/vikttot + viktaveg = ( + vikttot + - ( + 63.227 * svfaveg**6 + - 161.51 * svfaveg**5 + + 156.91 * svfaveg**4 + - 70.424 * svfaveg**3 + + 16.773 * svfaveg**2 + - 0.4863 * svfaveg + ) + ) / vikttot - viktwall=viktonlywall-viktaveg + viktwall = viktonlywall - viktaveg - svfvegbu=(svfveg+svf-1) # Vegetation plus buildings - viktsky=(63.227*svfvegbu**6-161.51*svfvegbu**5+156.91*svfvegbu**4-70.424*svfvegbu**3+16.773*svfvegbu**2-0.4863*svfvegbu)/vikttot - viktrefl=(vikttot-(63.227*svfvegbu**6-161.51*svfvegbu**5+156.91*svfvegbu**4-70.424*svfvegbu**3+16.773*svfvegbu**2-0.4863*svfvegbu))/vikttot - viktveg=(vikttot-(63.227*svfvegbu**6-161.51*svfvegbu**5+156.91*svfvegbu**4-70.424*svfvegbu**3+16.773*svfvegbu**2-0.4863*svfvegbu))/vikttot - viktveg=viktveg-viktwall + svfvegbu = svfveg + svf - 1 # Vegetation plus buildings + viktsky = ( + 63.227 * svfvegbu**6 + - 161.51 * svfvegbu**5 + + 156.91 * svfvegbu**4 + - 70.424 * svfvegbu**3 + + 16.773 * svfvegbu**2 + - 0.4863 * svfvegbu + ) / vikttot + viktrefl = ( + vikttot + - ( + 63.227 * svfvegbu**6 + - 161.51 * svfvegbu**5 + + 156.91 * svfvegbu**4 + - 70.424 * svfvegbu**3 + + 16.773 * svfvegbu**2 + - 0.4863 * svfvegbu + ) + ) / vikttot + viktveg = ( + vikttot + - ( + 63.227 * svfvegbu**6 + - 161.51 * svfvegbu**5 + + 156.91 * svfvegbu**4 + - 70.424 * svfvegbu**3 + + 16.773 * svfvegbu**2 + - 0.4863 * svfvegbu + ) + ) / vikttot + viktveg = viktveg - viktwall - return viktveg,viktwall,viktsky,viktrefl \ No newline at end of file + return viktveg, viktwall, viktsky, viktrefl diff --git a/functions/SOLWEIGpython/PET_calculations.py b/functions/SOLWEIGpython/PET_calculations.py index 234a0f5..73dd3c1 100644 --- a/functions/SOLWEIGpython/PET_calculations.py +++ b/functions/SOLWEIGpython/PET_calculations.py @@ -3,30 +3,42 @@ import numpy as np from collections import namedtuple -PET_person=namedtuple("PET_person","mbody age height activity sex clo") +PET_person = namedtuple("PET_person", "mbody age height activity sex clo") def calculate_PET_index(Ta, Pa, Tmrt, va, pet): - mbody=pet.mbody - age=pet.age - height=pet.height - activity=pet.activity - sex=pet.sex - clo=pet.clo - pet_index=np.zeros_like(Tmrt) + mbody = pet.mbody + age = pet.age + height = pet.height + activity = pet.activity + sex = pet.sex + clo = pet.clo + pet_index = np.zeros_like(Tmrt) for y in range(pet_index.shape[0]): for x in range(pet_index.shape[1]): - pet_index[y,y]=_PET(Ta[x],Pa[x],Tmrt[x][y],va[x][y],mbody,age,height,activity,clo,sex) + pet_index[y, y] = _PET( + Ta[x], + Pa[x], + Tmrt[x][y], + va[x][y], + mbody, + age, + height, + activity, + clo, + sex, + ) + def calculate_PET_grid(Ta, RH, Tmrt, va, pet, feedback): - mbody=pet.mbody - age=pet.age - height=pet.height - activity=pet.activity - sex=pet.sex - clo=pet.clo - pet_index=np.zeros_like(Tmrt) - total = 100. / (int(pet_index.shape[0] * pet_index.shape[1])) + mbody = pet.mbody + age = pet.age + height = pet.height + activity = pet.activity + sex = pet.sex + clo = pet.clo + pet_index = np.zeros_like(Tmrt) + total = 100.0 / (int(pet_index.shape[0] * pet_index.shape[1])) index = 0 # print(Tmrt.shape) # print(va.shape) @@ -38,23 +50,36 @@ def calculate_PET_grid(Ta, RH, Tmrt, va, pet, feedback): if va[y, x] > 0: index = index + 1 feedback.setProgress(int(index * total)) - pet_index[y,x]=_PET(Ta,RH,Tmrt[y,x],va[y,x],mbody,age,height,activity,clo,sex) + pet_index[y, x] = _PET( + Ta, + RH, + Tmrt[y, x], + va[y, x], + mbody, + age, + height, + activity, + clo, + sex, + ) else: pet_index[y, x] = -9999 return pet_index -def calculate_PET_index_vec(Ta, Pa, Tmrt, va,pet): - mbody=pet.mbody - age=pet.age - height=pet.height - activity=pet.activity - sex=pet.sex - clo=pet.clo - pet_index=_PET(Ta,Pa,Tmrt,va,mbody,age,height,activity,clo,sex) +def calculate_PET_index_vec(Ta, Pa, Tmrt, va, pet): + mbody = pet.mbody + age = pet.age + height = pet.height + activity = pet.activity + sex = pet.sex + clo = pet.clo + + pet_index = _PET(Ta, Pa, Tmrt, va, mbody, age, height, activity, clo, sex) + -def _PET(ta,RH,tmrt,v,mbody,age,ht,work,icl,sex): +def _PET(ta, RH, tmrt, v, mbody, age, ht, work, icl, sex): """ Args: ta: air temperature @@ -71,7 +96,7 @@ def _PET(ta,RH,tmrt,v,mbody,age,ht,work,icl,sex): """ # humidity conversion - vps = 6.107 * (10. ** (7.5 * ta / (238. + ta))) + vps = 6.107 * (10.0 ** (7.5 * ta / (238.0 + ta))) vpa = RH * vps / 100 # water vapour presure, kPa po = 1013.25 # Pressure @@ -87,21 +112,37 @@ def _PET(ta,RH,tmrt,v,mbody,age,ht,work,icl,sex): eta = 0 # No idea what eta is - c_1 = 0. - c_2 = 0. - c_3 = 0. - c_4 = 0. - c_5 = 0. - c_6 = 0. - c_7 = 0. - c_8 = 0. - c_9 = 0. - c_10 = 0. - c_11 = 0. + c_1 = 0.0 + c_2 = 0.0 + c_3 = 0.0 + c_4 = 0.0 + c_5 = 0.0 + c_6 = 0.0 + c_7 = 0.0 + c_8 = 0.0 + c_9 = 0.0 + c_10 = 0.0 + c_11 = 0.0 # INBODY - metbf = 3.19 * mbody ** (3 / 4) * (1 + 0.004 * (30 - age) + 0.018 * ((ht * 100 / (mbody ** (1 / 3))) - 42.1)) - metbm = 3.45 * mbody ** (3 / 4) * (1 + 0.004 * (30 - age) + 0.010 * ((ht * 100 / (mbody ** (1 / 3))) - 43.4)) + metbf = ( + 3.19 + * mbody ** (3 / 4) + * ( + 1 + + 0.004 * (30 - age) + + 0.018 * ((ht * 100 / (mbody ** (1 / 3))) - 42.1) + ) + ) + metbm = ( + 3.45 + * mbody ** (3 / 4) + * ( + 1 + + 0.004 * (30 - age) + + 0.010 * ((ht * 100 / (mbody ** (1 / 3))) - 43.4) + ) + ) if sex == 1: met = metbm + work else: @@ -122,8 +163,8 @@ def _PET(ta,RH,tmrt,v,mbody,age,ht,work,icl,sex): # calcul constants feff = 0.725 - adu = 0.203 * mbody ** 0.425 * ht ** 0.725 - facl = (-2.36 + 173.51 * icl - 100.76 * icl * icl + 19.28 * (icl ** 3)) / 100 + adu = 0.203 * mbody**0.425 * ht**0.725 + facl = (-2.36 + 173.51 * icl - 100.76 * icl * icl + 19.28 * (icl**3)) / 100 if facl > 1: facl = 1 rcl = (icl / 6.45) / facl @@ -131,14 +172,14 @@ def _PET(ta,RH,tmrt,v,mbody,age,ht,work,icl,sex): # should these be else if statements? if icl < 2: - y = (ht-0.2) / ht + y = (ht - 0.2) / ht if icl <= 0.6: y = 0.5 if icl <= 0.3: y = 0.1 fcl = 1 + 0.15 * icl - r2 = adu * (fcl - 1. + facl) / (2 * 3.14 * ht * y) + r2 = adu * (fcl - 1.0 + facl) / (2 * 3.14 * ht * y) r1 = facl * adu / (2 * 3.14 * ht * y) di = r2 - r1 acl = adu * facl + adu * (fcl - 1) @@ -146,7 +187,7 @@ def _PET(ta,RH,tmrt,v,mbody,age,ht,work,icl,sex): tcore = [0] * 8 wetsk = 0 - hc = 2.67 + 6.5 * v ** 0.67 + hc = 2.67 + 6.5 * v**0.67 hc = hc * (p / po) ** 0.55 c_1 = h + ere he = 0.633 * hc / (p * cair) @@ -156,7 +197,7 @@ def _PET(ta,RH,tmrt,v,mbody,age,ht,work,icl,sex): c_2 = adu * rob * cb c_5 = 0.0208 * c_2 c_6 = 0.76075 * c_2 - rdsk = 0.79 * 10 ** 7 + rdsk = 0.79 * 10**7 rdcl = 0 count2 = 0 @@ -171,15 +212,32 @@ def _PET(ta,RH,tmrt,v,mbody,age,ht,work,icl,sex): while count1 <= 3: enbal = 0 - while (enbal*enbal2) >= 0 and count3 < 200: + while (enbal * enbal2) >= 0 and count3 < 200: enbal2 = enbal # 20 - rclo2 = emcl * sigma * ((tcl + 273.2) ** 4 - (tmrt + 273.2) ** 4) * feff + rclo2 = ( + emcl + * sigma + * ((tcl + 273.2) ** 4 - (tmrt + 273.2) ** 4) + * feff + ) tsk = 1 / htcl * (hc * (tcl - ta) + rclo2) + tcl # radiation balance - rbare = aeff * (1 - facl) * emsk * sigma * ((tmrt + 273.2) ** 4 - (tsk + 273.2) ** 4) - rclo = feff * acl * emcl * sigma * ((tmrt + 273.2) ** 4 - (tcl + 273.2) ** 4) + rbare = ( + aeff + * (1 - facl) + * emsk + * sigma + * ((tmrt + 273.2) ** 4 - (tsk + 273.2) ** 4) + ) + rclo = ( + feff + * acl + * emcl + * sigma + * ((tmrt + 273.2) ** 4 - (tcl + 273.2) ** 4) + ) rsum = rbare + rclo # convection @@ -192,30 +250,39 @@ def _PET(ta,RH,tmrt,v,mbody,age,ht,work,icl,sex): c_4 = 5.28 * adu * c_3 c_7 = c_4 - c_6 - tsk * c_5 c_8 = -c_1 * c_3 - tsk * c_4 + tsk * c_6 - c_9 = c_7 * c_7 - 4. * c_5 * c_8 + c_9 = c_7 * c_7 - 4.0 * c_5 * c_8 c_10 = 5.28 * adu - c_6 - c_5 * tsk - c_11 = c_10 * c_10 - 4 * c_5 * (c_6 * tsk - c_1 - 5.28 * adu * tsk) + c_11 = c_10 * c_10 - 4 * c_5 * ( + c_6 * tsk - c_1 - 5.28 * adu * tsk + ) # tsk[tsk==36]=36.01 - #print(tsk.shape) + # print(tsk.shape) if tsk == 36: tsk = 36.01 tcore[7] = c_1 / (5.28 * adu + c_2 * 6.3 / 3600) + tsk - tcore[3] = c_1 / (5.28 * adu + (c_2 * 6.3 / 3600) / (1 + 0.5 * (34 - tsk))) + tsk + tcore[3] = ( + c_1 + / ( + 5.28 * adu + + (c_2 * 6.3 / 3600) / (1 + 0.5 * (34 - tsk)) + ) + + tsk + ) if c_11 >= 0: - tcore[6] = (-c_10-c_11 ** 0.5) / (2 * c_5) + tcore[6] = (-c_10 - c_11**0.5) / (2 * c_5) if c_11 >= 0: - tcore[1] = (-c_10+c_11 ** 0.5) / (2 * c_5) + tcore[1] = (-c_10 + c_11**0.5) / (2 * c_5) if c_9 >= 0: - tcore[2] = (-c_7+abs(c_9) ** 0.5) / (2 * c_5) + tcore[2] = (-c_7 + abs(c_9) ** 0.5) / (2 * c_5) if c_9 >= 0: - tcore[5] = (-c_7-abs(c_9) ** 0.5) / (2 * c_5) + tcore[5] = (-c_7 - abs(c_9) ** 0.5) / (2 * c_5) tcore[4] = c_1 / (5.28 * adu + c_2 * 1 / 40) + tsk # transpiration tbody = 0.1 * tsk + 0.9 * tcore[j] sw = 304.94 * (tbody - 36.6) * adu / 3600000 - vpts = 6.11 * 10 ** (7.45 * tsk / (235. + tsk)) + vpts = 6.11 * 10 ** (7.45 * tsk / (235.0 + tsk)) if tbody <= 36.6: sw = 0 if sex == 2: @@ -321,7 +388,7 @@ def _PET(ta,RH,tmrt,v,mbody,age,ht,work,icl,sex): count1 = 0 enbal = 0 - hc = 2.67 + 6.5 * 0.1 ** 0.67 + hc = 2.67 + 6.5 * 0.1**0.67 hc = hc * (p / po) ** 0.55 while count1 <= 3: @@ -329,8 +396,20 @@ def _PET(ta,RH,tmrt,v,mbody,age,ht,work,icl,sex): enbal2 = enbal # radiation balance - rbare = aeff * (1 - facl) * emsk * sigma * ((tx + 273.2) ** 4 - (tsk + 273.2) ** 4) - rclo = feff * acl * emcl * sigma * ((tx + 273.2) ** 4 - (tcl + 273.2) ** 4) + rbare = ( + aeff + * (1 - facl) + * emsk + * sigma + * ((tx + 273.2) ** 4 - (tsk + 273.2) ** 4) + ) + rclo = ( + feff + * acl + * emcl + * sigma + * ((tx + 273.2) ** 4 - (tcl + 273.2) ** 4) + ) rsum = rbare + rclo # convection @@ -367,4 +446,4 @@ def _PET(ta,RH,tmrt,v,mbody,age,ht,work,icl,sex): count1 = count1 + 1 enbal2 = 0 - return tx \ No newline at end of file + return tx diff --git a/functions/SOLWEIGpython/Perez_v3_moved.py b/functions/SOLWEIGpython/Perez_v3_moved.py index 30bccef..abbb2c4 100644 --- a/functions/SOLWEIGpython/Perez_v3_moved.py +++ b/functions/SOLWEIGpython/Perez_v3_moved.py @@ -6,22 +6,22 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): """ This function calculates distribution of luminance on the skyvault based on Perez luminince distribution model. - + Created by: Fredrik Lindberg 20120527, fredrikl@gvc.gu.se Gothenburg University, Sweden Urban Climte Group - + Input parameters: - zen: Zenith angle of the Sun (in degrees) - azimuth: Azimuth angle of the Sun (in degrees) - radD: Horizontal diffuse radiation (W m-2) - radI: Direct radiation perpendicular to the Sun beam (W m-2) - jday: Day of year - + Output parameters: - lv: Relative luminance map (same dimensions as theta. gamma) - + acoeff=[1.353 -0.258 -0.269 -1.437 -1.222 -0.773 1.415 1.102 @@ -31,7 +31,7 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): -1.016 -0.367 1.008 1.405 -1.000 0.021 0.503 -0.512 -1.050 0.029 0.426 0.359]; - + bcoeff=[-0.767 0.001 1.273 -0.123 -0.205 0.037 -3.913 0.916 0.278 -0.181 -4.500 1.177 @@ -40,7 +40,7 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): 0.288 -0.533 -3.850 3.375 -0.300 0.192 0.702 -1.632 -0.325 0.116 0.778 0.003]; - + ccoeff=[2.800 0.600 1.238 1.000 6.975 0.177 6.448 -0.124 24.22 -13.08 -37.70 34.84 @@ -49,7 +49,7 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): 14.00 -0.999 -7.14 7.547 19.00 -5.000 1.243 -1.91 31.06 -14.50 -46.11 55.37]; - + dcoeff=[1.874 0.630 0.974 0.281 -1.580 -0.508 -1.781 0.108 -5.00 1.522 3.923 -2.62 @@ -58,7 +58,7 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): -3.40 -0.108 -1.075 1.57 -4.00 0.025 0.384 0.266 -7.23 0.405 13.35 0.623]; - + ecoeff=[0.035 -0.125 -0.572 0.994 0.262 0.067 -0.219 -0.428 -0.016 0.160 0.420 -0.556 @@ -77,36 +77,94 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): :return: """ - m_a1 = np.array([1.3525, -1.2219, -1.1000, -0.5484, -0.6000, -1.0156, -1.0000, -1.0500]) - m_a2 = np.array([-0.2576, -0.7730, -0.2515, -0.6654, -0.3566, -0.3670, 0.0211, 0.0289]) - m_a3 = np.array([-0.2690, 1.4148, 0.8952, -0.2672, -2.5000, 1.0078, 0.5025, 0.4260]) - m_a4 = np.array([-1.4366, 1.1016, 0.0156, 0.7117, 2.3250, 1.4051, -0.5119, 0.3590]) - m_b1 = np.array([-0.7670, -0.2054, 0.2782, 0.7234, 0.2937, 0.2875, -0.3000, -0.3250]) - m_b2 = np.array([0.0007, 0.0367, -0.1812, -0.6219, 0.0496, -0.5328, 0.1922, 0.1156]) - m_b3 = np.array([1.2734, -3.9128, -4.5000, -5.6812, -5.6812, -3.8500, 0.7023, 0.7781]) - m_b4 = np.array([-0.1233, 0.9156, 1.1766, 2.6297, 1.8415, 3.3750, -1.6317, 0.0025]) - m_c1 = np.array([2.8000, 6.9750, 24.7219, 33.3389, 21.0000, 14.0000, 19.0000, 31.0625]) - m_c2 = np.array([0.6004, 0.1774, -13.0812, -18.3000, -4.7656, -0.9999, -5.0000, -14.5000]) - m_c3 = np.array([1.2375, 6.4477, -37.7000, -62.2500, -21.5906, -7.1406, 1.2438, -46.1148]) - m_c4 = np.array([1.0000, -0.1239, 34.8438, 52.0781, 7.2492, 7.5469, -1.9094, 55.3750]) - m_d1 = np.array([1.8734, -1.5798, -5.0000, -3.5000, -3.5000, -3.4000, -4.0000, -7.2312]) - m_d2 = np.array([0.6297, -0.5081, 1.5218, 0.0016, -0.1554, -0.1078, 0.0250, 0.4050]) - m_d3 = np.array([0.9738, -1.7812, 3.9229, 1.1477, 1.4062, -1.0750, 0.3844, 13.3500]) - m_d4 = np.array([0.2809, 0.1080, -2.6204, 0.1062, 0.3988, 1.5702, 0.2656, 0.6234]) - m_e1 = np.array([0.0356, 0.2624, -0.0156, 0.4659, 0.0032, -0.0672, 1.0468, 1.5000]) - m_e2 = np.array([-0.1246, 0.0672, 0.1597, -0.3296, 0.0766, 0.4016, -0.3788, -0.6426]) - m_e3 = np.array([-0.5718, -0.2190, 0.4199, -0.0876, -0.0656, 0.3017, -2.4517, 1.8564]) - m_e4 = np.array([0.9938, -0.4285, -0.5562, -0.0329, -0.1294, -0.4844, 1.4656, 0.5636]) - + m_a1 = np.array( + [1.3525, -1.2219, -1.1000, -0.5484, -0.6000, -1.0156, -1.0000, -1.0500] + ) + m_a2 = np.array( + [-0.2576, -0.7730, -0.2515, -0.6654, -0.3566, -0.3670, 0.0211, 0.0289] + ) + m_a3 = np.array( + [-0.2690, 1.4148, 0.8952, -0.2672, -2.5000, 1.0078, 0.5025, 0.4260] + ) + m_a4 = np.array( + [-1.4366, 1.1016, 0.0156, 0.7117, 2.3250, 1.4051, -0.5119, 0.3590] + ) + m_b1 = np.array( + [-0.7670, -0.2054, 0.2782, 0.7234, 0.2937, 0.2875, -0.3000, -0.3250] + ) + m_b2 = np.array( + [0.0007, 0.0367, -0.1812, -0.6219, 0.0496, -0.5328, 0.1922, 0.1156] + ) + m_b3 = np.array( + [1.2734, -3.9128, -4.5000, -5.6812, -5.6812, -3.8500, 0.7023, 0.7781] + ) + m_b4 = np.array( + [-0.1233, 0.9156, 1.1766, 2.6297, 1.8415, 3.3750, -1.6317, 0.0025] + ) + m_c1 = np.array( + [2.8000, 6.9750, 24.7219, 33.3389, 21.0000, 14.0000, 19.0000, 31.0625] + ) + m_c2 = np.array( + [ + 0.6004, + 0.1774, + -13.0812, + -18.3000, + -4.7656, + -0.9999, + -5.0000, + -14.5000, + ] + ) + m_c3 = np.array( + [ + 1.2375, + 6.4477, + -37.7000, + -62.2500, + -21.5906, + -7.1406, + 1.2438, + -46.1148, + ] + ) + m_c4 = np.array( + [1.0000, -0.1239, 34.8438, 52.0781, 7.2492, 7.5469, -1.9094, 55.3750] + ) + m_d1 = np.array( + [1.8734, -1.5798, -5.0000, -3.5000, -3.5000, -3.4000, -4.0000, -7.2312] + ) + m_d2 = np.array( + [0.6297, -0.5081, 1.5218, 0.0016, -0.1554, -0.1078, 0.0250, 0.4050] + ) + m_d3 = np.array( + [0.9738, -1.7812, 3.9229, 1.1477, 1.4062, -1.0750, 0.3844, 13.3500] + ) + m_d4 = np.array( + [0.2809, 0.1080, -2.6204, 0.1062, 0.3988, 1.5702, 0.2656, 0.6234] + ) + m_e1 = np.array( + [0.0356, 0.2624, -0.0156, 0.4659, 0.0032, -0.0672, 1.0468, 1.5000] + ) + m_e2 = np.array( + [-0.1246, 0.0672, 0.1597, -0.3296, 0.0766, 0.4016, -0.3788, -0.6426] + ) + m_e3 = np.array( + [-0.5718, -0.2190, 0.4199, -0.0876, -0.0656, 0.3017, -2.4517, 1.8564] + ) + m_e4 = np.array( + [0.9938, -0.4285, -0.5562, -0.0329, -0.1294, -0.4844, 1.4656, 0.5636] + ) + acoeff = np.transpose(np.atleast_2d([m_a1, m_a2, m_a3, m_a4])) bcoeff = np.transpose(np.atleast_2d([m_b1, m_b2, m_b3, m_b4])) ccoeff = np.transpose(np.atleast_2d([m_c1, m_c2, m_c3, m_c4])) dcoeff = np.transpose(np.atleast_2d([m_d1, m_d2, m_d3, m_d4])) ecoeff = np.transpose(np.atleast_2d([m_e1, m_e2, m_e3, m_e4])) - deg2rad = np.pi/180 - rad2deg = 180/np.pi - altitude = 90-zen + deg2rad = np.pi / 180 + rad2deg = 180 / np.pi + altitude = 90 - zen zen = zen * deg2rad azimuth = azimuth * deg2rad altitude = altitude * deg2rad @@ -115,32 +173,43 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): Ibn = radI # Skyclearness - PerezClearness = ((Idh+Ibn)/(Idh+1.041*np.power(zen, 3)))/(1+1.041*np.power(zen, 3)) + PerezClearness = ((Idh + Ibn) / (Idh + 1.041 * np.power(zen, 3))) / ( + 1 + 1.041 * np.power(zen, 3) + ) # Extra terrestrial radiation - day_angle = jday*2*np.pi/365 - #I0=1367*(1+0.033*np.cos((2*np.pi*jday)/365)) - I0 = 1367*(1.00011+0.034221*np.cos(day_angle) + 0.00128*np.sin(day_angle)+0.000719 * - np.cos(2*day_angle)+0.000077*np.sin(2*day_angle)) # New from robinsson + day_angle = jday * 2 * np.pi / 365 + # I0=1367*(1+0.033*np.cos((2*np.pi*jday)/365)) + I0 = 1367 * ( + 1.00011 + + 0.034221 * np.cos(day_angle) + + 0.00128 * np.sin(day_angle) + + 0.000719 * np.cos(2 * day_angle) + + 0.000077 * np.sin(2 * day_angle) + ) # New from robinsson # Optical air mass # m=1/altitude; old - if altitude >= 10*deg2rad: - AirMass = 1/np.sin(altitude) - elif altitude < 0: # below equation becomes complex - AirMass = 1/np.sin(altitude)+0.50572*np.power(180*complex(altitude)/np.pi+6.07995, -1.6364) + if altitude >= 10 * deg2rad: + AirMass = 1 / np.sin(altitude) + elif altitude < 0: # below equation becomes complex + AirMass = 1 / np.sin(altitude) + 0.50572 * np.power( + 180 * complex(altitude) / np.pi + 6.07995, -1.6364 + ) else: - AirMass = 1/np.sin(altitude)+0.50572*np.power(180*altitude/np.pi+6.07995, -1.6364) + AirMass = 1 / np.sin(altitude) + 0.50572 * np.power( + 180 * altitude / np.pi + 6.07995, -1.6364 + ) # Skybrightness # if altitude*rad2deg+6.07995>=0 - PerezBrightness = (AirMass*radD)/I0 + PerezBrightness = (AirMass * radD) / I0 if Idh <= 10: # m_a=0;m_b=0;m_c=0;m_d=0;m_e=0; PerezBrightness = 0 - #if altitude < 0: - #print("Airmass") - #print(AirMass) - #print(PerezBrightness) + # if altitude < 0: + # print("Airmass") + # print(AirMass) + # print(PerezBrightness) # sky clearness bins if PerezClearness < 1.065: intClearness = 0 @@ -159,18 +228,60 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): if PerezClearness > 6.200: intClearness = 7 - m_a = acoeff[intClearness, 0] + acoeff[intClearness, 1] * zen + PerezBrightness * (acoeff[intClearness, 2] + acoeff[intClearness, 3] * zen) - m_b = bcoeff[intClearness, 0] + bcoeff[intClearness, 1] * zen + PerezBrightness * (bcoeff[intClearness, 2] + bcoeff[intClearness, 3] * zen) - m_e = ecoeff[intClearness, 0] + ecoeff[intClearness, 1] * zen + PerezBrightness * (ecoeff[intClearness, 2] + ecoeff[intClearness, 3] * zen) + m_a = ( + acoeff[intClearness, 0] + + acoeff[intClearness, 1] * zen + + PerezBrightness + * (acoeff[intClearness, 2] + acoeff[intClearness, 3] * zen) + ) + m_b = ( + bcoeff[intClearness, 0] + + bcoeff[intClearness, 1] * zen + + PerezBrightness + * (bcoeff[intClearness, 2] + bcoeff[intClearness, 3] * zen) + ) + m_e = ( + ecoeff[intClearness, 0] + + ecoeff[intClearness, 1] * zen + + PerezBrightness + * (ecoeff[intClearness, 2] + ecoeff[intClearness, 3] * zen) + ) if intClearness > 0: - m_c = ccoeff[intClearness, 0] + ccoeff[intClearness, 1] * zen + PerezBrightness * (ccoeff[intClearness, 2] + ccoeff[intClearness, 3] * zen) - m_d = dcoeff[intClearness, 0] + dcoeff[intClearness, 1] * zen + PerezBrightness * (dcoeff[intClearness, 2] + dcoeff[intClearness, 3] * zen) + m_c = ( + ccoeff[intClearness, 0] + + ccoeff[intClearness, 1] * zen + + PerezBrightness + * (ccoeff[intClearness, 2] + ccoeff[intClearness, 3] * zen) + ) + m_d = ( + dcoeff[intClearness, 0] + + dcoeff[intClearness, 1] * zen + + PerezBrightness + * (dcoeff[intClearness, 2] + dcoeff[intClearness, 3] * zen) + ) else: # different equations for c & d in clearness bin no. 1, from Robinsson - m_c = np.exp(np.power(PerezBrightness * (ccoeff[intClearness, 0] + ccoeff[intClearness, 1] * zen), ccoeff[intClearness, 2]))-1 - m_d = -np.exp(PerezBrightness * (dcoeff[intClearness, 0] + dcoeff[intClearness, 1] * zen)) + dcoeff[intClearness, 2] + \ - PerezBrightness * dcoeff[intClearness, 3] * PerezBrightness + m_c = ( + np.exp( + np.power( + PerezBrightness + * ( + ccoeff[intClearness, 0] + ccoeff[intClearness, 1] * zen + ), + ccoeff[intClearness, 2], + ) + ) + - 1 + ) + m_d = ( + -np.exp( + PerezBrightness + * (dcoeff[intClearness, 0] + dcoeff[intClearness, 1] * zen) + ) + + dcoeff[intClearness, 2] + + PerezBrightness * dcoeff[intClearness, 3] * PerezBrightness + ) # print 'a = ', m_a # print 'b = ', m_b @@ -182,20 +293,20 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): skyvaultazi = np.atleast_2d([]) if patchchoice == 2: # Creating skyvault at one degree intervals - skyvaultalt = np.ones([90, 361])*90 + skyvaultalt = np.ones([90, 361]) * 90 skyvaultazi = np.empty((90, 361)) for j in range(90): - skyvaultalt[j, :] = 91-j + skyvaultalt[j, :] = 91 - j skyvaultazi[j, :] = range(361) - + elif patchchoice == 1: # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) skyvaultaltint = [6, 18, 30, 42, 54, 66, 78] skyvaultaziint = [12, 12, 15, 15, 20, 30, 60] for j in range(7): - for k in range(1, int(360/skyvaultaziint[j]) + 1): + for k in range(1, int(360 / skyvaultaziint[j]) + 1): skyvaultalt = np.append(skyvaultalt, skyvaultaltint[j]) - skyvaultazi = np.append(skyvaultazi, k*skyvaultaziint[j]) + skyvaultazi = np.append(skyvaultazi, k * skyvaultaziint[j]) skyvaultalt = np.append(skyvaultalt, 90) skyvaultazi = np.append(skyvaultazi, 360) @@ -205,12 +316,18 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): skyvaultazi = skyvaultazi * deg2rad # Angular distance from the sun from Robinsson - cosSkySunAngle = np.sin(skyvaultalt) * np.sin(altitude) + \ - np.cos(altitude) * np.cos(skyvaultalt) * np.cos(np.abs(skyvaultazi-azimuth)) + cosSkySunAngle = np.sin(skyvaultalt) * np.sin(altitude) + np.cos( + altitude + ) * np.cos(skyvaultalt) * np.cos(np.abs(skyvaultazi - azimuth)) # Main equation - lv = (1 + m_a * np.exp(m_b / np.cos(skyvaultzen))) * ((1 + m_c * np.exp(m_d * np.arccos(cosSkySunAngle)) + - m_e * cosSkySunAngle * cosSkySunAngle)) + lv = (1 + m_a * np.exp(m_b / np.cos(skyvaultzen))) * ( + ( + 1 + + m_c * np.exp(m_d * np.arccos(cosSkySunAngle)) + + m_e * cosSkySunAngle * cosSkySunAngle + ) + ) # Normalisation lv = lv / np.sum(lv) @@ -225,10 +342,10 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): # pause(1) if patchchoice == 1: - #x = np.atleast_2d([]) - #lv = np.transpose(np.append(np.append(np.append(x, skyvaultalt*rad2deg), skyvaultazi*rad2deg), lv)) - x = np.transpose(np.atleast_2d(skyvaultalt*rad2deg)) - y = np.transpose(np.atleast_2d(skyvaultazi*rad2deg)) + # x = np.atleast_2d([]) + # lv = np.transpose(np.append(np.append(np.append(x, skyvaultalt*rad2deg), skyvaultazi*rad2deg), lv)) + x = np.transpose(np.atleast_2d(skyvaultalt * rad2deg)) + y = np.transpose(np.atleast_2d(skyvaultazi * rad2deg)) z = np.transpose(np.atleast_2d(lv)) lv = np.append(np.append(x, y, axis=1), z, axis=1) - return lv, PerezClearness, PerezBrightness \ No newline at end of file + return lv, PerezClearness, PerezBrightness diff --git a/functions/SOLWEIGpython/Solweig_2021a_calc_forprocessing.py b/functions/SOLWEIGpython/Solweig_2021a_calc_forprocessing.py index 8a089c4..74fbd47 100644 --- a/functions/SOLWEIGpython/Solweig_2021a_calc_forprocessing.py +++ b/functions/SOLWEIGpython/Solweig_2021a_calc_forprocessing.py @@ -2,10 +2,16 @@ import numpy as np from .daylen import daylen -from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import clearnessindex_2013b +from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import ( + clearnessindex_2013b, +) from ...util.SEBESOLWEIGCommonFiles.diffusefraction import diffusefraction -from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_13 import shadowingfunction_wallheight_13 -from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_23 import shadowingfunction_wallheight_23 +from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_13 import ( + shadowingfunction_wallheight_13, +) +from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_23 import ( + shadowingfunction_wallheight_23, +) from .gvf_2018a import gvf_2018a from .cylindric_wedge import cylindric_wedge from .TsWaveDelay_2015a import TsWaveDelay_2015a @@ -14,13 +20,88 @@ from .Kside_veg_v2019a import Kside_veg_v2019a from ...util.SEBESOLWEIGCommonFiles.Perez_v3 import Perez_v3 -def Solweig_2021a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, svfveg, svfNveg, svfEveg, svfSveg, - svfWveg, svfaveg, svfEaveg, svfSaveg, svfWaveg, svfNaveg, vegdem, vegdem2, albedo_b, absK, absL, - ewall, Fside, Fup, Fcyl, altitude, azimuth, zen, jday, usevegdem, onlyglobal, buildings, location, psi, - landcover, lc_grid, dectime, altmax, dirwalls, walls, cyl, elvis, Ta, RH, radG, radD, radI, P, - amaxvalue, bush, Twater, TgK, Tstart, alb_grid, emis_grid, TgK_wall, Tstart_wall, TmaxLST, - TmaxLST_wall, first, second, svfalfa, svfbuveg, firstdaytime, timeadd, timestepdec, Tgmap1, - Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, CI, TgOut1, diffsh, ani): + +def Solweig_2021a_calc( + i, + dsm, + scale, + rows, + cols, + svf, + svfN, + svfW, + svfE, + svfS, + svfveg, + svfNveg, + svfEveg, + svfSveg, + svfWveg, + svfaveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + vegdem, + vegdem2, + albedo_b, + absK, + absL, + ewall, + Fside, + Fup, + Fcyl, + altitude, + azimuth, + zen, + jday, + usevegdem, + onlyglobal, + buildings, + location, + psi, + landcover, + lc_grid, + dectime, + altmax, + dirwalls, + walls, + cyl, + elvis, + Ta, + RH, + radG, + radD, + radI, + P, + amaxvalue, + bush, + Twater, + TgK, + Tstart, + alb_grid, + emis_grid, + TgK_wall, + Tstart_wall, + TmaxLST, + TmaxLST_wall, + first, + second, + svfalfa, + svfbuveg, + firstdaytime, + timeadd, + timestepdec, + Tgmap1, + Tgmap1E, + Tgmap1S, + Tgmap1W, + Tgmap1N, + CI, + TgOut1, + diffsh, + ani, +): # This is the core function of the SOLWEIG model # 2016-Aug-28 @@ -68,31 +149,37 @@ def Solweig_2021a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, s # # # Core program start # # # # Instrument offset in degrees - t = 0. + t = 0.0 # Stefan Bolzmans Constant SBC = 5.67051e-8 # Find sunrise decimal hour - new from 2014a - _, _, _, SNUP = daylen(jday, location['latitude']) + _, _, _, SNUP = daylen(jday, location["latitude"]) # Vapor pressure - ea = 6.107 * 10 ** ((7.5 * Ta) / (237.3 + Ta)) * (RH / 100.) + ea = 6.107 * 10 ** ((7.5 * Ta) / (237.3 + Ta)) * (RH / 100.0) # Determination of clear - sky emissivity from Prata (1996) msteg = 46.5 * (ea / (Ta + 273.15)) - esky = (1 - (1 + msteg) * np.exp(-((1.2 + 3.0 * msteg) ** 0.5))) + elvis # -0.04 old error from Jonsson et al.2006 + esky = ( + 1 - (1 + msteg) * np.exp(-((1.2 + 3.0 * msteg) ** 0.5)) + ) + elvis # -0.04 old error from Jonsson et al.2006 - if altitude > 0: # # # # # # DAYTIME # # # # # # + if altitude > 0: # # # # # # DAYTIME # # # # # # # Clearness Index on Earth's surface after Crawford and Dunchon (1999) with a correction # factor for low sun elevations after Lindberg et al.(2008) - I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b(zen, jday, Ta, RH / 100., radG, location, P) + I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( + zen, jday, Ta, RH / 100.0, radG, location, P + ) if (CI > 1) or (CI == np.inf): CI = 1 # Estimation of radD and radI if not measured after Reindl et al.(1990) if onlyglobal == 1: - I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b(zen, jday, Ta, RH / 100., radG, location, P) + I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( + zen, jday, Ta, RH / 100.0, radG, location, P + ) if (CI > 1) or (CI == np.inf): CI = 1 @@ -102,84 +189,232 @@ def Solweig_2021a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, s # Anisotropic Diffuse Radiation after Perez et al. 1993 if ani == 1: patchchoice = 1 - zenDeg = zen*(180/np.pi) - lv = Perez_v3(zenDeg, azimuth, radD, radI, jday, patchchoice) # Relative luminance + zenDeg = zen * (180 / np.pi) + lv = Perez_v3( + zenDeg, azimuth, radD, radI, jday, patchchoice + ) # Relative luminance aniLum = np.zeros((rows, cols)) - for idx in range(0,145): - aniLum = aniLum + diffsh[:,:,idx] * lv[0][idx][2] # Total relative luminance from sky into each cell - - dRad = aniLum * radD # Total diffuse radiation from sky into each cell + for idx in range(0, 145): + aniLum = ( + aniLum + diffsh[:, :, idx] * lv[0][idx][2] + ) # Total relative luminance from sky into each cell + + dRad = ( + aniLum * radD + ) # Total diffuse radiation from sky into each cell else: dRad = radD * svfbuveg lv = 0 # Shadow images if usevegdem == 1: - vegsh, sh, _, wallsh, wallsun, wallshve, _, facesun = shadowingfunction_wallheight_23(dsm, vegdem, vegdem2, - azimuth, altitude, scale, amaxvalue, bush, walls, dirwalls * np.pi / 180.) + vegsh, sh, _, wallsh, wallsun, wallshve, _, facesun = ( + shadowingfunction_wallheight_23( + dsm, + vegdem, + vegdem2, + azimuth, + altitude, + scale, + amaxvalue, + bush, + walls, + dirwalls * np.pi / 180.0, + ) + ) shadow = sh - (1 - vegsh) * (1 - psi) else: - sh, wallsh, wallsun, facesh, facesun = shadowingfunction_wallheight_13(dsm, azimuth, altitude, scale, - walls, dirwalls * np.pi / 180.) + sh, wallsh, wallsun, facesh, facesun = ( + shadowingfunction_wallheight_13( + dsm, + azimuth, + altitude, + scale, + walls, + dirwalls * np.pi / 180.0, + ) + ) shadow = sh # # # Surface temperature parameterisation during daytime # # # # # new using max sun alt.instead of dfm Tgamp = (TgK * altmax - Tstart) + Tstart Tgampwall = (TgK_wall * altmax - (Tstart_wall)) + (Tstart_wall) - Tg = Tgamp * np.sin((((dectime - np.floor(dectime)) - SNUP / 24) / (TmaxLST / 24 - SNUP / 24)) * np.pi / 2) + Tstart # 2015 a, based on max sun altitude - Tgwall = Tgampwall * np.sin((((dectime - np.floor(dectime)) - SNUP / 24) / (TmaxLST_wall / 24 - SNUP / 24)) * np.pi / 2) + (Tstart_wall) # 2015a, based on max sun altitude + Tg = ( + Tgamp + * np.sin( + ( + ((dectime - np.floor(dectime)) - SNUP / 24) + / (TmaxLST / 24 - SNUP / 24) + ) + * np.pi + / 2 + ) + + Tstart + ) # 2015 a, based on max sun altitude + Tgwall = Tgampwall * np.sin( + ( + ((dectime - np.floor(dectime)) - SNUP / 24) + / (TmaxLST_wall / 24 - SNUP / 24) + ) + * np.pi + / 2 + ) + ( + Tstart_wall + ) # 2015a, based on max sun altitude if Tgwall < 0: # temporary for removing low Tg during morning 20130205 # Tg = 0 Tgwall = 0 # New estimation of Tg reduction for non - clear situation based on Reindl et al.1990 - radI0, _ = diffusefraction(I0, altitude, 1., Ta, RH) - corr = 0.1473 * np.log(90 - (zen / np.pi * 180)) + 0.3454 # 20070329 correction of lat, Lindberg et al. 2008 + radI0, _ = diffusefraction(I0, altitude, 1.0, Ta, RH) + corr = ( + 0.1473 * np.log(90 - (zen / np.pi * 180)) + 0.3454 + ) # 20070329 correction of lat, Lindberg et al. 2008 CI_Tg = (radI / radI0) + (1 - corr) if (CI_Tg > 1) or (CI_Tg == np.inf): CI_Tg = 1 Tg = Tg * CI_Tg # new estimation Tgwall = Tgwall * CI_Tg if landcover == 1: - Tg[Tg < 0] = 0 # temporary for removing low Tg during morning 20130205 + Tg[Tg < 0] = ( + 0 # temporary for removing low Tg during morning 20130205 + ) # # # # Ground View Factors # # # # - gvfLup, gvfalb, gvfalbnosh, gvfLupE, gvfalbE, gvfalbnoshE, gvfLupS, gvfalbS, gvfalbnoshS, gvfLupW, gvfalbW,\ - gvfalbnoshW, gvfLupN, gvfalbN, gvfalbnoshN, gvfSum, gvfNorm = gvf_2018a(wallsun, walls, buildings, scale, shadow, first, - second, dirwalls, Tg, Tgwall, Ta, emis_grid, ewall, alb_grid, SBC, albedo_b, rows, cols, - Twater, lc_grid, landcover) + ( + gvfLup, + gvfalb, + gvfalbnosh, + gvfLupE, + gvfalbE, + gvfalbnoshE, + gvfLupS, + gvfalbS, + gvfalbnoshS, + gvfLupW, + gvfalbW, + gvfalbnoshW, + gvfLupN, + gvfalbN, + gvfalbnoshN, + gvfSum, + gvfNorm, + ) = gvf_2018a( + wallsun, + walls, + buildings, + scale, + shadow, + first, + second, + dirwalls, + Tg, + Tgwall, + Ta, + emis_grid, + ewall, + alb_grid, + SBC, + albedo_b, + rows, + cols, + Twater, + lc_grid, + landcover, + ) # # # # Lup, daytime # # # # # Surface temperature wave delay - new as from 2014a - Lup, timeaddnotused, Tgmap1 = TsWaveDelay_2015a(gvfLup, firstdaytime, timeadd, timestepdec, Tgmap1) - LupE, timeaddnotused, Tgmap1E = TsWaveDelay_2015a(gvfLupE, firstdaytime, timeadd, timestepdec, Tgmap1E) - LupS, timeaddnotused, Tgmap1S = TsWaveDelay_2015a(gvfLupS, firstdaytime, timeadd, timestepdec, Tgmap1S) - LupW, timeaddnotused, Tgmap1W = TsWaveDelay_2015a(gvfLupW, firstdaytime, timeadd, timestepdec, Tgmap1W) - LupN, timeaddnotused, Tgmap1N = TsWaveDelay_2015a(gvfLupN, firstdaytime, timeadd, timestepdec, Tgmap1N) - + Lup, timeaddnotused, Tgmap1 = TsWaveDelay_2015a( + gvfLup, firstdaytime, timeadd, timestepdec, Tgmap1 + ) + LupE, timeaddnotused, Tgmap1E = TsWaveDelay_2015a( + gvfLupE, firstdaytime, timeadd, timestepdec, Tgmap1E + ) + LupS, timeaddnotused, Tgmap1S = TsWaveDelay_2015a( + gvfLupS, firstdaytime, timeadd, timestepdec, Tgmap1S + ) + LupW, timeaddnotused, Tgmap1W = TsWaveDelay_2015a( + gvfLupW, firstdaytime, timeadd, timestepdec, Tgmap1W + ) + LupN, timeaddnotused, Tgmap1N = TsWaveDelay_2015a( + gvfLupN, firstdaytime, timeadd, timestepdec, Tgmap1N + ) + # # For Tg output in POIs TgTemp = Tg * shadow + Ta - TgOut, timeadd, TgOut1 = TsWaveDelay_2015a(TgTemp, firstdaytime, timeadd, timestepdec, TgOut1) #timeadd only here v2021a + TgOut, timeadd, TgOut1 = TsWaveDelay_2015a( + TgTemp, firstdaytime, timeadd, timestepdec, TgOut1 + ) # timeadd only here v2021a # Building height angle from svf - F_sh = cylindric_wedge(zen, svfalfa, rows, cols) # Fraction shadow on building walls based on sun alt and svf + F_sh = cylindric_wedge( + zen, svfalfa, rows, cols + ) # Fraction shadow on building walls based on sun alt and svf F_sh[np.isnan(F_sh)] = 0.5 # # # # # # # Calculation of shortwave daytime radiative fluxes # # # # # # # - Kdown = radI * shadow * np.sin(altitude * (np.pi / 180)) + dRad + albedo_b * (1 - svfbuveg) * \ - (radG * (1 - F_sh) + radD * F_sh) # *sin(altitude(i) * (pi / 180)) - - #Kdown = radI * shadow * np.sin(altitude * (np.pi / 180)) + radD * svfbuveg + albedo_b * (1 - svfbuveg) * \ - #(radG * (1 - F_sh) + radD * F_sh) # *sin(altitude(i) * (pi / 180)) - - Kup, KupE, KupS, KupW, KupN = Kup_veg_2015a(radI, radD, radG, altitude, svfbuveg, albedo_b, F_sh, gvfalb, - gvfalbE, gvfalbS, gvfalbW, gvfalbN, gvfalbnosh, gvfalbnoshE, gvfalbnoshS, gvfalbnoshW, gvfalbnoshN) - - Keast, Ksouth, Kwest, Knorth, KsideI, KsideD = Kside_veg_v2019a(radI, radD, radG, shadow, svfS, svfW, svfN, svfE, - svfEveg, svfSveg, svfWveg, svfNveg, azimuth, altitude, psi, t, albedo_b, F_sh, KupE, KupS, KupW, - KupN, cyl, lv, ani, diffsh, rows, cols) + Kdown = ( + radI * shadow * np.sin(altitude * (np.pi / 180)) + + dRad + + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) + ) # *sin(altitude(i) * (pi / 180)) + + # Kdown = radI * shadow * np.sin(altitude * (np.pi / 180)) + radD * svfbuveg + albedo_b * (1 - svfbuveg) * \ + # (radG * (1 - F_sh) + radD * F_sh) # *sin(altitude(i) * (pi / 180)) + + Kup, KupE, KupS, KupW, KupN = Kup_veg_2015a( + radI, + radD, + radG, + altitude, + svfbuveg, + albedo_b, + F_sh, + gvfalb, + gvfalbE, + gvfalbS, + gvfalbW, + gvfalbN, + gvfalbnosh, + gvfalbnoshE, + gvfalbnoshS, + gvfalbnoshW, + gvfalbnoshN, + ) + + Keast, Ksouth, Kwest, Knorth, KsideI, KsideD = Kside_veg_v2019a( + radI, + radD, + radG, + shadow, + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + azimuth, + altitude, + psi, + t, + albedo_b, + F_sh, + KupE, + KupS, + KupW, + KupN, + cyl, + lv, + ani, + diffsh, + rows, + cols, + ) firstdaytime = 0 @@ -205,7 +440,9 @@ def Solweig_2021a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, s # # # # Lup # # # # Lup = SBC * emis_grid * ((Knight + Ta + Tg + 273.15) ** 4) if landcover == 1: - Lup[lc_grid == 3] = SBC * 0.98 * (Twater + 273.15) ** 4 # nocturnal Water temp + Lup[lc_grid == 3] = ( + SBC * 0.98 * (Twater + 273.15) ** 4 + ) # nocturnal Water temp LupE = Lup LupS = Lup @@ -220,35 +457,130 @@ def Solweig_2021a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, s firstdaytime = 1 # # # # Ldown # # # # - Ldown = (svf + svfveg - 1) * esky * SBC * ((Ta + 273.15) ** 4) + (2 - svfveg - svfaveg) * ewall * SBC * \ - ((Ta + 273.15) ** 4) + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + \ - (2 - svf - svfveg) * (1 - ewall) * esky * SBC * ((Ta + 273.15) ** 4) # Jonsson et al.(2006) + Ldown = ( + (svf + svfveg - 1) * esky * SBC * ((Ta + 273.15) ** 4) + + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) + + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + + (2 - svf - svfveg) * (1 - ewall) * esky * SBC * ((Ta + 273.15) ** 4) + ) # Jonsson et al.(2006) # Ldown = Ldown - 25 # Shown by Jonsson et al.(2006) and Duarte et al.(2006) if CI < 0.95: # non - clear conditions c = 1 - CI - Ldown = Ldown * (1 - c) + c * ((svf + svfveg - 1) * SBC * ((Ta + 273.15) ** 4) + (2 - svfveg - svfaveg) * - ewall * SBC * ((Ta + 273.15) ** 4) + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + - (2 - svf - svfveg) * (1 - ewall) * SBC * ((Ta + 273.15) ** 4)) # NOT REALLY TESTED!!! BUT MORE CORRECT? + Ldown = Ldown * (1 - c) + c * ( + (svf + svfveg - 1) * SBC * ((Ta + 273.15) ** 4) + + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) + + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + + (2 - svf - svfveg) * (1 - ewall) * SBC * ((Ta + 273.15) ** 4) + ) # NOT REALLY TESTED!!! BUT MORE CORRECT? # # # # Lside # # # # - Least, Lsouth, Lwest, Lnorth = Lside_veg_v2015a(svfS, svfW, svfN, svfE, svfEveg, svfSveg, svfWveg, svfNveg, - svfEaveg, svfSaveg, svfWaveg, svfNaveg, azimuth, altitude, Ta, Tgwall, SBC, ewall, Ldown, - esky, t, F_sh, CI, LupE, LupS, LupW, LupN) + Least, Lsouth, Lwest, Lnorth = Lside_veg_v2015a( + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + azimuth, + altitude, + Ta, + Tgwall, + SBC, + ewall, + Ldown, + esky, + t, + F_sh, + CI, + LupE, + LupS, + LupW, + LupN, + ) # # # # Calculation of radiant flux density and Tmrt # # # # - if cyl == 1 and ani == 1: # Human body considered as a cylinder with Perez et al. (1993) - Sstr = absK * ((KsideI + KsideD) * Fcyl + (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside) + absL * \ - (Ldown * Fup + Lup * Fup + Lnorth * Fside + Least * Fside + Lsouth * Fside + Lwest * Fside) - elif cyl == 1 and ani == 0: # Human body considered as a cylinder with isotropic all-sky diffuse - Sstr = absK * (KsideI * Fcyl + (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside) + absL * \ - (Ldown * Fup + Lup * Fup + Lnorth * Fside + Least * Fside + Lsouth * Fside + Lwest * Fside) - else: # Human body considered as a standing cube - Sstr = absK * ((Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside) +absL * \ - (Ldown * Fup + Lup * Fup + Lnorth * Fside + Least * Fside + Lsouth * Fside + Lwest * Fside) + if ( + cyl == 1 and ani == 1 + ): # Human body considered as a cylinder with Perez et al. (1993) + Sstr = absK * ( + (KsideI + KsideD) * Fcyl + + (Kdown + Kup) * Fup + + (Knorth + Keast + Ksouth + Kwest) * Fside + ) + absL * ( + Ldown * Fup + + Lup * Fup + + Lnorth * Fside + + Least * Fside + + Lsouth * Fside + + Lwest * Fside + ) + elif ( + cyl == 1 and ani == 0 + ): # Human body considered as a cylinder with isotropic all-sky diffuse + Sstr = absK * ( + KsideI * Fcyl + + (Kdown + Kup) * Fup + + (Knorth + Keast + Ksouth + Kwest) * Fside + ) + absL * ( + Ldown * Fup + + Lup * Fup + + Lnorth * Fside + + Least * Fside + + Lsouth * Fside + + Lwest * Fside + ) + else: # Human body considered as a standing cube + Sstr = absK * ( + (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside + ) + absL * ( + Ldown * Fup + + Lup * Fup + + Lnorth * Fside + + Least * Fside + + Lsouth * Fside + + Lwest * Fside + ) Tmrt = np.sqrt(np.sqrt((Sstr / (absL * SBC)))) - 273.2 - return Tmrt, Kdown, Kup, Ldown, Lup, Tg, ea, esky, I0, CI, shadow, firstdaytime, timestepdec, \ - timeadd, Tgmap1, Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, Keast, Ksouth, Kwest, Knorth, Least, \ - Lsouth, Lwest, Lnorth, KsideI, TgOut1, TgOut, radI, radD \ No newline at end of file + return ( + Tmrt, + Kdown, + Kup, + Ldown, + Lup, + Tg, + ea, + esky, + I0, + CI, + shadow, + firstdaytime, + timestepdec, + timeadd, + Tgmap1, + Tgmap1E, + Tgmap1S, + Tgmap1W, + Tgmap1N, + Keast, + Ksouth, + Kwest, + Knorth, + Least, + Lsouth, + Lwest, + Lnorth, + KsideI, + TgOut1, + TgOut, + radI, + radD, + ) diff --git a/functions/SOLWEIGpython/Solweig_2022a_calc_forprocessing.py b/functions/SOLWEIGpython/Solweig_2022a_calc_forprocessing.py index 3420973..f3e2353 100644 --- a/functions/SOLWEIGpython/Solweig_2022a_calc_forprocessing.py +++ b/functions/SOLWEIGpython/Solweig_2022a_calc_forprocessing.py @@ -2,14 +2,21 @@ import numpy as np from .daylen import daylen -from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import clearnessindex_2013b +from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import ( + clearnessindex_2013b, +) from ...util.SEBESOLWEIGCommonFiles.diffusefraction import diffusefraction -from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_13 import shadowingfunction_wallheight_13 -from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_23 import shadowingfunction_wallheight_23 +from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_13 import ( + shadowingfunction_wallheight_13, +) +from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_23 import ( + shadowingfunction_wallheight_23, +) from .gvf_2018a import gvf_2018a from .cylindric_wedge import cylindric_wedge from .TsWaveDelay_2015a import TsWaveDelay_2015a from .Kup_veg_2015a import Kup_veg_2015a + # from .Lside_veg_v2015a import Lside_veg_v2015a # from .Kside_veg_v2019a import Kside_veg_v2019a from .Kside_veg_v2022a import Kside_veg_v2022a @@ -21,21 +28,101 @@ from .Lside_veg_v2022a import Lside_veg_v2022a from copy import deepcopy -def Solweig_2022a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, svfveg, svfNveg, svfEveg, svfSveg, - svfWveg, svfaveg, svfEaveg, svfSaveg, svfWaveg, svfNaveg, vegdem, vegdem2, albedo_b, absK, absL, - ewall, Fside, Fup, Fcyl, altitude, azimuth, zen, jday, usevegdem, onlyglobal, buildings, location, psi, - landcover, lc_grid, dectime, altmax, dirwalls, walls, cyl, elvis, Ta, RH, radG, radD, radI, P, - amaxvalue, bush, Twater, TgK, Tstart, alb_grid, emis_grid, TgK_wall, Tstart_wall, TmaxLST, - TmaxLST_wall, first, second, svfalfa, svfbuveg, firstdaytime, timeadd, timestepdec, Tgmap1, - Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, CI, TgOut1, diffsh, shmat, vegshmat, vbshvegshmat, anisotropic_sky, asvf, patch_option): - -#def Solweig_2021a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, svfveg, svfNveg, svfEveg, svfSveg, -# svfWveg, svfaveg, svfEaveg, svfSaveg, svfWaveg, svfNaveg, vegdem, vegdem2, albedo_b, absK, absL, -# ewall, Fside, Fup, Fcyl, altitude, azimuth, zen, jday, usevegdem, onlyglobal, buildings, location, psi, -# landcover, lc_grid, dectime, altmax, dirwalls, walls, cyl, elvis, Ta, RH, radG, radD, radI, P, -# amaxvalue, bush, Twater, TgK, Tstart, alb_grid, emis_grid, TgK_wall, Tstart_wall, TmaxLST, -# TmaxLST_wall, first, second, svfalfa, svfbuveg, firstdaytime, timeadd, timestepdec, Tgmap1, -# Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, CI, TgOut1, diffsh, ani): + +def Solweig_2022a_calc( + i, + dsm, + scale, + rows, + cols, + svf, + svfN, + svfW, + svfE, + svfS, + svfveg, + svfNveg, + svfEveg, + svfSveg, + svfWveg, + svfaveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + vegdem, + vegdem2, + albedo_b, + absK, + absL, + ewall, + Fside, + Fup, + Fcyl, + altitude, + azimuth, + zen, + jday, + usevegdem, + onlyglobal, + buildings, + location, + psi, + landcover, + lc_grid, + dectime, + altmax, + dirwalls, + walls, + cyl, + elvis, + Ta, + RH, + radG, + radD, + radI, + P, + amaxvalue, + bush, + Twater, + TgK, + Tstart, + alb_grid, + emis_grid, + TgK_wall, + Tstart_wall, + TmaxLST, + TmaxLST_wall, + first, + second, + svfalfa, + svfbuveg, + firstdaytime, + timeadd, + timestepdec, + Tgmap1, + Tgmap1E, + Tgmap1S, + Tgmap1W, + Tgmap1N, + CI, + TgOut1, + diffsh, + shmat, + vegshmat, + vbshvegshmat, + anisotropic_sky, + asvf, + patch_option, +): + + # def Solweig_2021a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, svfveg, svfNveg, svfEveg, svfSveg, + # svfWveg, svfaveg, svfEaveg, svfSaveg, svfWaveg, svfNaveg, vegdem, vegdem2, albedo_b, absK, absL, + # ewall, Fside, Fup, Fcyl, altitude, azimuth, zen, jday, usevegdem, onlyglobal, buildings, location, psi, + # landcover, lc_grid, dectime, altmax, dirwalls, walls, cyl, elvis, Ta, RH, radG, radD, radI, P, + # amaxvalue, bush, Twater, TgK, Tstart, alb_grid, emis_grid, TgK_wall, Tstart_wall, TmaxLST, + # TmaxLST_wall, first, second, svfalfa, svfbuveg, firstdaytime, timeadd, timestepdec, Tgmap1, + # Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, CI, TgOut1, diffsh, ani): # This is the core function of the SOLWEIG model # 2016-Aug-28 @@ -86,43 +173,49 @@ def Solweig_2022a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, s # amaxvalue = max height of buildings # bush = grid representing bushes # Twater = temperature of water (daily) - # TgK, Tstart, TgK_wall, Tstart_wall, TmaxLST,TmaxLST_wall, + # TgK, Tstart, TgK_wall, Tstart_wall, TmaxLST,TmaxLST_wall, # alb_grid, emis_grid = albedo and emmissivity on ground # first, second = conneted to old Ts model (source area based on Smidt et al.) # svfalfa = SVF recalculated to angle - # svfbuveg = complete SVF - # firstdaytime, timeadd, timestepdec, Tgmap1, Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, + # svfbuveg = complete SVF + # firstdaytime, timeadd, timestepdec, Tgmap1, Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, # CI = Clearness index # TgOut1 = old Ts model # diffsh, ani = Used in anisotrpic models (Wallenberg et al. 2019, 2022) # # # Core program start # # # # Instrument offset in degrees - t = 0. + t = 0.0 # Stefan Bolzmans Constant SBC = 5.67051e-8 # Find sunrise decimal hour - new from 2014a - _, _, _, SNUP = daylen(jday, location['latitude']) + _, _, _, SNUP = daylen(jday, location["latitude"]) # Vapor pressure - ea = 6.107 * 10 ** ((7.5 * Ta) / (237.3 + Ta)) * (RH / 100.) + ea = 6.107 * 10 ** ((7.5 * Ta) / (237.3 + Ta)) * (RH / 100.0) # Determination of clear - sky emissivity from Prata (1996) msteg = 46.5 * (ea / (Ta + 273.15)) - esky = (1 - (1 + msteg) * np.exp(-((1.2 + 3.0 * msteg) ** 0.5))) + elvis # -0.04 old error from Jonsson et al.2006 + esky = ( + 1 - (1 + msteg) * np.exp(-((1.2 + 3.0 * msteg) ** 0.5)) + ) + elvis # -0.04 old error from Jonsson et al.2006 - if altitude > 0: # # # # # # DAYTIME # # # # # # + if altitude > 0: # # # # # # DAYTIME # # # # # # # Clearness Index on Earth's surface after Crawford and Dunchon (1999) with a correction # factor for low sun elevations after Lindberg et al.(2008) - I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b(zen, jday, Ta, RH / 100., radG, location, P) + I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( + zen, jday, Ta, RH / 100.0, radG, location, P + ) if (CI > 1) or (CI == np.inf): CI = 1 # Estimation of radD and radI if not measured after Reindl et al.(1990) if onlyglobal == 1: - I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b(zen, jday, Ta, RH / 100., radG, location, P) + I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( + zen, jday, Ta, RH / 100.0, radG, location, P + ) if (CI > 1) or (CI == np.inf): CI = 1 @@ -132,15 +225,19 @@ def Solweig_2022a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, s # Anisotropic Diffuse Radiation after Perez et al. 1993 if anisotropic_sky == 1: patchchoice = 1 - zenDeg = zen*(180/np.pi) + zenDeg = zen * (180 / np.pi) # Relative luminance - lv, pc_, pb_ = Perez_v3(zenDeg, azimuth, radD, radI, jday, patchchoice, patch_option) + lv, pc_, pb_ = Perez_v3( + zenDeg, azimuth, radD, radI, jday, patchchoice, patch_option + ) # Total relative luminance from sky, i.e. from each patch, into each cell aniLum = np.zeros((rows, cols)) for idx in range(lv.shape[0]): - aniLum += diffsh[:,:,idx] * lv[idx,2] + aniLum += diffsh[:, :, idx] * lv[idx, 2] - dRad = aniLum * radD # Total diffuse radiation from sky into each cell + dRad = ( + aniLum * radD + ) # Total diffuse radiation from sky into each cell else: dRad = radD * svfbuveg patchchoice = 1 @@ -150,86 +247,226 @@ def Solweig_2022a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, s # Shadow images if usevegdem == 1: - vegsh, sh, _, wallsh, wallsun, wallshve, _, facesun = shadowingfunction_wallheight_23(dsm, vegdem, vegdem2, - azimuth, altitude, scale, amaxvalue, bush, walls, dirwalls * np.pi / 180.) + vegsh, sh, _, wallsh, wallsun, wallshve, _, facesun = ( + shadowingfunction_wallheight_23( + dsm, + vegdem, + vegdem2, + azimuth, + altitude, + scale, + amaxvalue, + bush, + walls, + dirwalls * np.pi / 180.0, + ) + ) shadow = sh - (1 - vegsh) * (1 - psi) else: - sh, wallsh, wallsun, facesh, facesun = shadowingfunction_wallheight_13(dsm, azimuth, altitude, scale, - walls, dirwalls * np.pi / 180.) + sh, wallsh, wallsun, facesh, facesun = ( + shadowingfunction_wallheight_13( + dsm, + azimuth, + altitude, + scale, + walls, + dirwalls * np.pi / 180.0, + ) + ) shadow = sh # # # Surface temperature parameterisation during daytime # # # # # new using max sun alt.instead of dfm # Tgamp = (TgK * altmax - Tstart) + Tstart # Old - Tgamp = TgK * altmax + Tstart # Fixed 2021 + Tgamp = TgK * altmax + Tstart # Fixed 2021 # Tgampwall = (TgK_wall * altmax - (Tstart_wall)) + (Tstart_wall) # Old Tgampwall = TgK_wall * altmax + Tstart_wall - Tg = Tgamp * np.sin((((dectime - np.floor(dectime)) - SNUP / 24) / (TmaxLST / 24 - SNUP / 24)) * np.pi / 2) # 2015 a, based on max sun altitude - Tgwall = Tgampwall * np.sin((((dectime - np.floor(dectime)) - SNUP / 24) / (TmaxLST_wall / 24 - SNUP / 24)) * np.pi / 2) # 2015a, based on max sun altitude + Tg = Tgamp * np.sin( + ( + ((dectime - np.floor(dectime)) - SNUP / 24) + / (TmaxLST / 24 - SNUP / 24) + ) + * np.pi + / 2 + ) # 2015 a, based on max sun altitude + Tgwall = Tgampwall * np.sin( + ( + ((dectime - np.floor(dectime)) - SNUP / 24) + / (TmaxLST_wall / 24 - SNUP / 24) + ) + * np.pi + / 2 + ) # 2015a, based on max sun altitude if Tgwall < 0: # temporary for removing low Tg during morning 20130205 # Tg = 0 Tgwall = 0 # New estimation of Tg reduction for non - clear situation based on Reindl et al.1990 - radI0, _ = diffusefraction(I0, altitude, 1., Ta, RH) - corr = 0.1473 * np.log(90 - (zen / np.pi * 180)) + 0.3454 # 20070329 correction of lat, Lindberg et al. 2008 + radI0, _ = diffusefraction(I0, altitude, 1.0, Ta, RH) + corr = ( + 0.1473 * np.log(90 - (zen / np.pi * 180)) + 0.3454 + ) # 20070329 correction of lat, Lindberg et al. 2008 CI_Tg = (radG / radI0) + (1 - corr) if (CI_Tg > 1) or (CI_Tg == np.inf): CI_Tg = 1 - deg2rad = np.pi/180 + deg2rad = np.pi / 180 radG0 = radI0 * (np.sin(altitude * deg2rad)) + _ CI_TgG = (radG / radG0) + (1 - corr) if (CI_TgG > 1) or (CI_TgG == np.inf): CI_TgG = 1 - + # Tg = Tg * CI_Tg # new estimation # Tgwall = Tgwall * CI_Tg Tg = Tg * CI_TgG # new estimation Tgwall = Tgwall * CI_TgG if landcover == 1: - Tg[Tg < 0] = 0 # temporary for removing low Tg during morning 20130205 + Tg[Tg < 0] = ( + 0 # temporary for removing low Tg during morning 20130205 + ) # # # # Ground View Factors # # # # - gvfLup, gvfalb, gvfalbnosh, gvfLupE, gvfalbE, gvfalbnoshE, gvfLupS, gvfalbS, gvfalbnoshS, gvfLupW, gvfalbW,\ - gvfalbnoshW, gvfLupN, gvfalbN, gvfalbnoshN, gvfSum, gvfNorm = gvf_2018a(wallsun, walls, buildings, scale, shadow, first, - second, dirwalls, Tg, Tgwall, Ta, emis_grid, ewall, alb_grid, SBC, albedo_b, rows, cols, - Twater, lc_grid, landcover) + ( + gvfLup, + gvfalb, + gvfalbnosh, + gvfLupE, + gvfalbE, + gvfalbnoshE, + gvfLupS, + gvfalbS, + gvfalbnoshS, + gvfLupW, + gvfalbW, + gvfalbnoshW, + gvfLupN, + gvfalbN, + gvfalbnoshN, + gvfSum, + gvfNorm, + ) = gvf_2018a( + wallsun, + walls, + buildings, + scale, + shadow, + first, + second, + dirwalls, + Tg, + Tgwall, + Ta, + emis_grid, + ewall, + alb_grid, + SBC, + albedo_b, + rows, + cols, + Twater, + lc_grid, + landcover, + ) # # # # Lup, daytime # # # # # Surface temperature wave delay - new as from 2014a - Lup, timeaddnotused, Tgmap1 = TsWaveDelay_2015a(gvfLup, firstdaytime, timeadd, timestepdec, Tgmap1) - LupE, timeaddnotused, Tgmap1E = TsWaveDelay_2015a(gvfLupE, firstdaytime, timeadd, timestepdec, Tgmap1E) - LupS, timeaddnotused, Tgmap1S = TsWaveDelay_2015a(gvfLupS, firstdaytime, timeadd, timestepdec, Tgmap1S) - LupW, timeaddnotused, Tgmap1W = TsWaveDelay_2015a(gvfLupW, firstdaytime, timeadd, timestepdec, Tgmap1W) - LupN, timeaddnotused, Tgmap1N = TsWaveDelay_2015a(gvfLupN, firstdaytime, timeadd, timestepdec, Tgmap1N) - + Lup, timeaddnotused, Tgmap1 = TsWaveDelay_2015a( + gvfLup, firstdaytime, timeadd, timestepdec, Tgmap1 + ) + LupE, timeaddnotused, Tgmap1E = TsWaveDelay_2015a( + gvfLupE, firstdaytime, timeadd, timestepdec, Tgmap1E + ) + LupS, timeaddnotused, Tgmap1S = TsWaveDelay_2015a( + gvfLupS, firstdaytime, timeadd, timestepdec, Tgmap1S + ) + LupW, timeaddnotused, Tgmap1W = TsWaveDelay_2015a( + gvfLupW, firstdaytime, timeadd, timestepdec, Tgmap1W + ) + LupN, timeaddnotused, Tgmap1N = TsWaveDelay_2015a( + gvfLupN, firstdaytime, timeadd, timestepdec, Tgmap1N + ) + # # For Tg output in POIs TgTemp = Tg * shadow + Ta - TgOut, timeadd, TgOut1 = TsWaveDelay_2015a(TgTemp, firstdaytime, timeadd, timestepdec, TgOut1) #timeadd only here v2021a + TgOut, timeadd, TgOut1 = TsWaveDelay_2015a( + TgTemp, firstdaytime, timeadd, timestepdec, TgOut1 + ) # timeadd only here v2021a # Building height angle from svf - F_sh = cylindric_wedge(zen, svfalfa, rows, cols) # Fraction shadow on building walls based on sun alt and svf + F_sh = cylindric_wedge( + zen, svfalfa, rows, cols + ) # Fraction shadow on building walls based on sun alt and svf F_sh[np.isnan(F_sh)] = 0.5 # # # # # # # Calculation of shortwave daytime radiative fluxes # # # # # # # - Kdown = radI * shadow * np.sin(altitude * (np.pi / 180)) + dRad + albedo_b * (1 - svfbuveg) * \ - (radG * (1 - F_sh) + radD * F_sh) # *sin(altitude(i) * (pi / 180)) - - #Kdown = radI * shadow * np.sin(altitude * (np.pi / 180)) + radD * svfbuveg + albedo_b * (1 - svfbuveg) * \ - #(radG * (1 - F_sh) + radD * F_sh) # *sin(altitude(i) * (pi / 180)) - - Kup, KupE, KupS, KupW, KupN = Kup_veg_2015a(radI, radD, radG, altitude, svfbuveg, albedo_b, F_sh, gvfalb, - gvfalbE, gvfalbS, gvfalbW, gvfalbN, gvfalbnosh, gvfalbnoshE, gvfalbnoshS, gvfalbnoshW, gvfalbnoshN) - - #Keast, Ksouth, Kwest, Knorth, KsideI, KsideD = Kside_veg_v2019a(radI, radD, radG, shadow, svfS, svfW, svfN, svfE, + Kdown = ( + radI * shadow * np.sin(altitude * (np.pi / 180)) + + dRad + + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) + ) # *sin(altitude(i) * (pi / 180)) + + # Kdown = radI * shadow * np.sin(altitude * (np.pi / 180)) + radD * svfbuveg + albedo_b * (1 - svfbuveg) * \ + # (radG * (1 - F_sh) + radD * F_sh) # *sin(altitude(i) * (pi / 180)) + + Kup, KupE, KupS, KupW, KupN = Kup_veg_2015a( + radI, + radD, + radG, + altitude, + svfbuveg, + albedo_b, + F_sh, + gvfalb, + gvfalbE, + gvfalbS, + gvfalbW, + gvfalbN, + gvfalbnosh, + gvfalbnoshE, + gvfalbnoshS, + gvfalbnoshW, + gvfalbnoshN, + ) + + # Keast, Ksouth, Kwest, Knorth, KsideI, KsideD = Kside_veg_v2019a(radI, radD, radG, shadow, svfS, svfW, svfN, svfE, # svfEveg, svfSveg, svfWveg, svfNveg, azimuth, altitude, psi, t, albedo_b, F_sh, KupE, KupS, KupW, # KupN, cyl, lv, ani, diffsh, rows, cols) - Keast, Ksouth, Kwest, Knorth, KsideI, KsideD, Kside = Kside_veg_v2022a(radI, radD, radG, shadow, svfS, svfW, svfN, svfE, - svfEveg, svfSveg, svfWveg, svfNveg, azimuth, altitude, psi, t, albedo_b, F_sh, KupE, KupS, KupW, - KupN, cyl, lv, anisotropic_sky, diffsh, rows, cols, asvf, shmat, vegshmat, vbshvegshmat) + Keast, Ksouth, Kwest, Knorth, KsideI, KsideD, Kside = Kside_veg_v2022a( + radI, + radD, + radG, + shadow, + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + azimuth, + altitude, + psi, + t, + albedo_b, + F_sh, + KupE, + KupS, + KupW, + KupN, + cyl, + lv, + anisotropic_sky, + diffsh, + rows, + cols, + asvf, + shmat, + vegshmat, + vbshvegshmat, + ) firstdaytime = 0 @@ -254,14 +491,16 @@ def Solweig_2022a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, s CI_Tg = deepcopy(CI) CI_TgG = deepcopy(CI) - dRad = np.zeros((rows,cols)) + dRad = np.zeros((rows, cols)) - Kside = np.zeros((rows,cols)) + Kside = np.zeros((rows, cols)) # # # # Lup # # # # Lup = SBC * emis_grid * ((Knight + Ta + Tg + 273.15) ** 4) if landcover == 1: - Lup[lc_grid == 3] = SBC * 0.98 * (Twater + 273.15) ** 4 # nocturnal Water temp + Lup[lc_grid == 3] = ( + SBC * 0.98 * (Twater + 273.15) ** 4 + ) # nocturnal Water temp LupE = Lup LupS = Lup @@ -278,9 +517,11 @@ def Solweig_2022a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, s # # # # Ldown # # # # # Anisotropic sky longwave radiation if anisotropic_sky == 1: - if 'lv' not in locals(): + if "lv" not in locals(): # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) - skyvaultalt, skyvaultazi, _, _, _, _, _ = create_patches(patch_option) + skyvaultalt, skyvaultazi, _, _, _, _, _ = create_patches( + patch_option + ) patch_emissivities = np.zeros(skyvaultalt.shape[0]) @@ -297,32 +538,82 @@ def Solweig_2022a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, s CI = deepcopy(CI) if CI < 0.95: - esky_c = CI * esky + (1 - CI) * 1. + esky_c = CI * esky + (1 - CI) * 1.0 esky = esky_c - Ldown, Lside, Least_, Lwest_, Lnorth_, Lsouth_ \ - = Lcyl_v2022a(esky, L_patches, Ta, Tgwall, ewall, Lup, shmat, vegshmat, vbshvegshmat, - altitude, azimuth, rows, cols, asvf) + Ldown, Lside, Least_, Lwest_, Lnorth_, Lsouth_ = Lcyl_v2022a( + esky, + L_patches, + Ta, + Tgwall, + ewall, + Lup, + shmat, + vegshmat, + vbshvegshmat, + altitude, + azimuth, + rows, + cols, + asvf, + ) else: - Ldown = (svf + svfveg - 1) * esky * SBC * ((Ta + 273.15) ** 4) + (2 - svfveg - svfaveg) * ewall * SBC * \ - ((Ta + 273.15) ** 4) + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + \ - (2 - svf - svfveg) * (1 - ewall) * esky * SBC * ((Ta + 273.15) ** 4) # Jonsson et al.(2006) + Ldown = ( + (svf + svfveg - 1) * esky * SBC * ((Ta + 273.15) ** 4) + + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) + + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + + (2 - svf - svfveg) + * (1 - ewall) + * esky + * SBC + * ((Ta + 273.15) ** 4) + ) # Jonsson et al.(2006) # Ldown = Ldown - 25 # Shown by Jonsson et al.(2006) and Duarte et al.(2006) Lside = np.zeros((rows, cols)) L_patches = None - + if CI < 0.95: # non - clear conditions c = 1 - CI - Ldown = Ldown * (1 - c) + c * ((svf + svfveg - 1) * SBC * ((Ta + 273.15) ** 4) + (2 - svfveg - svfaveg) * - ewall * SBC * ((Ta + 273.15) ** 4) + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + - (2 - svf - svfveg) * (1 - ewall) * SBC * ((Ta + 273.15) ** 4)) # NOT REALLY TESTED!!! BUT MORE CORRECT? + Ldown = Ldown * (1 - c) + c * ( + (svf + svfveg - 1) * SBC * ((Ta + 273.15) ** 4) + + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) + + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + + (2 - svf - svfveg) * (1 - ewall) * SBC * ((Ta + 273.15) ** 4) + ) # NOT REALLY TESTED!!! BUT MORE CORRECT? # # # # Lside # # # # - Least, Lsouth, Lwest, Lnorth = Lside_veg_v2022a(svfS, svfW, svfN, svfE, svfEveg, svfSveg, svfWveg, svfNveg, - svfEaveg, svfSaveg, svfWaveg, svfNaveg, azimuth, altitude, Ta, Tgwall, SBC, ewall, Ldown, - esky, t, F_sh, CI, LupE, LupS, LupW, LupN, anisotropic_sky) + Least, Lsouth, Lwest, Lnorth = Lside_veg_v2022a( + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + azimuth, + altitude, + Ta, + Tgwall, + SBC, + ewall, + Ldown, + esky, + t, + F_sh, + CI, + LupE, + LupS, + LupW, + LupN, + anisotropic_sky, + ) # Box and anisotropic longwave if cyl == 0 and anisotropic_sky == 1: @@ -333,18 +624,33 @@ def Solweig_2022a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, s # # # # Calculation of radiant flux density and Tmrt # # # # # Human body considered as a cylinder with isotropic all-sky diffuse - if cyl == 1 and anisotropic_sky == 0: - Sstr = absK * (KsideI * Fcyl + (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside) + absL * \ - ((Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside) - # Human body considered as a cylinder with Perez et al. (1993) (anisotropic sky diffuse) + if cyl == 1 and anisotropic_sky == 0: + Sstr = absK * ( + KsideI * Fcyl + + (Kdown + Kup) * Fup + + (Knorth + Keast + Ksouth + Kwest) * Fside + ) + absL * ( + (Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside + ) + # Human body considered as a cylinder with Perez et al. (1993) (anisotropic sky diffuse) # and Martin and Berdahl (1984) (anisotropic sky longwave) elif cyl == 1 and anisotropic_sky == 1: - Sstr = absK * (Kside * Fcyl + (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside) + absL * \ - ((Ldown + Lup) * Fup + Lside * Fcyl + (Lnorth + Least + Lsouth + Lwest) * Fside) + Sstr = absK * ( + Kside * Fcyl + + (Kdown + Kup) * Fup + + (Knorth + Keast + Ksouth + Kwest) * Fside + ) + absL * ( + (Ldown + Lup) * Fup + + Lside * Fcyl + + (Lnorth + Least + Lsouth + Lwest) * Fside + ) # Knorth = nan Ksouth = nan Kwest = nan Keast = nan - else: # Human body considered as a standing cube - Sstr = absK * ((Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside) + absL * \ - ((Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside) + else: # Human body considered as a standing cube + Sstr = absK * ( + (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside + ) + absL * ( + (Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside + ) Tmrt = np.sqrt(np.sqrt((Sstr / (absL * SBC)))) - 273.2 @@ -355,7 +661,44 @@ def Solweig_2022a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, s Lnorth += Lnorth_ Lsouth += Lsouth_ - return Tmrt, Kdown, Kup, Ldown, Lup, Tg, ea, esky, I0, CI, shadow, firstdaytime, timestepdec, \ - timeadd, Tgmap1, Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, Keast, Ksouth, Kwest, Knorth, Least, \ - Lsouth, Lwest, Lnorth, KsideI, TgOut1, TgOut, radI, radD, \ - Lside, L_patches, CI_Tg, CI_TgG, KsideD, dRad, Kside + return ( + Tmrt, + Kdown, + Kup, + Ldown, + Lup, + Tg, + ea, + esky, + I0, + CI, + shadow, + firstdaytime, + timestepdec, + timeadd, + Tgmap1, + Tgmap1E, + Tgmap1S, + Tgmap1W, + Tgmap1N, + Keast, + Ksouth, + Kwest, + Knorth, + Least, + Lsouth, + Lwest, + Lnorth, + KsideI, + TgOut1, + TgOut, + radI, + radD, + Lside, + L_patches, + CI_Tg, + CI_TgG, + KsideD, + dRad, + Kside, + ) diff --git a/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py b/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py index 5d97978..adcecaf 100644 --- a/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py +++ b/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py @@ -2,14 +2,21 @@ import numpy as np from .daylen import daylen -from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import clearnessindex_2013b +from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import ( + clearnessindex_2013b, +) from ...util.SEBESOLWEIGCommonFiles.diffusefraction import diffusefraction -from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_13 import shadowingfunction_wallheight_13 -from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_23 import shadowingfunction_wallheight_23 +from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_13 import ( + shadowingfunction_wallheight_13, +) +from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_23 import ( + shadowingfunction_wallheight_23, +) from .gvf_2018a import gvf_2018a from .cylindric_wedge import cylindric_wedge from .TsWaveDelay_2015a import TsWaveDelay_2015a from .Kup_veg_2015a import Kup_veg_2015a + # from .Lside_veg_v2015a import Lside_veg_v2015a # from .Kside_veg_v2019a import Kside_veg_v2019a from .Kside_veg_v2022a import Kside_veg_v2022a @@ -27,22 +34,109 @@ # Wall surface temperature scheme from .wall_surface_temperature import wall_surface_temperature -def Solweig_2025a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, svfveg, svfNveg, svfEveg, svfSveg, - svfWveg, svfaveg, svfEaveg, svfSaveg, svfWaveg, svfNaveg, vegdem, vegdem2, albedo_b, absK, absL, - ewall, Fside, Fup, Fcyl, altitude, azimuth, zen, jday, usevegdem, onlyglobal, buildings, location, psi, - landcover, lc_grid, dectime, altmax, dirwalls, walls, cyl, elvis, Ta, RH, radG, radD, radI, P, - amaxvalue, bush, Twater, TgK, Tstart, alb_grid, emis_grid, TgK_wall, Tstart_wall, TmaxLST, - TmaxLST_wall, first, second, svfalfa, svfbuveg, firstdaytime, timeadd, timestepdec, Tgmap1, - Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, CI, TgOut1, diffsh, shmat, vegshmat, vbshvegshmat, anisotropic_sky, asvf, patch_option, - voxelMaps, voxelTable, ws, wallScheme, timeStep, steradians, walls_scheme, dirwalls_scheme): - -#def Solweig_2021a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, svfveg, svfNveg, svfEveg, svfSveg, -# svfWveg, svfaveg, svfEaveg, svfSaveg, svfWaveg, svfNaveg, vegdem, vegdem2, albedo_b, absK, absL, -# ewall, Fside, Fup, Fcyl, altitude, azimuth, zen, jday, usevegdem, onlyglobal, buildings, location, psi, -# landcover, lc_grid, dectime, altmax, dirwalls, walls, cyl, elvis, Ta, RH, radG, radD, radI, P, -# amaxvalue, bush, Twater, TgK, Tstart, alb_grid, emis_grid, TgK_wall, Tstart_wall, TmaxLST, -# TmaxLST_wall, first, second, svfalfa, svfbuveg, firstdaytime, timeadd, timestepdec, Tgmap1, -# Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, CI, TgOut1, diffsh, ani): + +def Solweig_2025a_calc( + i, + dsm, + scale, + rows, + cols, + svf, + svfN, + svfW, + svfE, + svfS, + svfveg, + svfNveg, + svfEveg, + svfSveg, + svfWveg, + svfaveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + vegdem, + vegdem2, + albedo_b, + absK, + absL, + ewall, + Fside, + Fup, + Fcyl, + altitude, + azimuth, + zen, + jday, + usevegdem, + onlyglobal, + buildings, + location, + psi, + landcover, + lc_grid, + dectime, + altmax, + dirwalls, + walls, + cyl, + elvis, + Ta, + RH, + radG, + radD, + radI, + P, + amaxvalue, + bush, + Twater, + TgK, + Tstart, + alb_grid, + emis_grid, + TgK_wall, + Tstart_wall, + TmaxLST, + TmaxLST_wall, + first, + second, + svfalfa, + svfbuveg, + firstdaytime, + timeadd, + timestepdec, + Tgmap1, + Tgmap1E, + Tgmap1S, + Tgmap1W, + Tgmap1N, + CI, + TgOut1, + diffsh, + shmat, + vegshmat, + vbshvegshmat, + anisotropic_sky, + asvf, + patch_option, + voxelMaps, + voxelTable, + ws, + wallScheme, + timeStep, + steradians, + walls_scheme, + dirwalls_scheme, +): + + # def Solweig_2021a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, svfveg, svfNveg, svfEveg, svfSveg, + # svfWveg, svfaveg, svfEaveg, svfSaveg, svfWaveg, svfNaveg, vegdem, vegdem2, albedo_b, absK, absL, + # ewall, Fside, Fup, Fcyl, altitude, azimuth, zen, jday, usevegdem, onlyglobal, buildings, location, psi, + # landcover, lc_grid, dectime, altmax, dirwalls, walls, cyl, elvis, Ta, RH, radG, radD, radI, P, + # amaxvalue, bush, Twater, TgK, Tstart, alb_grid, emis_grid, TgK_wall, Tstart_wall, TmaxLST, + # TmaxLST_wall, first, second, svfalfa, svfbuveg, firstdaytime, timeadd, timestepdec, Tgmap1, + # Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, CI, TgOut1, diffsh, ani): # This is the core function of the SOLWEIG model # 2016-Aug-28 @@ -93,46 +187,52 @@ def Solweig_2025a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, s # amaxvalue = max height of buildings # bush = grid representing bushes # Twater = temperature of water (daily) - # TgK, Tstart, TgK_wall, Tstart_wall, TmaxLST,TmaxLST_wall, + # TgK, Tstart, TgK_wall, Tstart_wall, TmaxLST,TmaxLST_wall, # alb_grid, emis_grid = albedo and emmissivity on ground # first, second = conneted to old Ts model (source area based on Smidt et al.) # svfalfa = SVF recalculated to angle - # svfbuveg = complete SVF - # firstdaytime, timeadd, timestepdec, Tgmap1, Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, + # svfbuveg = complete SVF + # firstdaytime, timeadd, timestepdec, Tgmap1, Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, # CI = Clearness index # TgOut1 = old Ts model # diffsh, ani = Used in anisotrpic models (Wallenberg et al. 2019, 2022) # # # Core program start # # # # Instrument offset in degrees - t = 0. + t = 0.0 # Stefan Bolzmans Constant SBC = 5.67051e-8 # Degrees to radians - deg2rad = np.pi/180 + deg2rad = np.pi / 180 # Find sunrise decimal hour - new from 2014a - _, _, _, SNUP = daylen(jday, location['latitude']) + _, _, _, SNUP = daylen(jday, location["latitude"]) # Vapor pressure - ea = 6.107 * 10 ** ((7.5 * Ta) / (237.3 + Ta)) * (RH / 100.) + ea = 6.107 * 10 ** ((7.5 * Ta) / (237.3 + Ta)) * (RH / 100.0) # Determination of clear - sky emissivity from Prata (1996) msteg = 46.5 * (ea / (Ta + 273.15)) - esky = (1 - (1 + msteg) * np.exp(-((1.2 + 3.0 * msteg) ** 0.5))) + elvis # -0.04 old error from Jonsson et al.2006 + esky = ( + 1 - (1 + msteg) * np.exp(-((1.2 + 3.0 * msteg) ** 0.5)) + ) + elvis # -0.04 old error from Jonsson et al.2006 - if altitude > 0: # # # # # # DAYTIME # # # # # # + if altitude > 0: # # # # # # DAYTIME # # # # # # # Clearness Index on Earth's surface after Crawford and Dunchon (1999) with a correction # factor for low sun elevations after Lindberg et al.(2008) - I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b(zen, jday, Ta, RH / 100., radG, location, P) + I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( + zen, jday, Ta, RH / 100.0, radG, location, P + ) if (CI > 1) or (CI == np.inf): CI = 1 # Estimation of radD and radI if not measured after Reindl et al.(1990) if onlyglobal == 1: - I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b(zen, jday, Ta, RH / 100., radG, location, P) + I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( + zen, jday, Ta, RH / 100.0, radG, location, P + ) if (CI > 1) or (CI == np.inf): CI = 1 @@ -142,15 +242,19 @@ def Solweig_2025a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, s # Anisotropic Diffuse Radiation after Perez et al. 1993 if anisotropic_sky == 1: patchchoice = 1 - zenDeg = zen*(180/np.pi) + zenDeg = zen * (180 / np.pi) # Relative luminance - lv, pc_, pb_ = Perez_v3(zenDeg, azimuth, radD, radI, jday, patchchoice, patch_option) + lv, pc_, pb_ = Perez_v3( + zenDeg, azimuth, radD, radI, jday, patchchoice, patch_option + ) # Total relative luminance from sky, i.e. from each patch, into each cell aniLum = np.zeros((rows, cols)) for idx in range(lv.shape[0]): - aniLum += diffsh[:,:,idx] * lv[idx,2] + aniLum += diffsh[:, :, idx] * lv[idx, 2] - dRad = aniLum * radD # Total diffuse radiation from sky into each cell + dRad = ( + aniLum * radD + ) # Total diffuse radiation from sky into each cell else: dRad = radD * svfbuveg patchchoice = 1 @@ -158,30 +262,70 @@ def Solweig_2025a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, s # Shadow images if usevegdem == 1: - vegsh, sh, _, wallsh, wallsun, wallshve, _, facesun, wallsh_ = shadowingfunction_wallheight_23(dsm, vegdem, vegdem2, - azimuth, altitude, scale, amaxvalue, bush, walls, dirwalls * np.pi / 180., walls_scheme, dirwalls_scheme * np.pi/180.) + vegsh, sh, _, wallsh, wallsun, wallshve, _, facesun, wallsh_ = ( + shadowingfunction_wallheight_23( + dsm, + vegdem, + vegdem2, + azimuth, + altitude, + scale, + amaxvalue, + bush, + walls, + dirwalls * np.pi / 180.0, + walls_scheme, + dirwalls_scheme * np.pi / 180.0, + ) + ) shadow = sh - (1 - vegsh) * (1 - psi) else: - sh, wallsh, wallsun, facesh, facesun, wallsh_ = shadowingfunction_wallheight_13(dsm, azimuth, altitude, scale, - walls, dirwalls * np.pi / 180., walls_scheme, dirwalls_scheme * np.pi/180.) + sh, wallsh, wallsun, facesh, facesun, wallsh_ = ( + shadowingfunction_wallheight_13( + dsm, + azimuth, + altitude, + scale, + walls, + dirwalls * np.pi / 180.0, + walls_scheme, + dirwalls_scheme * np.pi / 180.0, + ) + ) shadow = sh # # # Surface temperature parameterisation during daytime # # # # # new using max sun alt.instead of dfm # Tgamp = (TgK * altmax - Tstart) + Tstart # Old - Tgamp = TgK * altmax + Tstart # Fixed 2021 + Tgamp = TgK * altmax + Tstart # Fixed 2021 # Tgampwall = (TgK_wall * altmax - (Tstart_wall)) + (Tstart_wall) # Old Tgampwall = TgK_wall * altmax + Tstart_wall - Tg = Tgamp * np.sin((((dectime - np.floor(dectime)) - SNUP / 24) / (TmaxLST / 24 - SNUP / 24)) * np.pi / 2) # 2015 a, based on max sun altitude - Tgwall = Tgampwall * np.sin((((dectime - np.floor(dectime)) - SNUP / 24) / (TmaxLST_wall / 24 - SNUP / 24)) * np.pi / 2) # 2015a, based on max sun altitude + Tg = Tgamp * np.sin( + ( + ((dectime - np.floor(dectime)) - SNUP / 24) + / (TmaxLST / 24 - SNUP / 24) + ) + * np.pi + / 2 + ) # 2015 a, based on max sun altitude + Tgwall = Tgampwall * np.sin( + ( + ((dectime - np.floor(dectime)) - SNUP / 24) + / (TmaxLST_wall / 24 - SNUP / 24) + ) + * np.pi + / 2 + ) # 2015a, based on max sun altitude if Tgwall < 0: # temporary for removing low Tg during morning 20130205 # Tg = 0 Tgwall = 0 # New estimation of Tg reduction for non - clear situation based on Reindl et al.1990 - radI0, _ = diffusefraction(I0, altitude, 1., Ta, RH) - corr = 0.1473 * np.log(90 - (zen / np.pi * 180)) + 0.3454 # 20070329 correction of lat, Lindberg et al. 2008 + radI0, _ = diffusefraction(I0, altitude, 1.0, Ta, RH) + corr = ( + 0.1473 * np.log(90 - (zen / np.pi * 180)) + 0.3454 + ) # 20070329 correction of lat, Lindberg et al. 2008 CI_Tg = (radG / radI0) + (1 - corr) if (CI_Tg > 1) or (CI_Tg == np.inf): CI_Tg = 1 @@ -190,47 +334,151 @@ def Solweig_2025a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, s CI_TgG = (radG / radG0) + (1 - corr) if (CI_TgG > 1) or (CI_TgG == np.inf): CI_TgG = 1 - + # Tg = Tg * CI_Tg # new estimation # Tgwall = Tgwall * CI_Tg Tg = Tg * CI_TgG # new estimation Tgwall = Tgwall * CI_TgG if landcover == 1: - Tg[Tg < 0] = 0 # temporary for removing low Tg during morning 20130205 + Tg[Tg < 0] = ( + 0 # temporary for removing low Tg during morning 20130205 + ) # # # # Ground View Factors # # # # - gvfLup, gvfalb, gvfalbnosh, gvfLupE, gvfalbE, gvfalbnoshE, gvfLupS, gvfalbS, gvfalbnoshS, gvfLupW, gvfalbW,\ - gvfalbnoshW, gvfLupN, gvfalbN, gvfalbnoshN, gvfSum, gvfNorm = gvf_2018a(wallsun, walls, buildings, scale, shadow, first, - second, dirwalls, Tg, Tgwall, Ta, emis_grid, ewall, alb_grid, SBC, albedo_b, rows, cols, - Twater, lc_grid, landcover) + ( + gvfLup, + gvfalb, + gvfalbnosh, + gvfLupE, + gvfalbE, + gvfalbnoshE, + gvfLupS, + gvfalbS, + gvfalbnoshS, + gvfLupW, + gvfalbW, + gvfalbnoshW, + gvfLupN, + gvfalbN, + gvfalbnoshN, + gvfSum, + gvfNorm, + ) = gvf_2018a( + wallsun, + walls, + buildings, + scale, + shadow, + first, + second, + dirwalls, + Tg, + Tgwall, + Ta, + emis_grid, + ewall, + alb_grid, + SBC, + albedo_b, + rows, + cols, + Twater, + lc_grid, + landcover, + ) # # # # Lup, daytime # # # # # Surface temperature wave delay - new as from 2014a - Lup, timeaddnotused, Tgmap1 = TsWaveDelay_2015a(gvfLup, firstdaytime, timeadd, timestepdec, Tgmap1) - LupE, timeaddnotused, Tgmap1E = TsWaveDelay_2015a(gvfLupE, firstdaytime, timeadd, timestepdec, Tgmap1E) - LupS, timeaddnotused, Tgmap1S = TsWaveDelay_2015a(gvfLupS, firstdaytime, timeadd, timestepdec, Tgmap1S) - LupW, timeaddnotused, Tgmap1W = TsWaveDelay_2015a(gvfLupW, firstdaytime, timeadd, timestepdec, Tgmap1W) - LupN, timeaddnotused, Tgmap1N = TsWaveDelay_2015a(gvfLupN, firstdaytime, timeadd, timestepdec, Tgmap1N) - + Lup, timeaddnotused, Tgmap1 = TsWaveDelay_2015a( + gvfLup, firstdaytime, timeadd, timestepdec, Tgmap1 + ) + LupE, timeaddnotused, Tgmap1E = TsWaveDelay_2015a( + gvfLupE, firstdaytime, timeadd, timestepdec, Tgmap1E + ) + LupS, timeaddnotused, Tgmap1S = TsWaveDelay_2015a( + gvfLupS, firstdaytime, timeadd, timestepdec, Tgmap1S + ) + LupW, timeaddnotused, Tgmap1W = TsWaveDelay_2015a( + gvfLupW, firstdaytime, timeadd, timestepdec, Tgmap1W + ) + LupN, timeaddnotused, Tgmap1N = TsWaveDelay_2015a( + gvfLupN, firstdaytime, timeadd, timestepdec, Tgmap1N + ) + # # For Tg output in POIs TgTemp = Tg * shadow + Ta - TgOut, timeadd, TgOut1 = TsWaveDelay_2015a(TgTemp, firstdaytime, timeadd, timestepdec, TgOut1) #timeadd only here v2021a + TgOut, timeadd, TgOut1 = TsWaveDelay_2015a( + TgTemp, firstdaytime, timeadd, timestepdec, TgOut1 + ) # timeadd only here v2021a # Building height angle from svf - F_sh = cylindric_wedge(zen, svfalfa, rows, cols) # Fraction shadow on building walls based on sun alt and svf + F_sh = cylindric_wedge( + zen, svfalfa, rows, cols + ) # Fraction shadow on building walls based on sun alt and svf F_sh[np.isnan(F_sh)] = 0.5 # # # # # # # Calculation of shortwave daytime radiative fluxes # # # # # # # - Kdown = radI * shadow * np.sin(altitude * (np.pi / 180)) + dRad + albedo_b * (1 - svfbuveg) * \ - (radG * (1 - F_sh) + radD * F_sh) # *sin(altitude(i) * (pi / 180)) - - Kup, KupE, KupS, KupW, KupN = Kup_veg_2015a(radI, radD, radG, altitude, svfbuveg, albedo_b, F_sh, gvfalb, - gvfalbE, gvfalbS, gvfalbW, gvfalbN, gvfalbnosh, gvfalbnoshE, gvfalbnoshS, gvfalbnoshW, gvfalbnoshN) + Kdown = ( + radI * shadow * np.sin(altitude * (np.pi / 180)) + + dRad + + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) + ) # *sin(altitude(i) * (pi / 180)) + + Kup, KupE, KupS, KupW, KupN = Kup_veg_2015a( + radI, + radD, + radG, + altitude, + svfbuveg, + albedo_b, + F_sh, + gvfalb, + gvfalbE, + gvfalbS, + gvfalbW, + gvfalbN, + gvfalbnosh, + gvfalbnoshE, + gvfalbnoshS, + gvfalbnoshW, + gvfalbnoshN, + ) + + Keast, Ksouth, Kwest, Knorth, KsideI, KsideD, Kside = Kside_veg_v2022a( + radI, + radD, + radG, + shadow, + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + azimuth, + altitude, + psi, + t, + albedo_b, + F_sh, + KupE, + KupS, + KupW, + KupN, + cyl, + lv, + anisotropic_sky, + diffsh, + rows, + cols, + asvf, + shmat, + vegshmat, + vbshvegshmat, + ) - Keast, Ksouth, Kwest, Knorth, KsideI, KsideD, Kside = Kside_veg_v2022a(radI, radD, radG, shadow, svfS, svfW, svfN, svfE, - svfEveg, svfSveg, svfWveg, svfNveg, azimuth, altitude, psi, t, albedo_b, F_sh, KupE, KupS, KupW, - KupN, cyl, lv, anisotropic_sky, diffsh, rows, cols, asvf, shmat, vegshmat, vbshvegshmat) - firstdaytime = 0 else: # # # # # # # NIGHTTIME # # # # # # # # @@ -253,13 +501,15 @@ def Solweig_2025a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, s shadow = np.zeros((rows, cols)) CI_Tg = deepcopy(CI) CI_TgG = deepcopy(CI) - dRad = np.zeros((rows,cols)) - Kside = np.zeros((rows,cols)) + dRad = np.zeros((rows, cols)) + Kside = np.zeros((rows, cols)) # # # # Lup # # # # Lup = SBC * emis_grid * ((Knight + Ta + Tg + 273.15) ** 4) if landcover == 1: - Lup[lc_grid == 3] = SBC * 0.98 * (Twater + 273.15) ** 4 # nocturnal Water temp + Lup[lc_grid == 3] = ( + SBC * 0.98 * (Twater + 273.15) ** 4 + ) # nocturnal Water temp LupE = Lup LupS = Lup @@ -274,33 +524,81 @@ def Solweig_2025a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, s firstdaytime = 1 # # # # Ldown # # # # - Ldown = (svf + svfveg - 1) * esky * SBC * ((Ta + 273.15) ** 4) + (2 - svfveg - svfaveg) * ewall * SBC * \ - ((Ta + 273.15) ** 4) + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + \ - (2 - svf - svfveg) * (1 - ewall) * esky * SBC * ((Ta + 273.15) ** 4) # Jonsson et al.(2006) + Ldown = ( + (svf + svfveg - 1) * esky * SBC * ((Ta + 273.15) ** 4) + + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) + + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + + (2 - svf - svfveg) * (1 - ewall) * esky * SBC * ((Ta + 273.15) ** 4) + ) # Jonsson et al.(2006) # Ldown = Ldown - 25 # Shown by Jonsson et al.(2006) and Duarte et al.(2006) if CI < 0.95: # non - clear conditions c = 1 - CI - Ldown = Ldown * (1 - c) + c * ((svf + svfveg - 1) * SBC * ((Ta + 273.15) ** 4) + (2 - svfveg - svfaveg) * - ewall * SBC * ((Ta + 273.15) ** 4) + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + - (2 - svf - svfveg) * (1 - ewall) * SBC * ((Ta + 273.15) ** 4)) # NOT REALLY TESTED!!! BUT MORE CORRECT? + Ldown = Ldown * (1 - c) + c * ( + (svf + svfveg - 1) * SBC * ((Ta + 273.15) ** 4) + + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) + + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + + (2 - svf - svfveg) * (1 - ewall) * SBC * ((Ta + 273.15) ** 4) + ) # NOT REALLY TESTED!!! BUT MORE CORRECT? # # # # Lside # # # # - Least, Lsouth, Lwest, Lnorth = Lside_veg_v2022a(svfS, svfW, svfN, svfE, svfEveg, svfSveg, svfWveg, svfNveg, - svfEaveg, svfSaveg, svfWaveg, svfNaveg, azimuth, altitude, Ta, Tgwall, SBC, ewall, Ldown, - esky, t, F_sh, CI, LupE, LupS, LupW, LupN, anisotropic_sky) + Least, Lsouth, Lwest, Lnorth = Lside_veg_v2022a( + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + azimuth, + altitude, + Ta, + Tgwall, + SBC, + ewall, + Ldown, + esky, + t, + F_sh, + CI, + LupE, + LupS, + LupW, + LupN, + anisotropic_sky, + ) # New parameterization scheme for wall temperatures if wallScheme == 1: # albedo_g = 0.15 #TODO Change to correct if altitude < 0: wallsh_ = 0 - voxelTable = wall_surface_temperature(voxelTable, wallsh_, altitude, azimuth, timeStep, radI, radD, radG, Ldown, Lup, Ta, esky) + voxelTable = wall_surface_temperature( + voxelTable, + wallsh_, + altitude, + azimuth, + timeStep, + radI, + radD, + radG, + Ldown, + Lup, + Ta, + esky, + ) # Anisotropic sky - if (anisotropic_sky == 1): - if 'lv' not in locals(): + if anisotropic_sky == 1: + if "lv" not in locals(): # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) - skyvaultalt, skyvaultazi, _, _, _, _, _ = create_patches(patch_option) + skyvaultalt, skyvaultazi, _, _, _, _, _ = create_patches( + patch_option + ) patch_emissivities = np.zeros(skyvaultalt.shape[0]) @@ -320,17 +618,70 @@ def Solweig_2025a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, s # Create lv from L_patches if nighttime, i.e. lv does not exist if altitude < 0: # CI = deepcopy(CI) - lv = deepcopy(L_patches); KupE = 0; KupS = 0; KupW = 0; KupN = 0 + lv = deepcopy(L_patches) + KupE = 0 + KupS = 0 + KupW = 0 + KupN = 0 # Adjust sky emissivity under semi-cloudy/hazy/cloudy/overcast conditions, i.e. CI lower than 0.95 if CI < 0.95: - esky_c = CI * esky + (1 - CI) * 1. + esky_c = CI * esky + (1 - CI) * 1.0 esky = esky_c - Ldown, Lside, Lside_sky, Lside_veg, Lside_sh, Lside_sun, Lside_ref, Least_, Lwest_, Lnorth_, Lsouth_, \ - Keast, Ksouth, Kwest, Knorth, KsideI, KsideD, Kside, steradians, skyalt = ani_sky(shmat, vegshmat, vbshvegshmat, altitude, azimuth, asvf, cyl, esky, - L_patches, wallScheme, voxelTable, voxelMaps, steradians, Ta, Tgwall, ewall, Lup, radI, radD, radG, lv, - albedo_b, 0, diffsh, shadow, KupE, KupS, KupW, KupN, i) + ( + Ldown, + Lside, + Lside_sky, + Lside_veg, + Lside_sh, + Lside_sun, + Lside_ref, + Least_, + Lwest_, + Lnorth_, + Lsouth_, + Keast, + Ksouth, + Kwest, + Knorth, + KsideI, + KsideD, + Kside, + steradians, + skyalt, + ) = ani_sky( + shmat, + vegshmat, + vbshvegshmat, + altitude, + azimuth, + asvf, + cyl, + esky, + L_patches, + wallScheme, + voxelTable, + voxelMaps, + steradians, + Ta, + Tgwall, + ewall, + Lup, + radI, + radD, + radG, + lv, + albedo_b, + 0, + diffsh, + shadow, + KupE, + KupS, + KupW, + KupN, + i, + ) else: Lside = np.zeros((rows, cols)) L_patches = None @@ -344,18 +695,33 @@ def Solweig_2025a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, s # # # # Calculation of radiant flux density and Tmrt # # # # # Human body considered as a cylinder with isotropic all-sky diffuse - if cyl == 1 and anisotropic_sky == 0: - Sstr = absK * (KsideI * Fcyl + (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside) + absL * \ - ((Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside) - # Human body considered as a cylinder with Perez et al. (1993) (anisotropic sky diffuse) + if cyl == 1 and anisotropic_sky == 0: + Sstr = absK * ( + KsideI * Fcyl + + (Kdown + Kup) * Fup + + (Knorth + Keast + Ksouth + Kwest) * Fside + ) + absL * ( + (Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside + ) + # Human body considered as a cylinder with Perez et al. (1993) (anisotropic sky diffuse) # and Martin and Berdahl (1984) (anisotropic sky longwave) elif cyl == 1 and anisotropic_sky == 1: - Sstr = absK * (Kside * Fcyl + (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside) + absL * \ - ((Ldown + Lup) * Fup + Lside * Fcyl + (Lnorth + Least + Lsouth + Lwest) * Fside) + Sstr = absK * ( + Kside * Fcyl + + (Kdown + Kup) * Fup + + (Knorth + Keast + Ksouth + Kwest) * Fside + ) + absL * ( + (Ldown + Lup) * Fup + + Lside * Fcyl + + (Lnorth + Least + Lsouth + Lwest) * Fside + ) # Knorth = nan Ksouth = nan Kwest = nan Keast = nan - else: # Human body considered as a standing cube - Sstr = absK * ((Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside) + absL * \ - ((Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside) + else: # Human body considered as a standing cube + Sstr = absK * ( + (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside + ) + absL * ( + (Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside + ) Tmrt = np.sqrt(np.sqrt((Sstr / (absL * SBC)))) - 273.2 @@ -366,7 +732,46 @@ def Solweig_2025a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, s Lnorth += Lnorth_ Lsouth += Lsouth_ - return Tmrt, Kdown, Kup, Ldown, Lup, Tg, ea, esky, I0, CI, shadow, firstdaytime, timestepdec, \ - timeadd, Tgmap1, Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, Keast, Ksouth, Kwest, Knorth, Least, \ - Lsouth, Lwest, Lnorth, KsideI, TgOut1, TgOut, radI, radD, \ - Lside, L_patches, CI_Tg, CI_TgG, KsideD, dRad, Kside, steradians, voxelTable + return ( + Tmrt, + Kdown, + Kup, + Ldown, + Lup, + Tg, + ea, + esky, + I0, + CI, + shadow, + firstdaytime, + timestepdec, + timeadd, + Tgmap1, + Tgmap1E, + Tgmap1S, + Tgmap1W, + Tgmap1N, + Keast, + Ksouth, + Kwest, + Knorth, + Least, + Lsouth, + Lwest, + Lnorth, + KsideI, + TgOut1, + TgOut, + radI, + radD, + Lside, + L_patches, + CI_Tg, + CI_TgG, + KsideD, + dRad, + Kside, + steradians, + voxelTable, + ) diff --git a/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py new file mode 100644 index 0000000..37539af --- /dev/null +++ b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py @@ -0,0 +1,950 @@ +""" +@author Fredrik Lindberg, University of Gothenburg +""" + +from __future__ import absolute_import + +import numpy as np +import matplotlib.pyplot as plt +from .daylen import daylen +from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import ( + clearnessindex_2013b, +) +from ...util.SEBESOLWEIGCommonFiles.diffusefraction import diffusefraction +from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_13 import ( + shadowingfunction_wallheight_13, +) +from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_23 import ( + shadowingfunction_wallheight_23, +) +from .gvf_2018a import gvf_2018a +from .cylindric_wedge import cylindric_wedge +from .TsWaveDelay_2015a import TsWaveDelay_2015a +from .Kup_veg_2015a import Kup_veg_2015a + +# from .Lside_veg_v2015a import Lside_veg_v2015a +# from .Kside_veg_v2019a import Kside_veg_v2019a +from .Kside_veg_v2022a import Kside_veg_v2022a +from ...util.SEBESOLWEIGCommonFiles.Perez_v3 import Perez_v3 +from ...util.SEBESOLWEIGCommonFiles.create_patches import create_patches + +# Anisotropic longwave +from .Lcyl_v2022a import Lcyl_v2022a +from .Lside_veg import Lside_veg_v2022a, Lside_veg_v2026 +from .anisotropic_sky import anisotropic_sky as ani_sky +from .patch_radiation import patch_steradians +from copy import deepcopy +import time + +# Wall surface temperature scheme +from .wall_surface_temperature import wall_surface_temperature + +# Ground surface temperature +from .ground_surface import surfaceTemperature_calc, outgoingLongwave_calc + + +def Solweig_2026a_calc( + i, + dsm, + scale, + rows, + cols, + svf, + svfN, + svfW, + svfE, + svfS, + svfveg, + svfNveg, + svfEveg, + svfSveg, + svfWveg, + svfaveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + vegdem, + vegdem2, + albedo_b, + absK, + absL, + ewall, + Fside, + Fup, + Fcyl, + altitude, + azimuth, + zen, + jday, + usevegdem, + onlyglobal, + buildings, + location, + psi, + landcover, + lc_grid, + dectime, + altmax, + dirwalls, + walls, + cyl, + elvis, + Ta, + RH, + radG, + radD, + radI, + P, + amaxvalue, + bush, + Twater, + TgK, + Tstart, + alb_grid, + emis_grid, + TgK_wall, + Tstart_wall, + TmaxLST, + TmaxLST_wall, + first, + second, + svfalfa, + svfbuveg, + firstdaytime, + timeadd, + timestepdec, + Tgmap1, + Tgmap1E, + Tgmap1S, + Tgmap1W, + Tgmap1N, + CI, + diffsh, + shmat, + vegshmat, + vbshvegshmat, + anisotropic_sky, + asvf, + patch_option, + voxelMaps, + voxelTable, + ws, + wallScheme, + timeStep, + steradians, + walls_scheme, + dirwalls_scheme, + groundScheme, + Tg, + Rn, + Rn_past, + G, + Tm, + cap_grid, + diff_grid, + a1_grid, + a2_grid, + a3_grid, + shadow_past, +): + """ + This is the core function of the SOLWEIG model + 2016-Aug-28 + Fredrik Lindberg, fredrikl@gvc.gu.se + Goteborg Urban Climate Group + Gothenburg University + + :Input variables: + dsm = digital surface model + scale = height to pixel size (2m pixel gives scale = 0.5) + svf,svfN,svfW,svfE,svfS = SVFs for building and ground + svfveg,svfNveg,svfEveg,svfSveg,svfWveg = Veg SVFs blocking sky + svfaveg,svfEaveg,svfSaveg,svfWaveg,svfNaveg = Veg SVFs blocking buildings + vegdem = Vegetation canopy DSM + vegdem2 = Vegetation trunk zone DSM + albedo_b = building wall albedo + absK = human absorption coefficient for shortwave radiation + absL = human absorption coefficient for longwave radiation + ewall = Emissivity of building walls + Fside = The angular factors between a person and the surrounding surfaces + Fup = The angular factors between a person and the surrounding surfaces + Fcyl = The angular factors between a culidric person and the surrounding surfaces + altitude = Sun altitude (degree) + azimuth = Sun azimuth (degree) + zen = Sun zenith angle (radians) + jday = day of year + usevegdem = use vegetation scheme + onlyglobal = calculate dir and diff from global shortwave (Reindl et al. 1990) + buildings = Boolena grid to identify building pixels + location = geographic location + height = height of measurements point (center of gravity of human) + psi = 1 - Transmissivity of shortwave through vegetation + landcover = use landcover scheme !!!NEW IN 2015a!!! + lc_grid = grid with landcoverclasses + lc_class = table with landcover properties + dectime = decimal time + altmax = maximum sun altitude + dirwalls = aspect of walls + walls = one pixel row outside building footprint. height of building walls + cyl = consider man as cylinder instead of cude + elvis = dummy + Ta = air temp + RH + radG = global radiation + radD = diffuse + radI = direct + P = pressure + amaxvalue = max height of buildings + bush = grid representing bushes + Twater = temperature of water (daily) + TgK, Tstart, TgK_wall, Tstart_wall, TmaxLST,TmaxLST_wall, + alb_grid, emis_grid = albedo and emmissivity on ground + first, second = conneted to old Ts model (source area based on Smidt et al.) + svfalfa = SVF recalculated to angle + svfbuveg = complete SVF + firstdaytime, timeadd, timestepdec, Tgmap1, Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, + CI = Clearness index + TgOut1 = old Ts model + diffsh, ani = Used in anisotrpic models (Wallenberg et al. 2019, 2022) + """ + + # # # Core program start # # # + # Instrument offset in degrees + t = 0.0 + + # Stefan Bolzmans Constant + SBC = 5.67051e-8 + + # Degrees to radians + deg2rad = np.pi / 180 + + # Find sunrise decimal hour - new from 2014a + _, _, _, SNUP = daylen(jday, location["latitude"]) + + # Vapor pressure in hPa + ea = 6.107 * 10 ** ((7.5 * Ta) / (237.3 + Ta)) * (RH / 100.0) + + # Determination of clear-sky emissivity from Prata (1996) + msteg = 46.5 * (ea / (Ta + 273.15)) + esky = ( + 1 - (1 + msteg) * np.exp(-((1.2 + 3.0 * msteg) ** 0.5)) + ) + elvis # -0.04 old error from Jonsson et al.2006 + + if altitude > 0: # # # # # # DAYTIME # # # # # # + # Clearness Index on Earth's surface after Crawford and Dunchon (1999) with a correction + # factor for low sun elevations after Lindberg et al.(2008) + I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( + zen, jday, Ta, RH / 100.0, radG, location, P + ) + if (CI > 1) or (CI == np.inf): + CI = 1 + + # Estimation of radD and radI if not measured after Reindl et al.(1990) + if onlyglobal == 1: + I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( + zen, jday, Ta, RH / 100.0, radG, location, P + ) + if (CI > 1) or (CI == np.inf): + CI = 1 + + radI, radD = diffusefraction(radG, altitude, Kt, Ta, RH) + + # Diffuse Radiation + # Anisotropic Diffuse Radiation after Perez et al. 1993 + if anisotropic_sky == 1: + patchchoice = 1 + zenDeg = zen * (180 / np.pi) + # Relative luminance + lv, pc_, pb_ = Perez_v3( + zenDeg, azimuth, radD, radI, jday, patchchoice, patch_option + ) + # Total relative luminance from sky, i.e. from each patch, into each cell + aniLum = np.zeros((rows, cols)) + for idx in range(lv.shape[0]): + aniLum += diffsh[:, :, idx] * lv[idx, 2] + + dRad = ( + aniLum * radD + ) # Total diffuse radiation from sky into each cell + else: + dRad = radD * svfbuveg + patchchoice = 1 + lv = None + + # Shadow images + if usevegdem == 1: + vegsh, sh, _, wallsh, wallsun, wallshve, _, facesun, wallsh_ = ( + shadowingfunction_wallheight_23( + dsm, + vegdem, + vegdem2, + azimuth, + altitude, + scale, + amaxvalue, + bush, + walls, + dirwalls * np.pi / 180.0, + walls_scheme, + dirwalls_scheme * np.pi / 180.0, + ) + ) + shadow = sh - (1 - vegsh) * (1 - psi) + else: + sh, wallsh, wallsun, facesh, facesun, wallsh_ = ( + shadowingfunction_wallheight_13( + dsm, + azimuth, + altitude, + scale, + walls, + dirwalls * np.pi / 180.0, + walls_scheme, + dirwalls_scheme * np.pi / 180.0, + ) + ) + shadow = sh + + # Building height angle from svf + F_sh = cylindric_wedge( + zen, svfalfa, rows, cols + ) # Fraction shadow on building walls based on sun alt and svf + F_sh[np.isnan(F_sh)] = 0.5 + + # New estimation of Tgwall with reduction for non-clear situation based on Reindl et al. 1990 + radI0, _ = diffusefraction(I0, altitude, 1.0, Ta, RH) + radG0 = radI0 * (np.sin(altitude * deg2rad)) + _ + corr = ( + 0.1473 * np.log(90 - (zen / np.pi * 180)) + 0.3454 + ) # 20070329 correction of lat, Lindberg et al. 2008 + CI_TgG = (radG / radG0) + (1 - corr) + if (CI_TgG > 1) or (CI_TgG == np.inf): + CI_TgG = 1 + + Tgampwall = TgK_wall * altmax + Tstart_wall + Tgwall = Tgampwall * np.sin( + ( + ((dectime - np.floor(dectime)) - SNUP / 24) + / (TmaxLST_wall / 24 - SNUP / 24) + ) + * np.pi + / 2 + ) # 2015a, based on max sun altitude + if Tgwall < 0: # temporary for removing low Tg during morning 20130205 + Tgwall = 0 + Tgwall = Tgwall * CI_TgG + + # # # # Kdown # # # # + Kdown = ( + radI * shadow * np.sin(altitude * (np.pi / 180)) + + dRad + + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) + ) # *sin(altitude(i) * (pi / 180)) + + # # # # Ldown # # # # + Ldown = ( + (svf + svfveg - 1) * esky * SBC * ((Ta + 273.15) ** 4) + + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) + + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + + (2 - svf - svfveg) + * (1 - ewall) + * esky + * SBC + * ((Ta + 273.15) ** 4) + ) # Jonsson et al.(2006) + # Ldown = Ldown - 25 # Shown by Jonsson et al.(2006) and Duarte et al.(2006) + if CI < 0.95: # non - clear conditions + c = 1 - CI + Ldown = Ldown * (1 - c) + c * ( + (svf + svfveg - 1) * SBC * ((Ta + 273.15) ** 4) + + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) + + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + + (2 - svf - svfveg) * (1 - ewall) * SBC * ((Ta + 273.15) ** 4) + ) # NOT REALLY TESTED!!! BUT MORE CORRECT? + + # Surface temperature parameterization during daytime + if groundScheme == 1: + # calculate the ground surface temperature, and relevant heat fluxes + Tg, Rn, Rn_past, G = surfaceTemperature_calc( + Kdown, + Ldown, + Rn, + Rn_past, + G, + Tg, + Tm, + alb_grid, + emis_grid, + cap_grid, + diff_grid, + lc_grid, + a1_grid, + a2_grid, + a3_grid, + timeStep, + RH, + shadow, + shadow_past, + ) + + # # # # Lup, daytime # # # # + ( + Lup, + gvfalb, + gvfalbnosh, + LupE, + gvfalbE, + gvfalbnoshE, + LupS, + gvfalbS, + gvfalbnoshS, + LupW, + gvfalbW, + gvfalbnoshW, + LupN, + gvfalbN, + gvfalbnoshN, + gvfLsideW, + gvfLsideS, + gvfLsideE, + gvfLsideN, + ) = outgoingLongwave_calc( + Tg, + Tgwall, + Ta, + Ldown, + emis_grid, + alb_grid, + buildings, + shadow, + wallsun, + walls, + rows, + cols, + 1 / scale, + ) + + else: + # using max sun alt instead of dfm + Tgamp = TgK * altmax + Tstart # Fixed 2021 + Tgdiff = Tgamp * np.sin( + ( + ((dectime - np.floor(dectime)) - SNUP / 24) + / (TmaxLST / 24 - SNUP / 24) + ) + * np.pi + / 2 + ) # 2015 a, based on max sun altitude + + Tgdiff = Tgdiff * CI_TgG # new estimation + + # For Tg output in POIs + TgTemp = Tgdiff * shadow + Ta + _, timeadd, Tg = TsWaveDelay_2015a( + TgTemp, firstdaytime, timeadd, timestepdec, Tg + ) # timeadd only here v2021a + + if landcover == 1: + Tg[Tg < 0] = ( + 0 # temporary for removing low Tg during morning 20130205 + ) + + ### Ground View Factors + ( + gvfLup, + gvfalb, + gvfalbnosh, + gvfLupE, + gvfalbE, + gvfalbnoshE, + gvfLupS, + gvfalbS, + gvfalbnoshS, + gvfLupW, + gvfalbW, + gvfalbnoshW, + gvfLupN, + gvfalbN, + gvfalbnoshN, + gvfSum, + gvfNorm, + ) = gvf_2018a( + wallsun, + walls, + buildings, + scale, + shadow, + first, + second, + dirwalls, + Tg, + Tgwall, + Ta, + emis_grid, + ewall, + alb_grid, + SBC, + albedo_b, + rows, + cols, + Twater, + lc_grid, + landcover, + ) + + # # # # Lup, daytime # # # # + # Surface temperature wave delay - new as from 2014a + Lup, timeaddnotused, Tgmap1 = TsWaveDelay_2015a( + gvfLup, firstdaytime, timeadd, timestepdec, Tgmap1 + ) + LupE, timeaddnotused, Tgmap1E = TsWaveDelay_2015a( + gvfLupE, firstdaytime, timeadd, timestepdec, Tgmap1E + ) + LupS, timeaddnotused, Tgmap1S = TsWaveDelay_2015a( + gvfLupS, firstdaytime, timeadd, timestepdec, Tgmap1S + ) + LupW, timeaddnotused, Tgmap1W = TsWaveDelay_2015a( + gvfLupW, firstdaytime, timeadd, timestepdec, Tgmap1W + ) + LupN, timeaddnotused, Tgmap1N = TsWaveDelay_2015a( + gvfLupN, firstdaytime, timeadd, timestepdec, Tgmap1N + ) + + # # # # Kup # # # # + Kup, KupE, KupS, KupW, KupN = Kup_veg_2015a( + radI, + radD, + radG, + altitude, + svfbuveg, + albedo_b, + F_sh, + gvfalb, + gvfalbE, + gvfalbS, + gvfalbW, + gvfalbN, + gvfalbnosh, + gvfalbnoshE, + gvfalbnoshS, + gvfalbnoshW, + gvfalbnoshN, + ) + + Keast, Ksouth, Kwest, Knorth, KsideI, KsideD, Kside = Kside_veg_v2022a( + radI, + radD, + radG, + shadow, + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + azimuth, + altitude, + psi, + t, + albedo_b, + F_sh, + KupE, + KupS, + KupW, + KupN, + cyl, + lv, + anisotropic_sky, + diffsh, + rows, + cols, + asvf, + shmat, + vegshmat, + vbshvegshmat, + ) + + firstdaytime = 0 + + else: # # # # # # # NIGHTTIME # # # # # # # # + # Nocturnal K fluxes set to 0 + Knight = np.zeros((rows, cols)) + Kdown = np.zeros((rows, cols)) + Kwest = np.zeros((rows, cols)) + Kup = np.zeros((rows, cols)) + Keast = np.zeros((rows, cols)) + Ksouth = np.zeros((rows, cols)) + Knorth = np.zeros((rows, cols)) + KsideI = np.zeros((rows, cols)) + KsideD = np.zeros((rows, cols)) + F_sh = np.zeros((rows, cols)) + shadow = np.zeros((rows, cols)) + CI_TgG = deepcopy(CI) + dRad = np.zeros((rows, cols)) + Kside = np.zeros((rows, cols)) + wallsun = np.zeros((rows, cols)) + + Tgwall = 0 + + # # # # Ldown # # # # + Ldown = ( + (svf + svfveg - 1) * esky * SBC * ((Ta + 273.15) ** 4) + + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) + + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + + (2 - svf - svfveg) + * (1 - ewall) + * esky + * SBC + * ((Ta + 273.15) ** 4) + ) # Jonsson et al.(2006) + # Ldown = Ldown - 25 # Shown by Jonsson et al.(2006) and Duarte et al.(2006) + + if CI < 0.95: # non - clear conditions + c = 1 - CI + Ldown = Ldown * (1 - c) + c * ( + (svf + svfveg - 1) * SBC * ((Ta + 273.15) ** 4) + + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) + + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + + (2 - svf - svfveg) * (1 - ewall) * SBC * ((Ta + 273.15) ** 4) + ) # NOT REALLY TESTED!!! BUT MORE CORRECT? + + # Surface temperature parameterization + if groundScheme == 1: + # calculate the ground surface temperature, and relevant heat fluxes + Tg, Rn, Rn_past, G = surfaceTemperature_calc( + Kdown, + Ldown, + Rn, + Rn_past, + G, + Tg, + Tm, + alb_grid, + emis_grid, + cap_grid, + diff_grid, + lc_grid, + a1_grid, + a2_grid, + a3_grid, + timeStep, + RH, + shadow, + shadow_past, + ) + + # # # # Lup, daytime # # # # + ( + Lup, + gvfalb, + gvfalbnosh, + LupE, + gvfalbE, + gvfalbnoshE, + LupS, + gvfalbS, + gvfalbnoshS, + LupW, + gvfalbW, + gvfalbnoshW, + LupN, + gvfalbN, + gvfalbnoshN, + gvfLsideW, + gvfLsideS, + gvfLsideE, + gvfLsideN, + ) = outgoingLongwave_calc( + Tg, + Tgwall, + Ta, + Ldown, + emis_grid, + alb_grid, + buildings, + shadow, + wallsun, + walls, + rows, + cols, + 1 / scale, + ) + + else: + # In the old scheme the ground surface temperature is equal to the air temperature during nighttime + Tg = np.ones((rows, cols)) * Ta + + # # # # Lup, nighttime # # # # + Lup = SBC * emis_grid * ((Knight + Tg + 273.15) ** 4) + LupE = Lup + LupS = Lup + LupW = Lup + LupN = Lup + + I0 = 0 + timeadd = 0 + firstdaytime = 1 + + # # # # Lside # # # # + if groundScheme == 1: + Least = np.copy(gvfLsideE) + Lsouth = np.copy(gvfLsideS) + Lwest = np.copy(gvfLsideW) + Lnorth = np.copy(gvfLsideN) + Least_, Lsouth_, Lwest_, Lnorth_ = Lside_veg_v2026( + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + azimuth, + altitude, + Ta, + Tgwall, + SBC, + ewall, + Ldown, + esky, + t, + F_sh, + CI, + anisotropic_sky, + ) + else: + Least = np.zeros_like(Ldown) + Lnorth = np.zeros_like(Ldown) + Lwest = np.zeros_like(Ldown) + Lsouth = np.zeros_like(Ldown) + Least_, Lsouth_, Lwest_, Lnorth_ = Lside_veg_v2022a( + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + azimuth, + altitude, + Ta, + Tgwall, + SBC, + ewall, + Ldown, + esky, + t, + F_sh, + CI, + LupE, + LupS, + LupW, + LupN, + anisotropic_sky, + ) + + Least += Least_ + Lsouth += Lsouth_ + Lwest += Lwest_ + Lnorth += Lnorth_ + Lside = (Lsouth + Lnorth + Least + Lwest) / 4 + + ### Anisotropic sky + if anisotropic_sky == 1: + if "lv" not in locals(): + # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) + skyvaultalt, skyvaultazi, _, _, _, _, _ = create_patches( + patch_option + ) + + patch_emissivities = np.zeros(skyvaultalt.shape[0]) + + x = np.transpose(np.atleast_2d(skyvaultalt)) + y = np.transpose(np.atleast_2d(skyvaultazi)) + z = np.transpose(np.atleast_2d(patch_emissivities)) + + L_patches = np.append(np.append(x, y, axis=1), z, axis=1) + + else: + L_patches = deepcopy(lv) + + # Calculate steradians for patches if it is the first model iteration + if i == 0: + steradians, skyalt, patch_altitude = patch_steradians(L_patches) + + # Create lv from L_patches if nighttime, i.e. lv does not exist + if altitude < 0: + # CI = deepcopy(CI) + lv = deepcopy(L_patches) + KupE = 0 + KupS = 0 + KupW = 0 + KupN = 0 + + # Adjust sky emissivity under semi-cloudy/hazy/cloudy/overcast conditions, i.e. CI lower than 0.95 + if CI < 0.95: + esky_c = CI * esky + (1 - CI) * 1.0 + esky = esky_c + + ( + Ldown, + Lside_, + Lside_sky, + Lside_veg, + Lside_sh, + Lside_sun, + Lside_ref, + Least_, + Lwest_, + Lnorth_, + Lsouth_, + Keast, + Ksouth, + Kwest, + Knorth, + KsideI, + KsideD, + Kside, + steradians, + skyalt, + ) = ani_sky( + shmat, + vegshmat, + vbshvegshmat, + altitude, + azimuth, + asvf, + cyl, + esky, + L_patches, + wallScheme, + voxelTable, + voxelMaps, + steradians, + Ta, + Tgwall, + ewall, + Lup, + radI, + radD, + radG, + lv, + albedo_b, + 0, + diffsh, + shadow, + KupE, + KupS, + KupW, + KupN, + i, + ) + Lside += Lside_ + else: + Lside_ = np.zeros((rows, cols)) + L_patches = None + + # Box and anisotropic longwave + if cyl == 0 and anisotropic_sky == 1: + Least += Least_ + Lwest += Lwest_ + Lnorth += Lnorth_ + Lsouth += Lsouth_ + + # Calculation of radiant flux density + # Human body considered as a cylinder with isotropic all-sky diffuse + if cyl == 1 and anisotropic_sky == 0: + Sstr = absK * ( + KsideI * Fcyl + + (Kdown + Kup) * Fup + + (Knorth + Keast + Ksouth + Kwest) * Fside + ) + absL * ( + (Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside + ) + # Human body considered as a cylinder with Perez et al. (1993) (anisotropic sky diffuse) + # and Martin and Berdahl (1984) (anisotropic sky longwave) + elif cyl == 1 and anisotropic_sky == 1: + Sstr = absK * ( + Kside * Fcyl + + (Kdown + Kup) * Fup + + (Knorth + Keast + Ksouth + Kwest) * Fside + ) + absL * ( + (Ldown + Lup) * Fup + + Lside * Fcyl + + (Lnorth + Least + Lsouth + Lwest) * Fside + ) + # Human body considered as a standing cube + else: + Sstr = absK * ( + (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside + ) + absL * ( + (Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside + ) + + # # # # Tmrt # # # # + Tmrt = np.sqrt(np.sqrt((Sstr / (absL * SBC)))) - 273.2 + + # Add longwave to cardinal directions for output in POI + if (cyl == 1) and (anisotropic_sky == 1): + Least += Least_ + Lwest += Lwest_ + Lnorth += Lnorth_ + Lsouth += Lsouth_ + + return ( + Tmrt, + Kdown, + Kup, + Ldown, + Lup, + Tg, + ea, + esky, + I0, + CI, + shadow, + firstdaytime, + timestepdec, + timeadd, + Tgmap1, + Tgmap1E, + Tgmap1S, + Tgmap1W, + Tgmap1N, + Keast, + Ksouth, + Kwest, + Knorth, + Least, + Lsouth, + Lwest, + Lnorth, + KsideI, + radI, + radD, + Lside, + L_patches, + CI_TgG, + KsideD, + dRad, + Kside, + steradians, + voxelTable, + Rn, + Rn_past, + Tm, + G, + ) diff --git a/functions/SOLWEIGpython/Solweig_run.py b/functions/SOLWEIGpython/Solweig_run.py index d2e1915..ef7fb2f 100644 --- a/functions/SOLWEIGpython/Solweig_run.py +++ b/functions/SOLWEIGpython/Solweig_run.py @@ -4,14 +4,19 @@ # Goteborg Urban Climate Group # Gothenburg University -#imports +# sommon imports from __future__ import absolute_import from ...util.umep_solweig_export_component import read_solweig_config -from ...util.SEBESOLWEIGCommonFiles.Solweig_v2015_metdata_noload import Solweig_2015a_metdata_noload -from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import clearnessindex_2013b +from ...util.SEBESOLWEIGCommonFiles.Solweig_v2015_metdata_noload import ( + Solweig_2015a_metdata_noload, +) +from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import ( + clearnessindex_2013b, +) -from ...functions.SOLWEIGpython import Solweig_2025a_calc_forprocessing as so -#from ...functions.SOLWEIGpython import WriteMetadataSOLWEIG # Not needed anymore? +from ...functions.SOLWEIGpython import Solweig_2026a_calc_forprocessing as so + +# from ...functions.SOLWEIGpython import WriteMetadataSOLWEIG # Not needed anymore? from ...functions.SOLWEIGpython import PET_calculations as p from ...functions.SOLWEIGpython import UTCI_calculations as utci from ...functions.SOLWEIGpython.CirclePlotBar import PolarBarPlot @@ -21,7 +26,7 @@ from ...functions.SOLWEIGpython.wallsAsNetCDF import walls_as_netcdf from ...functions.SOLWEIGpython.Tgmaps_v1 import Tgmaps_v1 from ...functions import wallalgorithms as wa - +from ...functions.SOLWEIGpython.ground_surface import initiate_groundScheme import numpy as np import json import zipfile @@ -34,8 +39,7 @@ from osgeo import gdal from osgeo.gdalconst import * from ...util.misc import saveraster, xy2latlon_fromraster - from qgis.core import QgsRasterLayer, QgsVectorLayer - import configparser + from qgis.core import QgsVectorLayer, QgsRasterLayer except: pass @@ -50,53 +54,53 @@ pass -def config_to_dict(config: configparser.ConfigParser): - def auto_cast(value: str): - """Try to interpret strings as bool, int, or float.""" - v = value.strip().lower() - if v in ('true', 'yes', 'on'): return True - if v in ('false', 'no', 'off'): return False - if v.isdigit(): return int(v) - try: - return float(v) - except ValueError: - return value # fallback: leave as string +# import numpy as np +# from .daylen import daylen +# from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import clearnessindex_2013b +# from ...util.SEBESOLWEIGCommonFiles.diffusefraction import diffusefraction +# from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_13 import shadowingfunction_wallheight_13 +# from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_23 import shadowingfunction_wallheight_23 +# from .gvf_2018a import gvf_2018a +# from .cylindric_wedge import cylindric_wedge +# from .TsWaveDelay_2015a import TsWaveDelay_2015a +# from .Kup_veg_2015a import Kup_veg_2015a +# # from .Lside_veg_v2015a import Lside_veg_v2015a +# # from .Kside_veg_v2019a import Kside_veg_v2019a +# from .Kside_veg_v2022a import Kside_veg_v2022a +# from ...util.SEBESOLWEIGCommonFiles.Perez_v3 import Perez_v3 +# from ...util.SEBESOLWEIGCommonFiles.create_patches import create_patches - return { - k: auto_cast(v) for k, v in config.items() - } def solweig_run(configPath, feedback): - """ Input: configPath : config file including geodata paths and settings. feedback : To communicate with qgis gui. Set to None if standalone """ - + # Load config file configDict = read_solweig_config(configPath) # Load parameters settings for SOLWEIG - with open(configDict['para_json_path'], "r") as jsn: + with open(configDict["para_json_path"], "r") as jsn: param = json.load(jsn) - configDict = config_to_dict(configDict) - - standAlone = int(configDict['standalone']) + standAlone = int(configDict["standalone"]) # reading variables from config and parameters that is not yet presented cyl = int(configDict["cyl"]) - albedo_b = param['Albedo']['Effective']['Value']['Walls'] - ewall = param['Emissivity']['Value']['Walls'] - onlyglobal = int(configDict['onlyglobal']) + albedo_b = param["Albedo"]["Effective"]["Value"]["Walls"] + ewall = param["Emissivity"]["Value"]["Walls"] + onlyglobal = int(configDict["onlyglobal"]) elvis = 0.0 - absK = param['Tmrt_params']['Value']['absK'] - absL = param['Tmrt_params']['Value']['absL'] + absK = param["Tmrt_params"]["Value"]["absK"] + absL = param["Tmrt_params"]["Value"]["absL"] - #Load DSM - if standAlone == 1: - dsm, dsm_transf, dsm_crs = common.load_raster(configDict['filepath_dsm'], bbox=None) + # Load DSM + if standAlone == 1: + dsm, dsm_transf, dsm_crs = common.load_raster( + configDict["filepath_dsm"], bbox=None + ) scale = 1 / dsm_transf.a # dsm_height, dsm_width = dsm.shape # y rows by x cols # y is flipped - so return max for lower row @@ -105,22 +109,25 @@ def solweig_run(configPath, feedback): source_crs = pyproj.CRS(dsm_crs) target_crs = pyproj.CRS(4326) # WGS 84 # Create a transformer object - transformer = pyproj.Transformer.from_crs(source_crs, target_crs, always_xy=True) + transformer = pyproj.Transformer.from_crs( + source_crs, target_crs, always_xy=True + ) # Perform the transformation - lon, lat = transformer.transform(minx, miny) - nd = -9999 #TODO: extract nodatavalue from rasterio + lon, lat = transformer.transform(minx, miny) + nd = -9999 # TODO: extract nodatavalue from rasterio else: - dsm_wkt = QgsRasterLayer(configDict['filepath_dsm']).crs().toWkt() - gdal_dsm = gdal.Open(configDict['filepath_dsm']) + # dsmlayer = QgsRasterLayer(configDict['filepath_dsm']) + dsm_wkt = QgsRasterLayer(configDict["filepath_dsm"]).crs().toWkt() + gdal_dsm = gdal.Open(configDict["filepath_dsm"]) lat, lon, scale, minx, miny = xy2latlon_fromraster(dsm_wkt, gdal_dsm) - dsm = gdal_dsm.ReadAsArray().astype(float) + dsm = gdal_dsm.ReadAsArray().astype(float) nd = gdal_dsm.GetRasterBand(1).GetNoDataValue() rows = dsm.shape[0] cols = dsm.shape[1] - + # response to issue #85 - dsm[dsm == nd] = 0. + dsm[dsm == nd] = 0.0 if dsm.min() < 0: dsmraise = np.abs(dsm.min()) dsm = dsm + dsmraise @@ -131,98 +138,210 @@ def solweig_run(configPath, feedback): if alt < 0: alt = 3 - #Vegetation - transVeg = param["Tree_settings"]["Value"]["Transmissivity"] - trunkratio = param["Tree_settings"]["Value"]["Trunk_ratio"] - usevegdem = int(configDict['usevegdem']) + # Vegetation + transVeg = param["Tree_settings"]["Value"]["Transmissivity"] + trunkratio = param["Tree_settings"]["Value"]["Trunk_ratio"] + usevegdem = int(configDict["usevegdem"]) if usevegdem == 1: if standAlone == 0: - vegdsm = gdal.Open(configDict['filepath_cdsm']).ReadAsArray().astype(float) + vegdsm = ( + gdal.Open(configDict["filepath_cdsm"]) + .ReadAsArray() + .astype(float) + ) else: - vegdsm, _ , _ = common.load_raster(configDict['filepath_cdsm'], bbox=None) - if configDict['filepath_tdsm'] is not '': + vegdsm, _, _ = common.load_raster( + configDict["filepath_cdsm"], bbox=None + ) + if configDict["filepath_tdsm"] != "": if standAlone == 0: - vegdsm2 = gdal.Open(configDict['filepath_tdsm']).ReadAsArray().astype(float) + vegdsm2 = ( + gdal.Open(configDict["filepath_tdsm"]) + .ReadAsArray() + .astype(float) + ) else: - vegdsm2, _ , _ = common.load_raster(configDict['filepath_tdsm'], bbox=None) + vegdsm2, _, _ = common.load_raster( + configDict["filepath_tdsm"], bbox=None + ) else: vegdsm2 = vegdsm * trunkratio else: vegdsm = 0 vegdsm2 = 0 - #Land cover - landcover = int(configDict['landcover']) + # Land cover + landcover = int(configDict["landcover"]) if landcover == 1: if standAlone == 0: - lcgrid = gdal.Open(configDict['filepath_lc']).ReadAsArray().astype(float) + lcgrid = ( + gdal.Open(configDict["filepath_lc"]) + .ReadAsArray() + .astype(float) + ) else: - lcgrid, _ , _ = common.load_raster(configDict['filepath_lc'], bbox=None) + lcgrid, _, _ = common.load_raster( + configDict["filepath_lc"], bbox=None + ) else: lcgrid = 0 - #DEM for buildings #TODO: fix nodata in standalone - demforbuild = int(configDict['demforbuild']) + # DEM for buildings #TODO: fix nodata in standalone + demforbuild = int(configDict["demforbuild"]) if demforbuild == 1: if standAlone == 0: - gdal_dem = gdal.Open(configDict['filepath_dem'])# .ReadAsArray().astype(float) + gdal_dem = gdal.Open( + configDict["filepath_dem"] + ) # .ReadAsArray().astype(float) dem = gdal_dem.ReadAsArray().astype(float) nd = gdal_dem.GetRasterBand(1).GetNoDataValue() else: - dem, _ , _ = common.load_raster(configDict['filepath_dem'], bbox=None) - nd = -9999 #TODO: standAlone nd exposure + dem, _, _ = common.load_raster( + configDict["filepath_dem"], bbox=None + ) + nd = -9999 # TODO: standAlone nd exposure # response to issue and #230 - dem[dem == nd] = 0. + dem[dem == nd] = 0.0 if dem.min() < 0: demraise = np.abs(dem.min()) dem = dem + demraise else: demraise = 0 - #SVF - zip = zipfile.ZipFile(configDict['input_svf'], 'r') - zip.extractall(configDict['working_dir']) + # SVF + zip = zipfile.ZipFile(configDict["input_svf"], "r") + zip.extractall(configDict["working_dir"]) zip.close() if standAlone == 0: - svf = gdal.Open(configDict['working_dir'] + "/svf.tif").ReadAsArray().astype(float) - svfN = gdal.Open(configDict['working_dir'] + "/svfN.tif").ReadAsArray().astype(float) - svfS = gdal.Open(configDict['working_dir'] + "/svfS.tif").ReadAsArray().astype(float) - svfE = gdal.Open(configDict['working_dir'] + "/svfE.tif").ReadAsArray().astype(float) - svfW = gdal.Open(configDict['working_dir'] + "/svfW.tif").ReadAsArray().astype(float) + svf = ( + gdal.Open(configDict["working_dir"] + "/svf.tif") + .ReadAsArray() + .astype(float) + ) + svfN = ( + gdal.Open(configDict["working_dir"] + "/svfN.tif") + .ReadAsArray() + .astype(float) + ) + svfS = ( + gdal.Open(configDict["working_dir"] + "/svfS.tif") + .ReadAsArray() + .astype(float) + ) + svfE = ( + gdal.Open(configDict["working_dir"] + "/svfE.tif") + .ReadAsArray() + .astype(float) + ) + svfW = ( + gdal.Open(configDict["working_dir"] + "/svfW.tif") + .ReadAsArray() + .astype(float) + ) else: - svf, _ , _ = common.load_raster(configDict['working_dir'] + "/svf.tif", bbox=None) - svfN, _ , _ = common.load_raster(configDict['working_dir'] + "/svfN.tif", bbox=None) - svfS, _ , _ = common.load_raster(configDict['working_dir'] + "/svfS.tif", bbox=None) - svfE, _ , _ = common.load_raster(configDict['working_dir'] + "/svfE.tif", bbox=None) - svfW, _ , _ = common.load_raster(configDict['working_dir'] + "/svfW.tif", bbox=None) + svf, _, _ = common.load_raster( + configDict["working_dir"] + "/svf.tif", bbox=None + ) + svfN, _, _ = common.load_raster( + configDict["working_dir"] + "/svfN.tif", bbox=None + ) + svfS, _, _ = common.load_raster( + configDict["working_dir"] + "/svfS.tif", bbox=None + ) + svfE, _, _ = common.load_raster( + configDict["working_dir"] + "/svfE.tif", bbox=None + ) + svfW, _, _ = common.load_raster( + configDict["working_dir"] + "/svfW.tif", bbox=None + ) if usevegdem == 1: if standAlone == 0: - svfveg = gdal.Open(configDict['working_dir'] + "/svfveg.tif").ReadAsArray().astype(float) - svfNveg = gdal.Open(configDict['working_dir'] + "/svfNveg.tif").ReadAsArray().astype(float) - svfSveg = gdal.Open(configDict['working_dir'] + "/svfSveg.tif").ReadAsArray().astype(float) - svfEveg = gdal.Open(configDict['working_dir'] + "/svfEveg.tif").ReadAsArray().astype(float) - svfWveg = gdal.Open(configDict['working_dir'] + "/svfWveg.tif").ReadAsArray().astype(float) - - svfaveg = gdal.Open(configDict['working_dir'] + "/svfaveg.tif").ReadAsArray().astype(float) - svfNaveg = gdal.Open(configDict['working_dir'] + "/svfNaveg.tif").ReadAsArray().astype(float) - svfSaveg = gdal.Open(configDict['working_dir'] + "/svfSaveg.tif").ReadAsArray().astype(float) - svfEaveg = gdal.Open(configDict['working_dir'] + "/svfEaveg.tif").ReadAsArray().astype(float) - svfWaveg = gdal.Open(configDict['working_dir'] + "/svfWaveg.tif").ReadAsArray().astype(float) + svfveg = ( + gdal.Open(configDict["working_dir"] + "/svfveg.tif") + .ReadAsArray() + .astype(float) + ) + svfNveg = ( + gdal.Open(configDict["working_dir"] + "/svfNveg.tif") + .ReadAsArray() + .astype(float) + ) + svfSveg = ( + gdal.Open(configDict["working_dir"] + "/svfSveg.tif") + .ReadAsArray() + .astype(float) + ) + svfEveg = ( + gdal.Open(configDict["working_dir"] + "/svfEveg.tif") + .ReadAsArray() + .astype(float) + ) + svfWveg = ( + gdal.Open(configDict["working_dir"] + "/svfWveg.tif") + .ReadAsArray() + .astype(float) + ) + + svfaveg = ( + gdal.Open(configDict["working_dir"] + "/svfaveg.tif") + .ReadAsArray() + .astype(float) + ) + svfNaveg = ( + gdal.Open(configDict["working_dir"] + "/svfNaveg.tif") + .ReadAsArray() + .astype(float) + ) + svfSaveg = ( + gdal.Open(configDict["working_dir"] + "/svfSaveg.tif") + .ReadAsArray() + .astype(float) + ) + svfEaveg = ( + gdal.Open(configDict["working_dir"] + "/svfEaveg.tif") + .ReadAsArray() + .astype(float) + ) + svfWaveg = ( + gdal.Open(configDict["working_dir"] + "/svfWaveg.tif") + .ReadAsArray() + .astype(float) + ) else: - svfveg, _ , _ = common.load_raster(configDict['working_dir'] + "/svfveg.tif", bbox=None) - svfNveg, _ , _ = common.load_raster(configDict['working_dir'] + "/svfNveg.tif", bbox=None) - svfSveg, _ , _ = common.load_raster(configDict['working_dir'] + "/svfSveg.tif", bbox=None) - svfEveg, _ , _ = common.load_raster(configDict['working_dir'] + "/svfEveg.tif", bbox=None) - svfWveg, _ , _ = common.load_raster(configDict['working_dir'] + "/svfWveg.tif", bbox=None) - - svfaveg, _ , _ = common.load_raster(configDict['working_dir'] + "/svfaveg.tif", bbox=None) - svfNaveg, _ , _ = common.load_raster(configDict['working_dir'] + "/svfNaveg.tif", bbox=None) - svfSaveg, _ , _ = common.load_raster(configDict['working_dir'] + "/svfSaveg.tif", bbox=None) - svfEaveg, _ , _ = common.load_raster(configDict['working_dir'] + "/svfEaveg.tif", bbox=None) - svfWaveg, _ , _ = common.load_raster(configDict['working_dir'] + "/svfWaveg.tif", bbox=None) + svfveg, _, _ = common.load_raster( + configDict["working_dir"] + "/svfveg.tif", bbox=None + ) + svfNveg, _, _ = common.load_raster( + configDict["working_dir"] + "/svfNveg.tif", bbox=None + ) + svfSveg, _, _ = common.load_raster( + configDict["working_dir"] + "/svfSveg.tif", bbox=None + ) + svfEveg, _, _ = common.load_raster( + configDict["working_dir"] + "/svfEveg.tif", bbox=None + ) + svfWveg, _, _ = common.load_raster( + configDict["working_dir"] + "/svfWveg.tif", bbox=None + ) + + svfaveg, _, _ = common.load_raster( + configDict["working_dir"] + "/svfaveg.tif", bbox=None + ) + svfNaveg, _, _ = common.load_raster( + configDict["working_dir"] + "/svfNaveg.tif", bbox=None + ) + svfSaveg, _, _ = common.load_raster( + configDict["working_dir"] + "/svfSaveg.tif", bbox=None + ) + svfEaveg, _, _ = common.load_raster( + configDict["working_dir"] + "/svfEaveg.tif", bbox=None + ) + svfWaveg, _, _ = common.load_raster( + configDict["working_dir"] + "/svfWaveg.tif", bbox=None + ) else: svfveg = np.ones((rows, cols)) svfNveg = np.ones((rows, cols)) @@ -235,27 +354,39 @@ def solweig_run(configPath, feedback): svfEaveg = np.ones((rows, cols)) svfWaveg = np.ones((rows, cols)) - tmp = svf + svfveg - 1. - tmp[tmp < 0.] = 0. + tmp = svf + svfveg - 1.0 + tmp[tmp < 0.0] = 0.0 # %matlab crazyness around 0 - svfalfa = np.arcsin(np.exp((np.log((1. - tmp)) / 2.))) + svfalfa = np.arcsin(np.exp((np.log((1.0 - tmp)) / 2.0))) if standAlone == 0: - wallheight = gdal.Open(configDict['filepath_wh']).ReadAsArray().astype(float) - wallaspect = gdal.Open(configDict['filepath_wa']).ReadAsArray().astype(float) + wallheight = ( + gdal.Open(configDict["filepath_wh"]).ReadAsArray().astype(float) + ) + wallaspect = ( + gdal.Open(configDict["filepath_wa"]).ReadAsArray().astype(float) + ) else: - wallheight, _ , _ = common.load_raster(configDict['filepath_wh'], bbox=None) - wallaspect, _ , _ = common.load_raster(configDict['filepath_wa'], bbox=None) + wallheight, _, _ = common.load_raster( + configDict["filepath_wh"], bbox=None + ) + wallaspect, _, _ = common.load_raster( + configDict["filepath_wa"], bbox=None + ) # Metdata headernum = 1 - delim = ' ' + delim = " " Twater = [] - metdata = np.loadtxt(configDict['input_met'],skiprows=headernum, delimiter=delim) + metdata = np.loadtxt( + configDict["input_met"], skiprows=headernum, delimiter=delim + ) - location = {'longitude': lon, 'latitude': lat, 'altitude': alt} - YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax = Solweig_2015a_metdata_noload(metdata,location, int(configDict['utc'])) + location = {"longitude": lon, "latitude": lat, "altitude": alt} + YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax = ( + Solweig_2015a_metdata_noload(metdata, location, int(configDict["utc"])) + ) DOY = metdata[:, 1] hours = metdata[:, 2] @@ -269,52 +400,85 @@ def solweig_run(configPath, feedback): Ws = metdata[:, 9] # POIs check - if configDict['poi_file'] is not '': # usePOI: - header = 'yyyy id it imin dectime altitude azimuth kdir kdiff kglobal kdown kup keast ksouth ' \ - 'kwest knorth ldown lup least lsouth lwest lnorth Ta Tg RH Esky Tmrt ' \ - 'I0 CI Shadow SVF_b SVF_bv KsideI PET UTCI CI_Tg CI_TgG KsideD Lside diffDown Kside' + if configDict["poi_file"] != "": # usePOI: + header = ( + "yyyy id it imin dectime altitude azimuth kdir kdiff kglobal kdown kup keast ksouth " + "kwest knorth ldown lup least lsouth lwest lnorth Ta Tg RH Esky Tmrt " + "I0 CI Shadow SVF_b SVF_bv KsideI PET UTCI CI_TgG KsideD Lside diffDown Kside " + ) # poiname = [] - poi_field = configDict['poi_field'] #self.parameterAsString(parameters, self.POI_FIELD, context) + poi_field = configDict[ + "poi_field" + ] # self.parameterAsString(parameters, self.POI_FIELD, context) if standAlone == 0: - poi_field = configDict['woi_field'] #self.parameterAsStrings(parameters, self.WOI_FIELD, context) - poisxy, poiname = pointOfInterest(configDict['poi_file'], poi_field, scale, gdal_dsm) + # vlayer = QgsVectorLayer(configDict['poi_file'], 'point', 'ogr') + # idx = vlayer.fields().indexFromName(poi_field) + # numfeat = vlayer.featureCount() + # poisxy = np.zeros((numfeat, 3)) - 999 + # ind = 0 + # for f in vlayer.getFeatures(): # looping through each POI + # y = f.geometry().centroid().asPoint().y() + # x = f.geometry().centroid().asPoint().x() + # poiname.append(f.attributes()[idx]) + # poisxy[ind, 0] = ind + # poisxy[ind, 1] = np.round((x - minx) * scale) + # if miny >= 0: + # poisxy[ind, 2] = np.round((miny + rows * (1. / scale) - y) * scale) + # else: + # poisxy[ind, 2] = np.round((miny + rows * (1. / scale) - y) * scale) + # ind += 1 + + poi_field = configDict[ + "woi_field" + ] # self.parameterAsStrings(parameters, self.WOI_FIELD, context) + poisxy, poiname = pointOfInterest( + configDict["poi_file"], poi_field, scale, gdal_dsm + ) else: - pois_gdf = gpd.read_file(configDict['poi_file']) + pois_gdf = gpd.read_file(configDict["poi_file"]) numfeat = pois_gdf.shape[0] poisxy = np.zeros((numfeat, 3)) - 999 for idx, row in pois_gdf.iterrows(): - y, x = rowcol(dsm_transf, row["geometry"].centroid.x, row["geometry"].centroid.y) #TODO: This produce different result since no standalone round coordinates - poiname.append(row[configDict['poi_field']]) + y, x = rowcol( + dsm_transf, + row["geometry"].centroid.x, + row["geometry"].centroid.y, + ) # TODO: This produce different result since no standalone round coordinates + poiname.append(row[configDict["poi_field"]]) poisxy[idx, 0] = idx poisxy[idx, 1] = x poisxy[idx, 2] = y for k in range(0, poisxy.shape[0]): poi_save = [] # np.zeros((1, 33)) - data_out = configDict['output_dir'] + '/POI_' + str(poiname[k]) + '.txt' - np.savetxt(data_out, poi_save, delimiter=' ', header=header, comments='') - + data_out = ( + configDict["output_dir"] + "/POI_" + str(poiname[k]) + ".txt" + ) + np.savetxt( + data_out, poi_save, delimiter=" ", header=header, comments="" + ) + # print(poisxy) # Num format for POI output - numformat = '%d %d %d %d %.5f ' + '%.2f ' * 36 + numformat = "%d %d %d %d %.5f " + "%.2f " * 35 # Other PET variables - sensorheight = param['Wind_Height']['Value']['magl'] - age = param['PET_settings']['Value']['Age'] - mbody = param['PET_settings']['Value']['Weight'] - ht = param['PET_settings']['Value']['Height'] - clo = param['PET_settings']['Value']['clo'] - activity = param['PET_settings']['Value']['Activity'] - sex = param['PET_settings']['Value']['Sex'] + sensorheight = param["Wind_Height"]["Value"]["magl"] + age = param["PET_settings"]["Value"]["Age"] + mbody = param["PET_settings"]["Value"]["Weight"] + ht = param["PET_settings"]["Value"]["Height"] + clo = param["PET_settings"]["Value"]["clo"] + activity = param["PET_settings"]["Value"]["Activity"] + sex = param["PET_settings"]["Value"]["Sex"] else: poisxy = None - - #Posture settings - if param['Tmrt_params']['Value']['posture'] == "Standing": + + # Posture settings + if param["Tmrt_params"]["Value"]["posture"] == "Standing": Fside = param["Posture"]["Standing"]["Value"]["Fside"] Fup = param["Posture"]["Standing"]["Value"]["Fup"] height = param["Posture"]["Standing"]["Value"]["height"] - Fcyl = param["Posture"]["Standing"]["Value"]["Fcyl"] + Fcyl = param["Posture"]["Standing"]["Value"]["Fcyl"] pos = 1 else: Fside = param["Posture"]["Sitting"]["Value"]["Fside"] @@ -325,23 +489,30 @@ def solweig_run(configPath, feedback): # Radiative surface influence, Rule of thumb by Schmid et al. (1990). first = np.round(height) - if first == 0.: - first = 1. - second = np.round((height * 20.)) + if first == 0.0: + first = 1.0 + second = np.round((height * 20.0)) if usevegdem == 1: # Conifer or deciduous - if configDict['conifer_bool']: + if configDict["conifer_bool"]: leafon = np.ones((1, DOY.shape[0])) else: leafon = np.zeros((1, DOY.shape[0])) - if param["Tree_settings"]["Value"]["First_day_leaf"] > param["Tree_settings"]["Value"]["Last_day_leaf"]: - leaf_bool = ((DOY > param["Tree_settings"]["Value"]["First_day_leaf"]) | (DOY < param["Tree_settings"]["Value"]["Last_day_leaf"])) + if ( + param["Tree_settings"]["Value"]["First_day_leaf"] + > param["Tree_settings"]["Value"]["Last_day_leaf"] + ): + leaf_bool = ( + DOY > param["Tree_settings"]["Value"]["First_day_leaf"] + ) | (DOY < param["Tree_settings"]["Value"]["Last_day_leaf"]) else: - leaf_bool = ((DOY > param["Tree_settings"]["Value"]["First_day_leaf"]) & (DOY < param["Tree_settings"]["Value"]["Last_day_leaf"])) + leaf_bool = ( + DOY > param["Tree_settings"]["Value"]["First_day_leaf"] + ) & (DOY < param["Tree_settings"]["Value"]["Last_day_leaf"]) leafon[0, leaf_bool] = 1 - # Vegetation transmittivity of shortwave radiation + # % Vegetation transmittivity of shortwave radiation psi = leafon * transVeg psi[leafon == 0] = 0.5 # amaxvalue @@ -358,9 +529,11 @@ def solweig_run(configPath, feedback): # % Bush separation bush = np.logical_not((vegdsm2 * vegdsm)) * vegdsm - svfbuveg = (svf - (1. - svfveg) * (1. - transVeg)) # % major bug fixed 20141203 + svfbuveg = svf - (1.0 - svfveg) * ( + 1.0 - transVeg + ) # % major bug fixed 20141203 else: - psi = leafon * 0. + 1. + psi = leafon * 0.0 + 1.0 svfbuveg = svf bush = np.zeros([rows, cols]) amaxvalue = 0 @@ -384,38 +557,49 @@ def solweig_run(configPath, feedback): buildings[buildings == 2] = 0 else: buildings = dsm - dem - buildings[buildings < 2.] = 1. - buildings[buildings >= 2.] = 0. + buildings[buildings < 2.0] = 1.0 + buildings[buildings >= 2.0] = 0.0 - if int(configDict['savebuild']) == 1: + if int(configDict["savebuild"]) == 1: if standAlone == 0: - saveraster(gdal_dsm, configDict['output_dir'] + '/buildings.tif', buildings) + saveraster( + gdal_dsm, + configDict["output_dir"] + "/buildings.tif", + buildings, + ) else: - common.save_raster(configDict['output_dir'] + '/buildings.tif', buildings, dsm_transf, dsm_crs) + common.save_raster( + configDict["output_dir"] + "/buildings.tif", + buildings, + dsm_transf, + dsm_crs, + ) # Import shadow matrices (Anisotropic sky) - anisotropic_sky = int(configDict['aniso']) - if anisotropic_sky == 1: #UseAniso - data = np.load(configDict['input_aniso']) - shmat = data['shadowmat'] - vegshmat = data['vegshadowmat'] - vbshvegshmat = data['vbshmat'] + anisotropic_sky = int(configDict["aniso"]) + if anisotropic_sky == 1: # UseAniso + data = np.load(configDict["input_aniso"]) + shmat = data["shadowmat"] + vegshmat = data["vegshadowmat"] + vbshvegshmat = data["vbshmat"] if usevegdem == 1: diffsh = np.zeros((rows, cols, shmat.shape[2])) for i in range(0, shmat.shape[2]): - diffsh[:, :, i] = shmat[:, :, i] - (1 - vegshmat[:, :, i]) * (1 - transVeg) # changes in psi not implemented yet + diffsh[:, :, i] = shmat[:, :, i] - (1 - vegshmat[:, :, i]) * ( + 1 - transVeg + ) # changes in psi not implemented yet else: diffsh = shmat # Estimate number of patches based on shadow matrices if shmat.shape[2] == 145: - patch_option = 1 # patch_option = 1 # 145 patches + patch_option = 1 # patch_option = 1 # 145 patches elif shmat.shape[2] == 153: - patch_option = 2 # patch_option = 2 # 153 patches + patch_option = 2 # patch_option = 2 # 153 patches elif shmat.shape[2] == 306: - patch_option = 3 # patch_option = 3 # 306 patches + patch_option = 3 # patch_option = 3 # 306 patches elif shmat.shape[2] == 612: - patch_option = 4 # patch_option = 4 # 612 patches + patch_option = 4 # patch_option = 4 # 612 patches # asvf to calculate sunlit and shaded patches asvf = np.arccos(np.sqrt(svf)) @@ -431,106 +615,202 @@ def solweig_run(configPath, feedback): asvf = None patch_option = 0 steradians = 0 - + shadow = np.zeros_like(dsm) + # % Ts parameterisation maps - if landcover == 1.: + if landcover == 1.0: # Get land cover properties for Tg wave (land cover scheme based on Bogren et al. 2000, explained in Lindberg et al., 2008 and Lindberg, Onomura & Grimmond, 2016) - [TgK, Tstart, alb_grid, emis_grid, TgK_wall, Tstart_wall, TmaxLST, TmaxLST_wall] = Tgmaps_v1(lcgrid.copy(), param) + [ + TgK, + Tstart, + alb_grid, + emis_grid, + TgK_wall, + Tstart_wall, + TmaxLST, + TmaxLST_wall, + ] = Tgmaps_v1(lcgrid.copy(), param) + else: + TgK = Knight + param["Ts_deg"]["Value"]["Cobble_stone_2014a"] + Tstart = Knight - param["Tstart"]["Value"]["Cobble_stone_2014a"] + TmaxLST = param["TmaxLST"]["Value"]["Cobble_stone_2014a"] + alb_grid = ( + Knight + + param["Albedo"]["Effective"]["Value"]["Cobble_stone_2014a"] + ) + emis_grid = Knight + param["Emissivity"]["Value"]["Cobble_stone_2014a"] + TgK_wall = param["Ts_deg"]["Value"]["Walls"] + Tstart_wall = param["Tstart"]["Value"]["Walls"] + TmaxLST_wall = param["TmaxLST"]["Value"]["Walls"] + + # Parameterization for the 2026 ground scheme + groundScheme = int(configDict["groundmodel"]) + if groundScheme == 1: + # Initiate the maps if the surface temperature is available + if configDict["input_surf"] != "": + surfData = pd.read_csv(configDict["input_surf"]) + Tg = surfData["Tg"] + Tm = np.mean(surfData["Tg"]) + ( + _, + _, + Rn, + Rn_past, + G, + cap_grid, + diff_grid, + a1_grid, + a2_grid, + a3_grid, + ) = initiate_groundScheme( + lcgrid.copy(), param, DOY[0], Ta, location + ) + else: + ( + Tg, + Tm, + Rn, + Rn_past, + G, + cap_grid, + diff_grid, + a1_grid, + a2_grid, + a3_grid, + ) = initiate_groundScheme( + lcgrid.copy(), param, DOY[0], Ta, location + ) else: - TgK = Knight + param['Ts_deg']['Value']['Cobble_stone_2014a'] - Tstart = Knight - param['Tstart']['Value']['Cobble_stone_2014a'] - TmaxLST = param['TmaxLST']['Value']['Cobble_stone_2014a'] - alb_grid = Knight + param['Albedo']['Effective']['Value']['Cobble_stone_2014a'] - emis_grid = Knight + param['Emissivity']['Value']['Cobble_stone_2014a'] - TgK_wall = param['Ts_deg']['Value']['Walls'] - Tstart_wall = param['Tstart']['Value']['Walls'] - TmaxLST_wall = param['TmaxLST']['Value']['Walls'] + pass # Import data for wall temperature parameterization TODO: fix for standalone - wallScheme = int(configDict['wallscheme']) + wallScheme = int(configDict["wallscheme"]) if wallScheme == 1: - wallData = np.load(configDict['input_wall']) - voxelMaps = wallData['voxelId'] - voxelTable = wallData['voxelTable'] + wallData = np.load(configDict["input_wall"]) + voxelMaps = wallData["voxelId"] + voxelTable = wallData["voxelTable"] # Get wall type from standalone if standAlone == 1: - wall_type_standalone = {'Brick_wall': '100', 'Concrete_wall': '101', 'Wood_wall': '102'} - wall_type = wall_type_standalone[configDict['walltype']] + wall_type_standalone = { + "Brick_wall": "100", + "Concrete_wall": "101", + "Wood_wall": "102", + } + wall_type = wall_type_standalone[configDict["walltype"]] else: # Get wall type set in GUI - wall_type = str(configDict['walltype']) + wall_type = configDict[ + "walltype" + ] # str(100 + int(self.parameterAsString(parameters, self.WALL_TYPE, context))) #TODO + # Calculate wall height for wall scheme, i.e. include corners (thicker walls) - walls_scheme = wa.findwalls_sp(dsm, 2, np.array([[1, 1, 1], [1, 0, 1], [1, 1, 1]])) + walls_scheme = wa.findwalls_sp( + dsm, 2, np.array([[1, 1, 1], [1, 0, 1], [1, 1, 1]]) + ) # Calculate wall aspect for wall scheme, i.e. include corners (thicker walls) - dirwalls_scheme = wa.filter1Goodwin_as_aspect_v3(walls_scheme.copy(), scale, dsm, feedback, 100./180.) - + dirwalls_scheme = wa.filter1Goodwin_as_aspect_v3( + walls_scheme.copy(), scale, dsm, feedback, 100.0 / 180.0 + ) + # Used in wall temperature parameterization scheme - first_timestep = (pd.to_datetime(YYYY[0][0], format='%Y') + pd.to_timedelta(DOY[0] - 1, unit='d') + - pd.to_timedelta(hours[0], unit='h') + pd.to_timedelta(minu[0], unit='m')) - second_timestep = (pd.to_datetime(YYYY[0][1], format='%Y') + pd.to_timedelta(DOY[1] - 1, unit='d') + - pd.to_timedelta(hours[1], unit='h') + pd.to_timedelta(minu[1], unit='m')) - + first_timestep = ( + pd.to_datetime(YYYY[0][0], format="%Y") + + pd.to_timedelta(DOY[0] - 1, unit="d") + + pd.to_timedelta(hours[0], unit="h") + + pd.to_timedelta(minu[0], unit="m") + ) + second_timestep = ( + pd.to_datetime(YYYY[0][1], format="%Y") + + pd.to_timedelta(DOY[1] - 1, unit="d") + + pd.to_timedelta(hours[1], unit="h") + + pd.to_timedelta(minu[1], unit="m") + ) + timeStep = (second_timestep - first_timestep).seconds # Load voxelTable as Pandas DataFrame - voxelTable, dirwalls_scheme = load_walls(voxelTable, param, wall_type, dirwalls_scheme, Ta[0], timeStep, albedo_b, ewall, alb_grid, landcover, lcgrid, dsm) + voxelTable, dirwalls_scheme = load_walls( + voxelTable, + param, + wall_type, + dirwalls_scheme, + Ta[0], + timeStep, + albedo_b, + ewall, + alb_grid, + landcover, + lcgrid, + dsm, + ) # Use wall of interest - woi_file = configDict['woi_file'] - + woi_file = configDict["woi_file"] if woi_file: # (dsm_minx, dsm_x_size, dsm_x_rotation, dsm_miny, dsm_y_rotation, dsm_y_size) = gdal_dsm.GetGeoTransform() #TODO: fix for standalone if standAlone == 0: - woi_field = configDict['woi_field'] #self.parameterAsStrings(parameters, self.WOI_FIELD, context) - woisxy, woiname = pointOfInterest(configDict['woi_file'], woi_field, scale, gdal_dsm) + woi_field = configDict[ + "woi_field" + ] # self.parameterAsStrings(parameters, self.WOI_FIELD, context) + woisxy, woiname = pointOfInterest( + configDict["woi_file"], woi_field, scale, gdal_dsm + ) else: - pois_gdf = gpd.read_file(configDict['poi_file']) + pois_gdf = gpd.read_file(configDict["poi_file"]) numfeat = pois_gdf.shape[0] poisxy = np.zeros((numfeat, 3)) - 999 for idx, row in pois_gdf.iterrows(): - y, x = rowcol(dsm_transf, row["geometry"].centroid.x, row["geometry"].centroid.y) #TODO: This produce different result since no standalone round coordinates - poiname.append(row[configDict['poi_field']]) + y, x = rowcol( + dsm_transf, + row["geometry"].centroid.x, + row["geometry"].centroid.y, + ) # TODO: This produce different result since no standalone round coordinates + poiname.append(row[configDict["poi_field"]]) poisxy[idx, 0] = idx poisxy[idx, 1] = x poisxy[idx, 2] = y - else: - woisxy = None + # Create pandas datetime object to be used when createing an xarray DataSet where wall temperatures/radiation is stored and eventually saved as a NetCDf if configDict["wallnetcdf"] == 1: - met_for_xarray = (pd.to_datetime(YYYY[0][:], format='%Y') + pd.to_timedelta(DOY - 1, unit='d') + - pd.to_timedelta(hours, unit='h') + pd.to_timedelta(minu, unit='m')) + met_for_xarray = ( + pd.to_datetime(YYYY[0][:], format="%Y") + + pd.to_timedelta(DOY - 1, unit="d") + + pd.to_timedelta(hours, unit="h") + + pd.to_timedelta(minu, unit="m") + ) else: wallScheme = 0 voxelMaps = 0 voxelTable = 0 timeStep = 0 - #thermal_effusivity = 0 - walls_scheme = np.ones((rows, cols)) * 10. - dirwalls_scheme = np.ones((rows, cols)) * 10. + # thermal_effusivity = 0 + walls_scheme = np.ones((rows, cols)) * 10.0 + dirwalls_scheme = np.ones((rows, cols)) * 10.0 # Initialisation of time related variables if Ta.__len__() == 1: timestepdec = 0 else: timestepdec = dectime[1] - dectime[0] - timeadd = 0. + timeadd = 0.0 # timeaddE = 0. # timeaddS = 0. # timeaddW = 0. # timeaddN = 0. - firstdaytime = 1. + firstdaytime = 1.0 # Save hemispheric image - if anisotropic_sky == 1: + if anisotropic_sky == 1: if not poisxy is None: - patch_characteristics = hemispheric_image(poisxy, shmat, vegshmat, vbshvegshmat, voxelMaps, wallScheme) + patch_characteristics = hemispheric_image( + poisxy, shmat, vegshmat, vbshvegshmat, voxelMaps, wallScheme + ) # If metfile starts at night - CI = 1. - + CI = 1.0 + # Main loop tmrtplot = np.zeros((rows, cols)) - TgOut1 = np.zeros((rows, cols)) # Initiate array for I0 values if np.unique(DOY).shape[0] > 1: @@ -546,7 +826,9 @@ def solweig_run(configPath, feedback): for i in np.arange(0, Ta.__len__()): if feedback is not None: - feedback.setProgress(int(i * (100. / Ta.__len__()))) # move progressbar forward + feedback.setProgress( + int(i * (100.0 / Ta.__len__())) + ) # move progressbar forward if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") break @@ -564,14 +846,19 @@ def solweig_run(configPath, feedback): alt = altitude[0][daylines] alt2 = np.where(alt > 1) rise = alt2[0][0] - [_, CI, _, _, _] = clearnessindex_2013b(zen[0, i + rise + 1], jday[0, i + rise + 1], - Ta[i + rise + 1], - RH[i + rise + 1] / 100., radG[i + rise + 1], location, - P[i + rise + 1]) - if (CI > 1.) or (CI == np.inf): - CI = 1. + [_, CI, _, _, _] = clearnessindex_2013b( + zen[0, i + rise + 1], + jday[0, i + rise + 1], + Ta[i + rise + 1], + RH[i + rise + 1] / 100.0, + radG[i + rise + 1], + location, + P[i + rise + 1], + ) + if (CI > 1.0) or (CI == np.inf): + CI = 1.0 else: - CI = 1. + CI = 1.0 # Only if Kdir is derived from horizontal global shortwave and horizontal diffuse shortwave # if altitude[0][i] > 0: @@ -581,39 +868,169 @@ def solweig_run(configPath, feedback): # radD[i] = 0. # radI[i] = 0. + # Timestep of the simulation used in the ground scheme calculation + first_timestep = ( + pd.to_datetime(YYYY[0][0], format="%Y") + + pd.to_timedelta(DOY[0] - 1, unit="d") + + pd.to_timedelta(hours[0], unit="h") + + pd.to_timedelta(minu[0], unit="m") + ) + second_timestep = ( + pd.to_datetime(YYYY[0][1], format="%Y") + + pd.to_timedelta(DOY[1] - 1, unit="d") + + pd.to_timedelta(hours[1], unit="h") + + pd.to_timedelta(minu[1], unit="m") + ) + timeStep = (second_timestep - first_timestep).seconds - Tmrt, Kdown, Kup, Ldown, Lup, Tg, ea, esky, I0, CI, shadow, firstdaytime, timestepdec, timeadd, \ - Tgmap1, Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, Keast, Ksouth, Kwest, Knorth, Least, \ - Lsouth, Lwest, Lnorth, KsideI, TgOut1, TgOut, radIout, radDout, \ - Lside, Lsky_patch_characteristics, CI_Tg, CI_TgG, KsideD, \ - dRad, Kside, steradians, voxelTable = so.Solweig_2025a_calc( - i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, svfveg, - svfNveg, svfEveg, svfSveg, svfWveg, svfaveg, svfEaveg, svfSaveg, svfWaveg, svfNaveg, \ - vegdsm, vegdsm2, albedo_b, absK, absL, ewall, Fside, Fup, Fcyl, altitude[0][i], - azimuth[0][i], zen[0][i], jday[0][i], usevegdem, onlyglobal, buildings, location, - psi[0][i], landcover, lcgrid, dectime[i], altmax[0][i], wallaspect, - wallheight, cyl, elvis, Ta[i], RH[i], radG[i], radD[i], radI[i], P[i], amaxvalue, - bush, Twater, TgK, Tstart, alb_grid, emis_grid, TgK_wall, Tstart_wall, TmaxLST, - TmaxLST_wall, first, second, svfalfa, svfbuveg, firstdaytime, timeadd, timestepdec, - Tgmap1, Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, CI, TgOut1, diffsh, shmat, vegshmat, vbshvegshmat, - anisotropic_sky, asvf, patch_option, voxelMaps, voxelTable, Ws[i], wallScheme, timeStep, steradians, walls_scheme, dirwalls_scheme) - - # Save I0 for I0 vs. Kdown output plot to check if UTC is off - # if i == (first_unique_day.shape[0] - 1): - # # Output I0 vs. Kglobal plot - # radG_for_plot = radG[DOY == first_unique_day[0]] - # # hours_for_plot = hours[DOY == first_unique_day[0]] - # dectime_for_plot = dectime[DOY == first_unique_day[0]] - # fig, ax = plt.subplots() - # ax.plot(dectime_for_plot, I0_array, label='I0') - # ax.plot(dectime_for_plot, radG_for_plot, label='Kglobal') - # ax.set_ylabel('Shortwave radiation [$Wm^{-2}$]') - # ax.set_xlabel('Decimal time') - # ax.set_title('UTC' + str(configDict['utc'])) - # ax.legend() - # fig.savefig(configDict['output_dir'] + '/metCheck.png', dpi=150) - # elif i < (first_unique_day.shape[0] - 1): - # I0_array[i] = I0 + ( + Tmrt, + Kdown, + Kup, + Ldown, + Lup, + Tg, + ea, + esky, + I0, + CI, + shadow, + firstdaytime, + timestepdec, + timeadd, + Tgmap1, + Tgmap1E, + Tgmap1S, + Tgmap1W, + Tgmap1N, + Keast, + Ksouth, + Kwest, + Knorth, + Least, + Lsouth, + Lwest, + Lnorth, + KsideI, + radIout, + radDout, + Lside, + Lsky_patch_characteristics, + CI_TgG, + KsideD, + dRad, + Kside, + steradians, + voxelTable, + Rn, + Rn_past, + Tm, + G, + ) = so.Solweig_2026a_calc( + i, + dsm, + scale, + rows, + cols, + svf, + svfN, + svfW, + svfE, + svfS, + svfveg, + svfNveg, + svfEveg, + svfSveg, + svfWveg, + svfaveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + vegdsm, + vegdsm2, + albedo_b, + absK, + absL, + ewall, + Fside, + Fup, + Fcyl, + altitude[0][i], + azimuth[0][i], + zen[0][i], + jday[0][i], + usevegdem, + onlyglobal, + buildings, + location, + psi[0][i], + landcover, + lcgrid, + dectime[i], + altmax[0][i], + wallaspect, + wallheight, + cyl, + elvis, + Ta[i], + RH[i], + radG[i], + radD[i], + radI[i], + P[i], + amaxvalue, + bush, + Twater, + TgK, + Tstart, + alb_grid, + emis_grid, + TgK_wall, + Tstart_wall, + TmaxLST, + TmaxLST_wall, + first, + second, + svfalfa, + svfbuveg, + firstdaytime, + timeadd, + timestepdec, + Tgmap1, + Tgmap1E, + Tgmap1S, + Tgmap1W, + Tgmap1N, + CI, + diffsh, + shmat, + vegshmat, + vbshvegshmat, + anisotropic_sky, + asvf, + patch_option, + voxelMaps, + voxelTable, + Ws[i], + wallScheme, + timeStep, + steradians, + walls_scheme, + dirwalls_scheme, + groundScheme, + Tg, + Rn, + Rn_past, + G, + Tm, + cap_grid, + diff_grid, + a1_grid, + a2_grid, + a3_grid, + shadow, + ) # Save I0 for I0 vs. Kdown output plot to check if UTC is off if i < first_unique_day.shape[0]: @@ -624,25 +1041,25 @@ def solweig_run(configPath, feedback): # hours_for_plot = hours[DOY == first_unique_day[0]] dectime_for_plot = dectime[DOY == first_unique_day[0]] fig, ax = plt.subplots() - ax.plot(dectime_for_plot, I0_array, label='I0') - ax.plot(dectime_for_plot, radG_for_plot, label='Kglobal') - ax.set_ylabel('Shortwave radiation [$Wm^{-2}$]') - ax.set_xlabel('Decimal time') - ax.set_title('UTC' + str(configDict['utc'])) + ax.plot(dectime_for_plot, I0_array, label="I0") + ax.plot(dectime_for_plot, radG_for_plot, label="Kglobal") + ax.set_ylabel("Shortwave radiation [$Wm^{-2}$]") + ax.set_xlabel("Decimal time") + ax.set_title("UTC" + str(configDict["utc"])) ax.legend() - fig.savefig(configDict['output_dir'] + '/metCheck.png', dpi=150) + fig.savefig(configDict["output_dir"] + "/metCheck.png", dpi=150) tmrtplot = tmrtplot + Tmrt if altitude[0][i] > 0: - w = 'D' + w = "D" else: - w = 'N' + w = "N" # Write to POIs - if not poisxy is None: + if not poisxy is None: for k in range(0, poisxy.shape[0]): - poi_save = np.zeros((1, 41)) + poi_save = np.zeros((1, 40)) poi_save[0, 0] = YYYY[0][i] poi_save[0, 1] = jday[0][i] poi_save[0, 2] = hours[i] @@ -666,7 +1083,7 @@ def solweig_run(configPath, feedback): poi_save[0, 20] = Lwest[int(poisxy[k, 2]), int(poisxy[k, 1])] poi_save[0, 21] = Lnorth[int(poisxy[k, 2]), int(poisxy[k, 1])] poi_save[0, 22] = Ta[i] - poi_save[0, 23] = TgOut[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 23] = Tg[int(poisxy[k, 2]), int(poisxy[k, 1])] poi_save[0, 24] = RH[i] poi_save[0, 25] = esky poi_save[0, 26] = Tmrt[int(poisxy[k, 2]), int(poisxy[k, 1])] @@ -674,54 +1091,119 @@ def solweig_run(configPath, feedback): poi_save[0, 28] = CI poi_save[0, 29] = shadow[int(poisxy[k, 2]), int(poisxy[k, 1])] poi_save[0, 30] = svf[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 31] = svfbuveg[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 31] = svfbuveg[ + int(poisxy[k, 2]), int(poisxy[k, 1]) + ] poi_save[0, 32] = KsideI[int(poisxy[k, 2]), int(poisxy[k, 1])] # Recalculating wind speed based on powerlaw WsPET = (1.1 / sensorheight) ** 0.2 * Ws[i] - WsUTCI = (10. / sensorheight) ** 0.2 * Ws[i] - resultPET = p._PET(Ta[i], RH[i], Tmrt[int(poisxy[k, 2]), int(poisxy[k, 1])], WsPET, - mbody, age, ht, activity, clo, sex) + WsUTCI = (10.0 / sensorheight) ** 0.2 * Ws[i] + resultPET = p._PET( + Ta[i], + RH[i], + Tmrt[int(poisxy[k, 2]), int(poisxy[k, 1])], + WsPET, + mbody, + age, + ht, + activity, + clo, + sex, + ) poi_save[0, 33] = resultPET - resultUTCI = utci.utci_calculator(Ta[i], RH[i], Tmrt[int(poisxy[k, 2]), int(poisxy[k, 1])], - WsUTCI) + resultUTCI = utci.utci_calculator( + Ta[i], + RH[i], + Tmrt[int(poisxy[k, 2]), int(poisxy[k, 1])], + WsUTCI, + ) poi_save[0, 34] = resultUTCI - poi_save[0, 35] = CI_Tg - poi_save[0, 36] = CI_TgG - poi_save[0, 37] = KsideD[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 38] = Lside[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 39] = dRad[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 40] = Kside[int(poisxy[k, 2]), int(poisxy[k, 1])] - data_out = configDict['output_dir'] + '/POI_' + str(poiname[k]) + '.txt' + poi_save[0, 35] = CI_TgG + poi_save[0, 36] = KsideD[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 37] = Lside[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 38] = dRad[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 39] = Kside[int(poisxy[k, 2]), int(poisxy[k, 1])] + data_out = ( + configDict["output_dir"] + + "/POI_" + + str(poiname[k]) + + ".txt" + ) # f_handle = file(data_out, 'a') - f_handle = open(data_out, 'ab') + f_handle = open(data_out, "ab") np.savetxt(f_handle, poi_save, fmt=numformat) f_handle.close() # If wall temperature parameterization scheme is in use - if configDict['wallscheme'] == 1: # folderWallScheme: TODO: Fix for standalone + if ( + configDict["wallscheme"] == 1 + ): # folderWallScheme: TODO: Fix for standalone # Store wall data for output if not woisxy is None: for k in range(0, woisxy.shape[0]): - temp_wall = voxelTable.loc[((voxelTable['ypos'] == woisxy[k, 2]) & (voxelTable['xpos'] == woisxy[k, 1])), 'wallTemperature'].to_numpy() - K_in = voxelTable.loc[((voxelTable['ypos'] == woisxy[k, 2]) & (voxelTable['xpos'] == woisxy[k, 1])), 'K_in'].to_numpy() - L_in = voxelTable.loc[((voxelTable['ypos'] == woisxy[k, 2]) & (voxelTable['xpos'] == woisxy[k, 1])), 'L_in'].to_numpy() - wallShade = voxelTable.loc[((voxelTable['ypos'] == woisxy[k, 2]) & (voxelTable['xpos'] == woisxy[k, 1])), 'wallShade'].to_numpy() - temp_all = np.concatenate([temp_wall, K_in, L_in, wallShade]) + temp_wall = voxelTable.loc[ + ( + (voxelTable["ypos"] == woisxy[k, 2]) + & (voxelTable["xpos"] == woisxy[k, 1]) + ), + "wallTemperature", + ].to_numpy() + K_in = voxelTable.loc[ + ( + (voxelTable["ypos"] == woisxy[k, 2]) + & (voxelTable["xpos"] == woisxy[k, 1]) + ), + "K_in", + ].to_numpy() + L_in = voxelTable.loc[ + ( + (voxelTable["ypos"] == woisxy[k, 2]) + & (voxelTable["xpos"] == woisxy[k, 1]) + ), + "L_in", + ].to_numpy() + wallShade = voxelTable.loc[ + ( + (voxelTable["ypos"] == woisxy[k, 2]) + & (voxelTable["xpos"] == woisxy[k, 1]) + ), + "wallShade", + ].to_numpy() + temp_all = np.concatenate( + [temp_wall, K_in, L_in, wallShade] + ) # temp_all = np.concatenate([temp_wall]) # wall_data = np.zeros((1, 7 + temp_wall.shape[0])) wall_data = np.zeros((1, 7 + temp_all.shape[0])) # Part of file name (wallid), i.e. WOI_wallid.txt - data_out = configDict['output_dir'] + '/WOI_' + str(woiname[k]) + '.txt' + data_out = ( + configDict["output_dir"] + + "/WOI_" + + str(woiname[k]) + + ".txt" + ) if i == 0: # Output file header - #header = 'yyyy id it imin dectime Ta SVF Ts' - header = 'yyyy id it imin dectime Ta SVF' + ' Ts' * temp_wall.shape[0] + ' Kin' * K_in.shape[0] + ' Lin' * L_in.shape[0] + ' shade' * wallShade.shape[0] + # header = 'yyyy id it imin dectime Ta SVF Ts' + header = ( + "yyyy id it imin dectime Ta SVF" + + " Ts" * temp_wall.shape[0] + + " Kin" * K_in.shape[0] + + " Lin" * L_in.shape[0] + + " shade" * wallShade.shape[0] + ) # Part of file name (wallid), i.e. WOI_wallid.txt # woiname = voxelTable.loc[((voxelTable['ypos'] == woisxy[k, 2]) & (voxelTable['xpos'] == woisxy[k, 1])), 'wallId'].to_numpy()[0] - woi_save = [] # - np.savetxt(data_out, woi_save, delimiter=' ', header=header, comments='') + woi_save = [] # + np.savetxt( + data_out, + woi_save, + delimiter=" ", + header=header, + comments="", + ) # Fill wall_data with variables - wall_data[0, 0] = YYYY[0][i] + wall_data[0, 0] = YYYY[0][i] wall_data[0, 1] = jday[0][i] wall_data[0, 2] = hours[i] wall_data[0, 3] = minu[i] @@ -731,98 +1213,253 @@ def solweig_run(configPath, feedback): wall_data[0, 7:] = temp_all # Num format for output file data - woi_numformat = '%d %d %d %d %.5f %.2f %.2f' + ' %.2f' * temp_all.shape[0] + woi_numformat = ( + "%d %d %d %d %.5f %.2f %.2f" + + " %.2f" * temp_all.shape[0] + ) # Open file, add data, save - f_handle = open(data_out, 'ab') + f_handle = open(data_out, "ab") np.savetxt(f_handle, wall_data, fmt=woi_numformat) - f_handle.close() + f_handle.close() # Save wall temperature/radiation as NetCDF TODO: fix for standAlone? - if configDict['wallnetcdf'] == 1: # wallNetCDF: - netcdf_output = configDict['output_dir'] + '/walls.nc' - walls_as_netcdf(voxelTable, rows, cols, met_for_xarray, i, dsm, configDict['filepath_dsm'], netcdf_output) + if configDict["wallnetcdf"] == "1": # wallNetCDF: + netcdf_output = configDict["outputDir"] + "/walls.nc" + walls_as_netcdf( + voxelTable, + rows, + cols, + met_for_xarray, + i, + dsm, + configDict["filepath_dsm"], + netcdf_output, + ) if hours[i] < 10: - XH = '0' + XH = "0" else: - XH = '' + XH = "" if minu[i] < 10: - XM = '0' + XM = "0" else: - XM = '' - - time_code = (str(int(YYYY[0, i])) + "_" + str(int(DOY[i])) + "_" + XH + str(int(hours[i])) + XM + str(int(minu[i])) + w) - - if configDict['outputtmrt'] == 1: + XM = "" + + time_code = ( + str(int(YYYY[0, i])) + + "_" + + str(int(DOY[i])) + + "_" + + XH + + str(int(hours[i])) + + XM + + str(int(minu[i])) + + w + ) + + if configDict["outputtmrt"] == "1": if standAlone == 0: - saveraster(gdal_dsm, configDict['output_dir'] + '/Tmrt_' + time_code + '.tif', Tmrt) + saveraster( + gdal_dsm, + configDict["output_dir"] + "/Tmrt_" + time_code + ".tif", + Tmrt, + ) else: - common.save_raster(configDict['output_dir'] + '/Tmrt_' + time_code + '.tif', Tmrt, dsm_transf, dsm_crs) - if configDict['outputkup'] == 1: - if standAlone == 0: - saveraster(gdal_dsm, configDict['output_dir'] + '/Kup_' + time_code + '.tif', Kup) + common.save_raster( + configDict["output_dir"] + "/Tmrt_" + time_code + ".tif", + Tmrt, + dsm_transf, + dsm_crs, + ) + if configDict["outputkup"] == "1": + if standAlone == 0: + saveraster( + gdal_dsm, + configDict["output_dir"] + "/Kup_" + time_code + ".tif", + Kup, + ) else: - common.save_raster(configDict['output_dir'] + '/Kup_' + time_code + '.tif', Kup, dsm_transf, dsm_crs) - if configDict['outputkdown'] == 1: + common.save_raster( + configDict["output_dir"] + "/Kup_" + time_code + ".tif", + Kup, + dsm_transf, + dsm_crs, + ) + if configDict["outputkdown"] == "1": if standAlone == 0: - saveraster(gdal_dsm, configDict['output_dir'] + '/Kdown_' + time_code + '.tif', Kdown) + saveraster( + gdal_dsm, + configDict["output_dir"] + "/Kdown_" + time_code + ".tif", + Kdown, + ) else: - common.save_raster(configDict['output_dir'] + '/Kdown_' + time_code + '.tif', Kdown, dsm_transf, dsm_crs) - if configDict['outputlup'] == 1: + common.save_raster( + configDict["output_dir"] + "/Kdown_" + time_code + ".tif", + Kdown, + dsm_transf, + dsm_crs, + ) + if configDict["outputlup"] == "1": if standAlone == 0: - saveraster(gdal_dsm, configDict['output_dir'] + '/Lup_' + time_code + '.tif', Lup) + saveraster( + gdal_dsm, + configDict["output_dir"] + "/Lup_" + time_code + ".tif", + Lup, + ) else: - common.save_raster(configDict['output_dir'] + '/Lup_' + time_code + '.tif', Lup, dsm_transf, dsm_crs) - if configDict['outputldown'] == 1: + common.save_raster( + configDict["output_dir"] + "/Lup_" + time_code + ".tif", + Lup, + dsm_transf, + dsm_crs, + ) + if configDict["outputldown"] == "1": if standAlone == 0: - saveraster(gdal_dsm, configDict['output_dir'] + '/Ldown_' + time_code + '.tif', Ldown) + saveraster( + gdal_dsm, + configDict["output_dir"] + "/Ldown_" + time_code + ".tif", + Ldown, + ) else: - common.save_raster(configDict['output_dir'] + '/Ldown_' + time_code + '.tif', Ldown, dsm_transf, dsm_crs) - if configDict['outputsh'] == 1: + common.save_raster( + configDict["output_dir"] + "/Ldown_" + time_code + ".tif", + Ldown, + dsm_transf, + dsm_crs, + ) + if configDict["outputsh"] == "1": if standAlone == 0: - saveraster(gdal_dsm, configDict['output_dir'] + '/Shadow_' + time_code + '.tif', shadow) + saveraster( + gdal_dsm, + configDict["output_dir"] + "/Shadow_" + time_code + ".tif", + shadow, + ) else: - common.save_raster(configDict['output_dir'] + '/Shadow_' + time_code + '.tif', shadow, dsm_transf, dsm_crs) - - if configDict['outputkdiff'] == 1: + common.save_raster( + configDict["output_dir"] + "/Shadow_" + time_code + ".tif", + shadow, + dsm_transf, + dsm_crs, + ) + + if configDict["outputkdiff"] == "1": if standAlone == 0: - saveraster(gdal_dsm, configDict['output_dir'] + '/Kdiff_' + time_code + '.tif', dRad) + saveraster( + gdal_dsm, + configDict["output_dir"] + "/Kdiff_" + time_code + ".tif", + dRad, + ) else: - common.save_raster(configDict['output_dir'] + '/Kdiff_' + time_code + '.tif', dRad, dsm_transf, dsm_crs) + common.save_raster( + configDict["output_dir"] + "/Kdiff_" + time_code + ".tif", + dRad, + dsm_transf, + dsm_crs, + ) # Sky view image of patches - if ((anisotropic_sky == 1) & (i == 0) & (not poisxy is None)): - for k in range(poisxy.shape[0]): - Lsky_patch_characteristics[:,2] = patch_characteristics[:,k] - skyviewimage_out = configDict['output_dir'] + '/POI_' + str(poiname[k]) + '.png' - PolarBarPlot(Lsky_patch_characteristics, altitude[0][i], azimuth[0][i], 'Hemisphere partitioning', skyviewimage_out, 0, 5, 0) + if (anisotropic_sky == 1) & (i == 0) & (not poisxy is None): + for k in range(poisxy.shape[0]): + Lsky_patch_characteristics[:, 2] = patch_characteristics[:, k] + skyviewimage_out = ( + configDict["output_dir"] + + "/POI_" + + str(poiname[k]) + + ".png" + ) + PolarBarPlot( + Lsky_patch_characteristics, + altitude[0][i], + azimuth[0][i], + "Hemisphere partitioning", + skyviewimage_out, + 0, + 5, + 0, + ) # Save files for Tree Planter - if configDict['outputtreeplanter'] == 1: # outputTreeplanter: + if configDict["outputtreeplanter"] == "1": # outputTreeplanter: if feedback is not None: feedback.setProgressText("Saving files for Tree Planter tool") # Save DSM - copyfile(configDict['filepath_dsm'], configDict['output_dir'] + '/DSM.tif') + copyfile( + configDict["filepath_dsm"], configDict["output_dir"] + "/DSM.tif" + ) # Save CDSM if usevegdem == 1: - copyfile(configDict['filepath_cdsm'], configDict['output_dir'] + '/CDSM.tif') + copyfile( + configDict["filepath_cdsm"], + configDict["output_dir"] + "/CDSM.tif", + ) - albedo_g = param['Albedo']['Effective']['Value']['Cobble_stone_2014a'] - eground = param['Emissivity']['Value']['Cobble_stone_2014a'] + albedo_g = param["Albedo"]["Effective"]["Value"]["Cobble_stone_2014a"] + eground = param["Emissivity"]["Value"]["Cobble_stone_2014a"] # Saving settings from SOLWEIG for SOLWEIG1D in TreePlanter - settingsHeader = 'UTC, posture, onlyglobal, landcover, anisotropic, cylinder, albedo_walls, albedo_ground, emissivity_walls, emissivity_ground, absK, absL, elevation, patch_option' - settingsFmt = '%i', '%i', '%i', '%i', '%i', '%i', '%1.2f', '%1.2f', '%1.2f', '%1.2f', '%1.2f', '%1.2f', '%1.2f', '%i' - settingsData = np.array([[int(configDict['utc']), pos, onlyglobal, landcover, anisotropic_sky, cyl, albedo_b, albedo_g, ewall, eground, absK, absL, alt, patch_option]]) + settingsHeader = "UTC, posture, onlyglobal, landcover, anisotropic, cylinder, albedo_walls, albedo_ground, emissivity_walls, emissivity_ground, absK, absL, elevation, patch_option" + settingsFmt = ( + "%i", + "%i", + "%i", + "%i", + "%i", + "%i", + "%1.2f", + "%1.2f", + "%1.2f", + "%1.2f", + "%1.2f", + "%1.2f", + "%1.2f", + "%i", + ) + settingsData = np.array( + [ + [ + int(configDict["utc"]), + pos, + onlyglobal, + landcover, + anisotropic_sky, + cyl, + albedo_b, + albedo_g, + ewall, + eground, + absK, + absL, + alt, + patch_option, + ] + ] + ) # print(settingsData) - np.savetxt(configDict['output_dir'] + '/treeplantersettings.txt', settingsData, fmt=settingsFmt, header=settingsHeader, delimiter=' ') + np.savetxt( + configDict["output_dir"] + "/treeplantersettings.txt", + settingsData, + fmt=settingsFmt, + header=settingsHeader, + delimiter=" ", + ) # Copying met file for SpatialTC - copyfile(configDict['input_met'], configDict['output_dir'] + '/metforcing.txt') - - tmrtplot = tmrtplot / Ta.__len__() # fix average Tmrt instead of sum, 20191022 + copyfile( + configDict["input_met"], configDict["output_dir"] + "/metforcing.txt" + ) + + tmrtplot = ( + tmrtplot / Ta.__len__() + ) # fix average Tmrt instead of sum, 20191022 if standAlone == 0: - saveraster(gdal_dsm, configDict['output_dir'] + '/Tmrt_average.tif', tmrtplot) + saveraster( + gdal_dsm, configDict["output_dir"] + "/Tmrt_average.tif", tmrtplot + ) else: - common.save_raster(configDict['output_dir'] + '/Tmrt_average.tif', tmrtplot, dsm_transf, dsm_crs) + common.save_raster( + configDict["output_dir"] + "/Tmrt_average.tif", + tmrtplot, + dsm_transf, + dsm_crs, + ) diff --git a/functions/SOLWEIGpython/Tgmaps_v1.py b/functions/SOLWEIGpython/Tgmaps_v1.py index 266aa76..b3a856e 100644 --- a/functions/SOLWEIGpython/Tgmaps_v1.py +++ b/functions/SOLWEIGpython/Tgmaps_v1.py @@ -1,8 +1,9 @@ import numpy as np + def Tgmaps_v1(lc_grid, solweig_parameters): - #Tgmaps_v1 Populates grids with cooeficients for Tg wave + # Tgmaps_v1 Populates grids with cooeficients for Tg wave # Detailed explanation goes here lc_grid[lc_grid >= 100] = 2 id = np.unique(lc_grid) @@ -15,14 +16,33 @@ def Tgmaps_v1(lc_grid, solweig_parameters): for i in id: # row = (lc_class[:, 0] == id[i]) - Tstart[Tstart == i] = solweig_parameters['Tstart']['Value'][solweig_parameters['Names']['Value'][str(i)]] - alb_grid[alb_grid == i] = solweig_parameters['Albedo']['Effective']['Value'][solweig_parameters['Names']['Value'][str(i)]] - emis_grid[emis_grid == i] = solweig_parameters['Emissivity']['Value'][solweig_parameters['Names']['Value'][str(i)]] - TmaxLST[TmaxLST == i] = solweig_parameters['TmaxLST']['Value'][solweig_parameters['Names']['Value'][str(i)]] - TgK[TgK == i] = solweig_parameters['Ts_deg']['Value'][solweig_parameters['Names']['Value'][str(i)]] + Tstart[Tstart == i] = solweig_parameters["Tstart"]["Value"][ + solweig_parameters["Names"]["Value"][str(i)] + ] + alb_grid[alb_grid == i] = solweig_parameters["Albedo"]["Effective"][ + "Value" + ][solweig_parameters["Names"]["Value"][str(i)]] + emis_grid[emis_grid == i] = solweig_parameters["Emissivity"]["Value"][ + solweig_parameters["Names"]["Value"][str(i)] + ] + TmaxLST[TmaxLST == i] = solweig_parameters["TmaxLST"]["Value"][ + solweig_parameters["Names"]["Value"][str(i)] + ] + TgK[TgK == i] = solweig_parameters["Ts_deg"]["Value"][ + solweig_parameters["Names"]["Value"][str(i)] + ] - TgK_wall = solweig_parameters['Ts_deg']['Value']['Walls'] - Tstart_wall = solweig_parameters['Tstart']['Value']['Walls'] - TmaxLST_wall = solweig_parameters['TmaxLST']['Value']['Walls'] + TgK_wall = solweig_parameters["Ts_deg"]["Value"]["Walls"] + Tstart_wall = solweig_parameters["Tstart"]["Value"]["Walls"] + TmaxLST_wall = solweig_parameters["TmaxLST"]["Value"]["Walls"] - return TgK, Tstart, alb_grid, emis_grid, TgK_wall, Tstart_wall, TmaxLST, TmaxLST_wall + return ( + TgK, + Tstart, + alb_grid, + emis_grid, + TgK_wall, + Tstart_wall, + TmaxLST, + TmaxLST_wall, + ) diff --git a/functions/SOLWEIGpython/TsWaveDelay_2015a.py b/functions/SOLWEIGpython/TsWaveDelay_2015a.py index 11637c8..70f5e14 100644 --- a/functions/SOLWEIGpython/TsWaveDelay_2015a.py +++ b/functions/SOLWEIGpython/TsWaveDelay_2015a.py @@ -7,17 +7,21 @@ def TsWaveDelay_2015a(gvfLup, firstdaytime, timeadd, timestepdec, Tgmap1): if firstdaytime == 1: # "first in morning" Tgmap1 = Tgmap0 - if timeadd >= (59/1440): # more or equal to 59 min - weight1 = np.exp(-33.27 * timeadd) # surface temperature delay function - 1 step + if timeadd >= (59 / 1440): # more or equal to 59 min + weight1 = np.exp( + -33.27 * timeadd + ) # surface temperature delay function - 1 step Tgmap1 = Tgmap0 * (1 - weight1) + Tgmap1 * weight1 Lup = Tgmap1 - if timestepdec > (59/1440): + if timestepdec > (59 / 1440): timeadd = timestepdec else: timeadd = 0 else: timeadd = timeadd + timestepdec - weight1 = np.exp(-33.27 * timeadd) # surface temperature delay function - 1 step - Lup = (Tgmap0 * (1 - weight1) + Tgmap1 * weight1) + weight1 = np.exp( + -33.27 * timeadd + ) # surface temperature delay function - 1 step + Lup = Tgmap0 * (1 - weight1) + Tgmap1 * weight1 - return Lup, timeadd, Tgmap1 \ No newline at end of file + return Lup, timeadd, Tgmap1 diff --git a/functions/SOLWEIGpython/UTCI_calculations.py b/functions/SOLWEIGpython/UTCI_calculations.py index dbc5959..a773a19 100644 --- a/functions/SOLWEIGpython/UTCI_calculations.py +++ b/functions/SOLWEIGpython/UTCI_calculations.py @@ -1,222 +1,232 @@ import numpy as np + def utci_polynomial(D_Tmrt, Ta, va, Pa): # calculate 6th order polynomial as approximation - UTCI_approx = Ta + \ - (6.07562052E-01) + \ - (-2.27712343E-02) * Ta + \ - (8.06470249E-04) * Ta * Ta + \ - (-1.54271372E-04) * Ta * Ta * Ta + \ - (-3.24651735E-06) * Ta * Ta * Ta * Ta + \ - (7.32602852E-08) * Ta * Ta * Ta * Ta * Ta + \ - (1.35959073E-09) * Ta * Ta * Ta * Ta * Ta * Ta + \ - (-2.25836520E+00) * va + \ - (8.80326035E-02) * Ta * va + \ - (2.16844454E-03) * Ta * Ta * va + \ - (-1.53347087E-05) * Ta * Ta * Ta * va + \ - (-5.72983704E-07) * Ta * Ta * Ta * Ta * va + \ - (-2.55090145E-09) * Ta * Ta * Ta * Ta * Ta * va + \ - (-7.51269505E-01) * va * va + \ - (-4.08350271E-03) * Ta * va * va + \ - (-5.21670675E-05) * Ta * Ta * va * va + \ - (1.94544667E-06) * Ta * Ta * Ta * va * va + \ - (1.14099531E-08) * Ta * Ta * Ta * Ta * va * va + \ - (1.58137256E-01) * va * va * va + \ - (-6.57263143E-05) * Ta * va * va * va + \ - (2.22697524E-07) * Ta * Ta * va * va * va + \ - (-4.16117031E-08) * Ta * Ta * Ta * va * va * va + \ - (-1.27762753E-02) * va * va * va * va + \ - (9.66891875E-06) * Ta * va * va * va * va + \ - (2.52785852E-09) * Ta * Ta * va * va * va * va + \ - (4.56306672E-04) * va * va * va * va * va + \ - (-1.74202546E-07) * Ta * va * va * va * va * va + \ - (-5.91491269E-06) * va * va * va * va * va * va + \ - (3.98374029E-01) * D_Tmrt + \ - (1.83945314E-04) * Ta * D_Tmrt + \ - (-1.73754510E-04) * Ta * Ta * D_Tmrt + \ - (-7.60781159E-07) * Ta * Ta * Ta * D_Tmrt + \ - (3.77830287E-08) * Ta * Ta * Ta * Ta * D_Tmrt + \ - (5.43079673E-10) * Ta * Ta * Ta * Ta * Ta * D_Tmrt + \ - (-2.00518269E-02) * va * D_Tmrt + \ - (8.92859837E-04) * Ta * va * D_Tmrt + \ - (3.45433048E-06) * Ta * Ta * va * D_Tmrt + \ - (-3.77925774E-07) * Ta * Ta * Ta * va * D_Tmrt + \ - (-1.69699377E-09) * Ta * Ta * Ta * Ta * va * D_Tmrt + \ - (1.69992415E-04) * va * va * D_Tmrt + \ - (-4.99204314E-05) * Ta * va * va * D_Tmrt + \ - (2.47417178E-07) * Ta * Ta * va * va * D_Tmrt + \ - (1.07596466E-08) * Ta * Ta * Ta * va * va * D_Tmrt + \ - (8.49242932E-05) * va * va * va * D_Tmrt + \ - (1.35191328E-06) * Ta * va * va * va * D_Tmrt + \ - (-6.21531254E-09) * Ta * Ta * va * va * va * D_Tmrt + \ - (-4.99410301E-06) * va * va * va * va * D_Tmrt + \ - (-1.89489258E-08) * Ta * va * va * va * va * D_Tmrt + \ - (8.15300114E-08) * va * va * va * va * va * D_Tmrt + \ - (7.55043090E-04) * D_Tmrt * D_Tmrt + \ - (-5.65095215E-05) * Ta * D_Tmrt * D_Tmrt + \ - (-4.52166564E-07) * Ta * Ta * D_Tmrt * D_Tmrt + \ - (2.46688878E-08) * Ta * Ta * Ta * D_Tmrt * D_Tmrt + \ - (2.42674348E-10) * Ta * Ta * Ta * Ta * D_Tmrt * D_Tmrt + \ - (1.54547250E-04) * va * D_Tmrt * D_Tmrt + \ - (5.24110970E-06) * Ta * va * D_Tmrt * D_Tmrt + \ - (-8.75874982E-08) * Ta * Ta * va * D_Tmrt * D_Tmrt + \ - (-1.50743064E-09) * Ta * Ta * Ta * va * D_Tmrt * D_Tmrt + \ - (-1.56236307E-05) * va * va * D_Tmrt * D_Tmrt + \ - (-1.33895614E-07) * Ta * va * va * D_Tmrt * D_Tmrt + \ - (2.49709824E-09) * Ta * Ta * va * va * D_Tmrt * D_Tmrt + \ - (6.51711721E-07) * va * va * va * D_Tmrt * D_Tmrt + \ - (1.94960053E-09) * Ta * va * va * va * D_Tmrt * D_Tmrt + \ - (-1.00361113E-08) * va * va * va * va * D_Tmrt * D_Tmrt + \ - (-1.21206673E-05) * D_Tmrt * D_Tmrt * D_Tmrt + \ - (-2.18203660E-07) * Ta * D_Tmrt * D_Tmrt * D_Tmrt + \ - (7.51269482E-09) * Ta * Ta * D_Tmrt * D_Tmrt * D_Tmrt + \ - (9.79063848E-11) * Ta * Ta * Ta * D_Tmrt * D_Tmrt * D_Tmrt + \ - (1.25006734E-06) * va * D_Tmrt * D_Tmrt * D_Tmrt + \ - (-1.81584736E-09) * Ta * va * D_Tmrt * D_Tmrt * D_Tmrt + \ - (-3.52197671E-10) * Ta * Ta * va * D_Tmrt * D_Tmrt * D_Tmrt + \ - (-3.36514630E-08) * va * va * D_Tmrt * D_Tmrt * D_Tmrt + \ - (1.35908359E-10) * Ta * va * va * D_Tmrt * D_Tmrt * D_Tmrt + \ - (4.17032620E-10) * va * va * va * D_Tmrt * D_Tmrt * D_Tmrt + \ - (-1.30369025E-09) * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt + \ - (4.13908461E-10) * Ta * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt + \ - (9.22652254E-12) * Ta * Ta * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt + \ - (-5.08220384E-09) * va * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt + \ - (-2.24730961E-11) * Ta * va * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt + \ - (1.17139133E-10) * va * va * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt + \ - (6.62154879E-10) * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt + \ - (4.03863260E-13) * Ta * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt + \ - (1.95087203E-12) * va * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt + \ - (-4.73602469E-12) * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt + \ - (5.12733497E+00) * Pa + \ - (-3.12788561E-01) * Ta * Pa + \ - (-1.96701861E-02) * Ta * Ta * Pa + \ - (9.99690870E-04) * Ta * Ta * Ta * Pa + \ - (9.51738512E-06) * Ta * Ta * Ta * Ta * Pa + \ - (-4.66426341E-07) * Ta * Ta * Ta * Ta * Ta * Pa + \ - (5.48050612E-01) * va * Pa + \ - (-3.30552823E-03) * Ta * va * Pa + \ - (-1.64119440E-03) * Ta * Ta * va * Pa + \ - (-5.16670694E-06) * Ta * Ta * Ta * va * Pa + \ - (9.52692432E-07) * Ta * Ta * Ta * Ta * va * Pa + \ - (-4.29223622E-02) * va * va * Pa + \ - (5.00845667E-03) * Ta * va * va * Pa + \ - (1.00601257E-06) * Ta * Ta * va * va * Pa + \ - (-1.81748644E-06) * Ta * Ta * Ta * va * va * Pa + \ - (-1.25813502E-03) * va * va * va * Pa + \ - (-1.79330391E-04) * Ta * va * va * va * Pa + \ - (2.34994441E-06) * Ta * Ta * va * va * va * Pa + \ - (1.29735808E-04) * va * va * va * va * Pa + \ - (1.29064870E-06) * Ta * va * va * va * va * Pa + \ - (-2.28558686E-06) * va * va * va * va * va * Pa + \ - (-3.69476348E-02) * D_Tmrt * Pa + \ - (1.62325322E-03) * Ta * D_Tmrt * Pa + \ - (-3.14279680E-05) * Ta * Ta * D_Tmrt * Pa + \ - (2.59835559E-06) * Ta * Ta * Ta * D_Tmrt * Pa + \ - (-4.77136523E-08) * Ta * Ta * Ta * Ta * D_Tmrt * Pa + \ - (8.64203390E-03) * va * D_Tmrt * Pa + \ - (-6.87405181E-04) * Ta * va * D_Tmrt * Pa + \ - (-9.13863872E-06) * Ta * Ta * va * D_Tmrt * Pa + \ - (5.15916806E-07) * Ta * Ta * Ta * va * D_Tmrt * Pa + \ - (-3.59217476E-05) * va * va * D_Tmrt * Pa + \ - (3.28696511E-05) * Ta * va * va * D_Tmrt * Pa + \ - (-7.10542454E-07) * Ta * Ta * va * va * D_Tmrt * Pa + \ - (-1.24382300E-05) * va * va * va * D_Tmrt * Pa + \ - (-7.38584400E-09) * Ta * va * va * va * D_Tmrt * Pa + \ - (2.20609296E-07) * va * va * va * va * D_Tmrt * Pa + \ - (-7.32469180E-04) * D_Tmrt * D_Tmrt * Pa + \ - (-1.87381964E-05) * Ta * D_Tmrt * D_Tmrt * Pa + \ - (4.80925239E-06) * Ta * Ta * D_Tmrt * D_Tmrt * Pa + \ - (-8.75492040E-08) * Ta * Ta * Ta * D_Tmrt * D_Tmrt * Pa + \ - (2.77862930E-05) * va * D_Tmrt * D_Tmrt * Pa + \ - (-5.06004592E-06) * Ta * va * D_Tmrt * D_Tmrt * Pa + \ - (1.14325367E-07) * Ta * Ta * va * D_Tmrt * D_Tmrt * Pa + \ - (2.53016723E-06) * va * va * D_Tmrt * D_Tmrt * Pa + \ - (-1.72857035E-08) * Ta * va * va * D_Tmrt * D_Tmrt * Pa + \ - (-3.95079398E-08) * va * va * va * D_Tmrt * D_Tmrt * Pa + \ - (-3.59413173E-07) * D_Tmrt * D_Tmrt * D_Tmrt * Pa + \ - (7.04388046E-07) * Ta * D_Tmrt * D_Tmrt * D_Tmrt * Pa + \ - (-1.89309167E-08) * Ta * Ta * D_Tmrt * D_Tmrt * D_Tmrt * Pa + \ - (-4.79768731E-07) * va * D_Tmrt * D_Tmrt * D_Tmrt * Pa + \ - (7.96079978E-09) * Ta * va * D_Tmrt * D_Tmrt * D_Tmrt * Pa + \ - (1.62897058E-09) * va * va * D_Tmrt * D_Tmrt * D_Tmrt * Pa + \ - (3.94367674E-08) * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * Pa + \ - (-1.18566247E-09) * Ta * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * Pa + \ - (3.34678041E-10) * va * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * Pa + \ - (-1.15606447E-10) * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * Pa + \ - (-2.80626406E+00) * Pa * Pa + \ - (5.48712484E-01) * Ta * Pa * Pa + \ - (-3.99428410E-03) * Ta * Ta * Pa * Pa + \ - (-9.54009191E-04) * Ta * Ta * Ta * Pa * Pa + \ - (1.93090978E-05) * Ta * Ta * Ta * Ta * Pa * Pa + \ - (-3.08806365E-01) * va * Pa * Pa + \ - (1.16952364E-02) * Ta * va * Pa * Pa + \ - (4.95271903E-04) * Ta * Ta * va * Pa * Pa + \ - (-1.90710882E-05) * Ta * Ta * Ta * va * Pa * Pa + \ - (2.10787756E-03) * va * va * Pa * Pa + \ - (-6.98445738E-04) * Ta * va * va * Pa * Pa + \ - (2.30109073E-05) * Ta * Ta * va * va * Pa * Pa + \ - (4.17856590E-04) * va * va * va * Pa * Pa + \ - (-1.27043871E-05) * Ta * va * va * va * Pa * Pa + \ - (-3.04620472E-06) * va * va * va * va * Pa * Pa + \ - (5.14507424E-02) * D_Tmrt * Pa * Pa + \ - (-4.32510997E-03) * Ta * D_Tmrt * Pa * Pa + \ - (8.99281156E-05) * Ta * Ta * D_Tmrt * Pa * Pa + \ - (-7.14663943E-07) * Ta * Ta * Ta * D_Tmrt * Pa * Pa + \ - (-2.66016305E-04) * va * D_Tmrt * Pa * Pa + \ - (2.63789586E-04) * Ta * va * D_Tmrt * Pa * Pa + \ - (-7.01199003E-06) * Ta * Ta * va * D_Tmrt * Pa * Pa + \ - (-1.06823306E-04) * va * va * D_Tmrt * Pa * Pa + \ - (3.61341136E-06) * Ta * va * va * D_Tmrt * Pa * Pa + \ - (2.29748967E-07) * va * va * va * D_Tmrt * Pa * Pa + \ - (3.04788893E-04) * D_Tmrt * D_Tmrt * Pa * Pa + \ - (-6.42070836E-05) * Ta * D_Tmrt * D_Tmrt * Pa * Pa + \ - (1.16257971E-06) * Ta * Ta * D_Tmrt * D_Tmrt * Pa * Pa + \ - (7.68023384E-06) * va * D_Tmrt * D_Tmrt * Pa * Pa + \ - (-5.47446896E-07) * Ta * va * D_Tmrt * D_Tmrt * Pa * Pa + \ - (-3.59937910E-08) * va * va * D_Tmrt * D_Tmrt * Pa * Pa + \ - (-4.36497725E-06) * D_Tmrt * D_Tmrt * D_Tmrt * Pa * Pa + \ - (1.68737969E-07) * Ta * D_Tmrt * D_Tmrt * D_Tmrt * Pa * Pa + \ - (2.67489271E-08) * va * D_Tmrt * D_Tmrt * D_Tmrt * Pa * Pa + \ - (3.23926897E-09) * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * Pa * Pa + \ - (-3.53874123E-02) * Pa * Pa * Pa + \ - (-2.21201190E-01) * Ta * Pa * Pa * Pa + \ - (1.55126038E-02) * Ta * Ta * Pa * Pa * Pa + \ - (-2.63917279E-04) * Ta * Ta * Ta * Pa * Pa * Pa + \ - (4.53433455E-02) * va * Pa * Pa * Pa + \ - (-4.32943862E-03) * Ta * va * Pa * Pa * Pa + \ - (1.45389826E-04) * Ta * Ta * va * Pa * Pa * Pa + \ - (2.17508610E-04) * va * va * Pa * Pa * Pa + \ - (-6.66724702E-05) * Ta * va * va * Pa * Pa * Pa + \ - (3.33217140E-05) * va * va * va * Pa * Pa * Pa + \ - (-2.26921615E-03) * D_Tmrt * Pa * Pa * Pa + \ - (3.80261982E-04) * Ta * D_Tmrt * Pa * Pa * Pa + \ - (-5.45314314E-09) * Ta * Ta * D_Tmrt * Pa * Pa * Pa + \ - (-7.96355448E-04) * va * D_Tmrt * Pa * Pa * Pa + \ - (2.53458034E-05) * Ta * va * D_Tmrt * Pa * Pa * Pa + \ - (-6.31223658E-06) * va * va * D_Tmrt * Pa * Pa * Pa + \ - (3.02122035E-04) * D_Tmrt * D_Tmrt * Pa * Pa * Pa + \ - (-4.77403547E-06) * Ta * D_Tmrt * D_Tmrt * Pa * Pa * Pa + \ - (1.73825715E-06) * va * D_Tmrt * D_Tmrt * Pa * Pa * Pa + \ - (-4.09087898E-07) * D_Tmrt * D_Tmrt * D_Tmrt * Pa * Pa * Pa + \ - (6.14155345E-01) * Pa * Pa * Pa * Pa + \ - (-6.16755931E-02) * Ta * Pa * Pa * Pa * Pa + \ - (1.33374846E-03) * Ta * Ta * Pa * Pa * Pa * Pa + \ - (3.55375387E-03) * va * Pa * Pa * Pa * Pa + \ - (-5.13027851E-04) * Ta * va * Pa * Pa * Pa * Pa + \ - (1.02449757E-04) * va * va * Pa * Pa * Pa * Pa + \ - (-1.48526421E-03) * D_Tmrt * Pa * Pa * Pa * Pa + \ - (-4.11469183E-05) * Ta * D_Tmrt * Pa * Pa * Pa * Pa + \ - (-6.80434415E-06) * va * D_Tmrt * Pa * Pa * Pa * Pa + \ - (-9.77675906E-06) * D_Tmrt * D_Tmrt * Pa * Pa * Pa * Pa + \ - (8.82773108E-02) * Pa * Pa * Pa * Pa * Pa + \ - (-3.01859306E-03) * Ta * Pa * Pa * Pa * Pa * Pa + \ - (1.04452989E-03) * va * Pa * Pa * Pa * Pa * Pa + \ - (2.47090539E-04) * D_Tmrt * Pa * Pa * Pa * Pa * Pa + \ - (1.48348065E-03) * Pa * Pa * Pa * Pa * Pa * Pa + UTCI_approx = ( + Ta + + (6.07562052e-01) + + (-2.27712343e-02) * Ta + + (8.06470249e-04) * Ta * Ta + + (-1.54271372e-04) * Ta * Ta * Ta + + (-3.24651735e-06) * Ta * Ta * Ta * Ta + + (7.32602852e-08) * Ta * Ta * Ta * Ta * Ta + + (1.35959073e-09) * Ta * Ta * Ta * Ta * Ta * Ta + + (-2.25836520e00) * va + + (8.80326035e-02) * Ta * va + + (2.16844454e-03) * Ta * Ta * va + + (-1.53347087e-05) * Ta * Ta * Ta * va + + (-5.72983704e-07) * Ta * Ta * Ta * Ta * va + + (-2.55090145e-09) * Ta * Ta * Ta * Ta * Ta * va + + (-7.51269505e-01) * va * va + + (-4.08350271e-03) * Ta * va * va + + (-5.21670675e-05) * Ta * Ta * va * va + + (1.94544667e-06) * Ta * Ta * Ta * va * va + + (1.14099531e-08) * Ta * Ta * Ta * Ta * va * va + + (1.58137256e-01) * va * va * va + + (-6.57263143e-05) * Ta * va * va * va + + (2.22697524e-07) * Ta * Ta * va * va * va + + (-4.16117031e-08) * Ta * Ta * Ta * va * va * va + + (-1.27762753e-02) * va * va * va * va + + (9.66891875e-06) * Ta * va * va * va * va + + (2.52785852e-09) * Ta * Ta * va * va * va * va + + (4.56306672e-04) * va * va * va * va * va + + (-1.74202546e-07) * Ta * va * va * va * va * va + + (-5.91491269e-06) * va * va * va * va * va * va + + (3.98374029e-01) * D_Tmrt + + (1.83945314e-04) * Ta * D_Tmrt + + (-1.73754510e-04) * Ta * Ta * D_Tmrt + + (-7.60781159e-07) * Ta * Ta * Ta * D_Tmrt + + (3.77830287e-08) * Ta * Ta * Ta * Ta * D_Tmrt + + (5.43079673e-10) * Ta * Ta * Ta * Ta * Ta * D_Tmrt + + (-2.00518269e-02) * va * D_Tmrt + + (8.92859837e-04) * Ta * va * D_Tmrt + + (3.45433048e-06) * Ta * Ta * va * D_Tmrt + + (-3.77925774e-07) * Ta * Ta * Ta * va * D_Tmrt + + (-1.69699377e-09) * Ta * Ta * Ta * Ta * va * D_Tmrt + + (1.69992415e-04) * va * va * D_Tmrt + + (-4.99204314e-05) * Ta * va * va * D_Tmrt + + (2.47417178e-07) * Ta * Ta * va * va * D_Tmrt + + (1.07596466e-08) * Ta * Ta * Ta * va * va * D_Tmrt + + (8.49242932e-05) * va * va * va * D_Tmrt + + (1.35191328e-06) * Ta * va * va * va * D_Tmrt + + (-6.21531254e-09) * Ta * Ta * va * va * va * D_Tmrt + + (-4.99410301e-06) * va * va * va * va * D_Tmrt + + (-1.89489258e-08) * Ta * va * va * va * va * D_Tmrt + + (8.15300114e-08) * va * va * va * va * va * D_Tmrt + + (7.55043090e-04) * D_Tmrt * D_Tmrt + + (-5.65095215e-05) * Ta * D_Tmrt * D_Tmrt + + (-4.52166564e-07) * Ta * Ta * D_Tmrt * D_Tmrt + + (2.46688878e-08) * Ta * Ta * Ta * D_Tmrt * D_Tmrt + + (2.42674348e-10) * Ta * Ta * Ta * Ta * D_Tmrt * D_Tmrt + + (1.54547250e-04) * va * D_Tmrt * D_Tmrt + + (5.24110970e-06) * Ta * va * D_Tmrt * D_Tmrt + + (-8.75874982e-08) * Ta * Ta * va * D_Tmrt * D_Tmrt + + (-1.50743064e-09) * Ta * Ta * Ta * va * D_Tmrt * D_Tmrt + + (-1.56236307e-05) * va * va * D_Tmrt * D_Tmrt + + (-1.33895614e-07) * Ta * va * va * D_Tmrt * D_Tmrt + + (2.49709824e-09) * Ta * Ta * va * va * D_Tmrt * D_Tmrt + + (6.51711721e-07) * va * va * va * D_Tmrt * D_Tmrt + + (1.94960053e-09) * Ta * va * va * va * D_Tmrt * D_Tmrt + + (-1.00361113e-08) * va * va * va * va * D_Tmrt * D_Tmrt + + (-1.21206673e-05) * D_Tmrt * D_Tmrt * D_Tmrt + + (-2.18203660e-07) * Ta * D_Tmrt * D_Tmrt * D_Tmrt + + (7.51269482e-09) * Ta * Ta * D_Tmrt * D_Tmrt * D_Tmrt + + (9.79063848e-11) * Ta * Ta * Ta * D_Tmrt * D_Tmrt * D_Tmrt + + (1.25006734e-06) * va * D_Tmrt * D_Tmrt * D_Tmrt + + (-1.81584736e-09) * Ta * va * D_Tmrt * D_Tmrt * D_Tmrt + + (-3.52197671e-10) * Ta * Ta * va * D_Tmrt * D_Tmrt * D_Tmrt + + (-3.36514630e-08) * va * va * D_Tmrt * D_Tmrt * D_Tmrt + + (1.35908359e-10) * Ta * va * va * D_Tmrt * D_Tmrt * D_Tmrt + + (4.17032620e-10) * va * va * va * D_Tmrt * D_Tmrt * D_Tmrt + + (-1.30369025e-09) * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt + + (4.13908461e-10) * Ta * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt + + (9.22652254e-12) * Ta * Ta * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt + + (-5.08220384e-09) * va * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt + + (-2.24730961e-11) * Ta * va * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt + + (1.17139133e-10) * va * va * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt + + (6.62154879e-10) * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt + + (4.03863260e-13) * Ta * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt + + (1.95087203e-12) * va * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt + + (-4.73602469e-12) + * D_Tmrt + * D_Tmrt + * D_Tmrt + * D_Tmrt + * D_Tmrt + * D_Tmrt + + (5.12733497e00) * Pa + + (-3.12788561e-01) * Ta * Pa + + (-1.96701861e-02) * Ta * Ta * Pa + + (9.99690870e-04) * Ta * Ta * Ta * Pa + + (9.51738512e-06) * Ta * Ta * Ta * Ta * Pa + + (-4.66426341e-07) * Ta * Ta * Ta * Ta * Ta * Pa + + (5.48050612e-01) * va * Pa + + (-3.30552823e-03) * Ta * va * Pa + + (-1.64119440e-03) * Ta * Ta * va * Pa + + (-5.16670694e-06) * Ta * Ta * Ta * va * Pa + + (9.52692432e-07) * Ta * Ta * Ta * Ta * va * Pa + + (-4.29223622e-02) * va * va * Pa + + (5.00845667e-03) * Ta * va * va * Pa + + (1.00601257e-06) * Ta * Ta * va * va * Pa + + (-1.81748644e-06) * Ta * Ta * Ta * va * va * Pa + + (-1.25813502e-03) * va * va * va * Pa + + (-1.79330391e-04) * Ta * va * va * va * Pa + + (2.34994441e-06) * Ta * Ta * va * va * va * Pa + + (1.29735808e-04) * va * va * va * va * Pa + + (1.29064870e-06) * Ta * va * va * va * va * Pa + + (-2.28558686e-06) * va * va * va * va * va * Pa + + (-3.69476348e-02) * D_Tmrt * Pa + + (1.62325322e-03) * Ta * D_Tmrt * Pa + + (-3.14279680e-05) * Ta * Ta * D_Tmrt * Pa + + (2.59835559e-06) * Ta * Ta * Ta * D_Tmrt * Pa + + (-4.77136523e-08) * Ta * Ta * Ta * Ta * D_Tmrt * Pa + + (8.64203390e-03) * va * D_Tmrt * Pa + + (-6.87405181e-04) * Ta * va * D_Tmrt * Pa + + (-9.13863872e-06) * Ta * Ta * va * D_Tmrt * Pa + + (5.15916806e-07) * Ta * Ta * Ta * va * D_Tmrt * Pa + + (-3.59217476e-05) * va * va * D_Tmrt * Pa + + (3.28696511e-05) * Ta * va * va * D_Tmrt * Pa + + (-7.10542454e-07) * Ta * Ta * va * va * D_Tmrt * Pa + + (-1.24382300e-05) * va * va * va * D_Tmrt * Pa + + (-7.38584400e-09) * Ta * va * va * va * D_Tmrt * Pa + + (2.20609296e-07) * va * va * va * va * D_Tmrt * Pa + + (-7.32469180e-04) * D_Tmrt * D_Tmrt * Pa + + (-1.87381964e-05) * Ta * D_Tmrt * D_Tmrt * Pa + + (4.80925239e-06) * Ta * Ta * D_Tmrt * D_Tmrt * Pa + + (-8.75492040e-08) * Ta * Ta * Ta * D_Tmrt * D_Tmrt * Pa + + (2.77862930e-05) * va * D_Tmrt * D_Tmrt * Pa + + (-5.06004592e-06) * Ta * va * D_Tmrt * D_Tmrt * Pa + + (1.14325367e-07) * Ta * Ta * va * D_Tmrt * D_Tmrt * Pa + + (2.53016723e-06) * va * va * D_Tmrt * D_Tmrt * Pa + + (-1.72857035e-08) * Ta * va * va * D_Tmrt * D_Tmrt * Pa + + (-3.95079398e-08) * va * va * va * D_Tmrt * D_Tmrt * Pa + + (-3.59413173e-07) * D_Tmrt * D_Tmrt * D_Tmrt * Pa + + (7.04388046e-07) * Ta * D_Tmrt * D_Tmrt * D_Tmrt * Pa + + (-1.89309167e-08) * Ta * Ta * D_Tmrt * D_Tmrt * D_Tmrt * Pa + + (-4.79768731e-07) * va * D_Tmrt * D_Tmrt * D_Tmrt * Pa + + (7.96079978e-09) * Ta * va * D_Tmrt * D_Tmrt * D_Tmrt * Pa + + (1.62897058e-09) * va * va * D_Tmrt * D_Tmrt * D_Tmrt * Pa + + (3.94367674e-08) * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * Pa + + (-1.18566247e-09) * Ta * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * Pa + + (3.34678041e-10) * va * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * Pa + + (-1.15606447e-10) * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * Pa + + (-2.80626406e00) * Pa * Pa + + (5.48712484e-01) * Ta * Pa * Pa + + (-3.99428410e-03) * Ta * Ta * Pa * Pa + + (-9.54009191e-04) * Ta * Ta * Ta * Pa * Pa + + (1.93090978e-05) * Ta * Ta * Ta * Ta * Pa * Pa + + (-3.08806365e-01) * va * Pa * Pa + + (1.16952364e-02) * Ta * va * Pa * Pa + + (4.95271903e-04) * Ta * Ta * va * Pa * Pa + + (-1.90710882e-05) * Ta * Ta * Ta * va * Pa * Pa + + (2.10787756e-03) * va * va * Pa * Pa + + (-6.98445738e-04) * Ta * va * va * Pa * Pa + + (2.30109073e-05) * Ta * Ta * va * va * Pa * Pa + + (4.17856590e-04) * va * va * va * Pa * Pa + + (-1.27043871e-05) * Ta * va * va * va * Pa * Pa + + (-3.04620472e-06) * va * va * va * va * Pa * Pa + + (5.14507424e-02) * D_Tmrt * Pa * Pa + + (-4.32510997e-03) * Ta * D_Tmrt * Pa * Pa + + (8.99281156e-05) * Ta * Ta * D_Tmrt * Pa * Pa + + (-7.14663943e-07) * Ta * Ta * Ta * D_Tmrt * Pa * Pa + + (-2.66016305e-04) * va * D_Tmrt * Pa * Pa + + (2.63789586e-04) * Ta * va * D_Tmrt * Pa * Pa + + (-7.01199003e-06) * Ta * Ta * va * D_Tmrt * Pa * Pa + + (-1.06823306e-04) * va * va * D_Tmrt * Pa * Pa + + (3.61341136e-06) * Ta * va * va * D_Tmrt * Pa * Pa + + (2.29748967e-07) * va * va * va * D_Tmrt * Pa * Pa + + (3.04788893e-04) * D_Tmrt * D_Tmrt * Pa * Pa + + (-6.42070836e-05) * Ta * D_Tmrt * D_Tmrt * Pa * Pa + + (1.16257971e-06) * Ta * Ta * D_Tmrt * D_Tmrt * Pa * Pa + + (7.68023384e-06) * va * D_Tmrt * D_Tmrt * Pa * Pa + + (-5.47446896e-07) * Ta * va * D_Tmrt * D_Tmrt * Pa * Pa + + (-3.59937910e-08) * va * va * D_Tmrt * D_Tmrt * Pa * Pa + + (-4.36497725e-06) * D_Tmrt * D_Tmrt * D_Tmrt * Pa * Pa + + (1.68737969e-07) * Ta * D_Tmrt * D_Tmrt * D_Tmrt * Pa * Pa + + (2.67489271e-08) * va * D_Tmrt * D_Tmrt * D_Tmrt * Pa * Pa + + (3.23926897e-09) * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * Pa * Pa + + (-3.53874123e-02) * Pa * Pa * Pa + + (-2.21201190e-01) * Ta * Pa * Pa * Pa + + (1.55126038e-02) * Ta * Ta * Pa * Pa * Pa + + (-2.63917279e-04) * Ta * Ta * Ta * Pa * Pa * Pa + + (4.53433455e-02) * va * Pa * Pa * Pa + + (-4.32943862e-03) * Ta * va * Pa * Pa * Pa + + (1.45389826e-04) * Ta * Ta * va * Pa * Pa * Pa + + (2.17508610e-04) * va * va * Pa * Pa * Pa + + (-6.66724702e-05) * Ta * va * va * Pa * Pa * Pa + + (3.33217140e-05) * va * va * va * Pa * Pa * Pa + + (-2.26921615e-03) * D_Tmrt * Pa * Pa * Pa + + (3.80261982e-04) * Ta * D_Tmrt * Pa * Pa * Pa + + (-5.45314314e-09) * Ta * Ta * D_Tmrt * Pa * Pa * Pa + + (-7.96355448e-04) * va * D_Tmrt * Pa * Pa * Pa + + (2.53458034e-05) * Ta * va * D_Tmrt * Pa * Pa * Pa + + (-6.31223658e-06) * va * va * D_Tmrt * Pa * Pa * Pa + + (3.02122035e-04) * D_Tmrt * D_Tmrt * Pa * Pa * Pa + + (-4.77403547e-06) * Ta * D_Tmrt * D_Tmrt * Pa * Pa * Pa + + (1.73825715e-06) * va * D_Tmrt * D_Tmrt * Pa * Pa * Pa + + (-4.09087898e-07) * D_Tmrt * D_Tmrt * D_Tmrt * Pa * Pa * Pa + + (6.14155345e-01) * Pa * Pa * Pa * Pa + + (-6.16755931e-02) * Ta * Pa * Pa * Pa * Pa + + (1.33374846e-03) * Ta * Ta * Pa * Pa * Pa * Pa + + (3.55375387e-03) * va * Pa * Pa * Pa * Pa + + (-5.13027851e-04) * Ta * va * Pa * Pa * Pa * Pa + + (1.02449757e-04) * va * va * Pa * Pa * Pa * Pa + + (-1.48526421e-03) * D_Tmrt * Pa * Pa * Pa * Pa + + (-4.11469183e-05) * Ta * D_Tmrt * Pa * Pa * Pa * Pa + + (-6.80434415e-06) * va * D_Tmrt * Pa * Pa * Pa * Pa + + (-9.77675906e-06) * D_Tmrt * D_Tmrt * Pa * Pa * Pa * Pa + + (8.82773108e-02) * Pa * Pa * Pa * Pa * Pa + + (-3.01859306e-03) * Ta * Pa * Pa * Pa * Pa * Pa + + (1.04452989e-03) * va * Pa * Pa * Pa * Pa * Pa + + (2.47090539e-04) * D_Tmrt * Pa * Pa * Pa * Pa * Pa + + (1.48348065e-03) * Pa * Pa * Pa * Pa * Pa * Pa + ) return UTCI_approx + def utci_calculator(Ta, RH, Tmrt, va10m): # Program for calculating UTCI Temperature (UTCI) # released for public use after termination of COST Action 730 @@ -229,17 +239,27 @@ def utci_calculator(Ta, RH, Tmrt, va10m): UTCI_approx = -999 else: # saturation vapour pressure (es) - g = np.array([-2.8365744E3, - 6.028076559E3, 1.954263612E1, - 2.737830188E-2, - 1.6261698E-5, 7.0229056E-10, - 1.8680009E-13, 2.7150305]) + g = np.array( + [ + -2.8365744e3, + -6.028076559e3, + 1.954263612e1, + -2.737830188e-2, + 1.6261698e-5, + 7.0229056e-10, + -1.8680009e-13, + 2.7150305, + ] + ) tk = Ta + 273.15 # ! air temp in K es = g[7] * np.log(tk) for i in range(0, 7): - es = es + g[i] * tk ** (i + 1 - 3.) + es = es + g[i] * tk ** (i + 1 - 3.0) es = np.exp(es) * 0.01 - ehPa = es * RH / 100. + ehPa = es * RH / 100.0 D_Tmrt = Tmrt - Ta Pa = ehPa / 10.0 # use vapour pressure in kPa @@ -250,6 +270,7 @@ def utci_calculator(Ta, RH, Tmrt, va10m): return UTCI_approx + def utci_calculator_grid(Ta, RH, Tmrt, va10m, feedback): # Program for calculating UTCI Temperature (UTCI) # released for public use after termination of COST Action 730 @@ -269,22 +290,32 @@ def utci_calculator_grid(Ta, RH, Tmrt, va10m, feedback): UTCI_approx = np.zeros((rows, cols)) # saturation vapour pressure (es) - g = np.array([-2.8365744E3, - 6.028076559E3, 1.954263612E1, - 2.737830188E-2, - 1.6261698E-5, 7.0229056E-10, - 1.8680009E-13, 2.7150305]) + g = np.array( + [ + -2.8365744e3, + -6.028076559e3, + 1.954263612e1, + -2.737830188e-2, + 1.6261698e-5, + 7.0229056e-10, + -1.8680009e-13, + 2.7150305, + ] + ) tk = Ta + 273.15 # ! air temp in K es = g[7] * np.log(tk) for i in range(0, 7): - es = es + g[i] * tk ** (i + 1 - 3.) + es = es + g[i] * tk ** (i + 1 - 3.0) es = np.exp(es) * 0.01 - ehPa = es * RH / 100. + ehPa = es * RH / 100.0 Pa = ehPa / 10.0 # use vapour pressure in kPa # For progress counter index = 0 - total = 100. / (rows * cols) + total = 100.0 / (rows * cols) # Start UTCI approximation for iy in np.arange(rows): @@ -295,16 +326,18 @@ def utci_calculator_grid(Ta, RH, Tmrt, va10m, feedback): D_Tmrt = Tmrt[iy, ix] - Ta # va = va10m[iy, ix] - + # Set UTCI to NaN (-999) if Tmrt or wind speed is NaN if Tmrt[iy, ix] <= -999 or va10m[iy, ix] <= -999: UTCI_approx[iy, ix] = -9999 # Calculate 6th order polynomial as approximation if wind speed is above zero elif va10m[iy, ix] > 0: - UTCI_approx[iy, ix] = utci_polynomial(D_Tmrt, Ta, va10m[iy, ix], Pa) + UTCI_approx[iy, ix] = utci_polynomial( + D_Tmrt, Ta, va10m[iy, ix], Pa + ) # Progress counter index = index + 1 feedback.setProgress(int(index * total)) - return UTCI_approx \ No newline at end of file + return UTCI_approx diff --git a/functions/SOLWEIGpython/WriteMetadataSOLWEIG.py b/functions/SOLWEIGpython/WriteMetadataSOLWEIG.py index 6b61be2..ee033b7 100644 --- a/functions/SOLWEIGpython/WriteMetadataSOLWEIG.py +++ b/functions/SOLWEIGpython/WriteMetadataSOLWEIG.py @@ -1,166 +1,263 @@ from builtins import str + # This file prints out run information used for each specific run from time import strftime from osgeo import osr, gdal import sys import qgis.core -def writeRunInfo(folderPath, filepath_dsm, gdal_dsm, usevegdem, filePath_cdsm, trunkfile, filePath_tdsm, lat, lon, UTC, - landcover, filePath_lc, metfileexist, filePath_metfile, metdata, plugin_dir, absK, absL, albedo_b, - albedo_g, ewall, eground, onlyglobal, trunkratio, trans, rows, cols, pos, elvis, cyl, demforbuild, ani, wallScheme, effusivity): + +def writeRunInfo( + folderPath, + filepath_dsm, + gdal_dsm, + usevegdem, + filePath_cdsm, + trunkfile, + filePath_tdsm, + lat, + lon, + UTC, + landcover, + filePath_lc, + metfileexist, + filePath_metfile, + metdata, + plugin_dir, + absK, + absL, + albedo_b, + albedo_g, + ewall, + eground, + onlyglobal, + trunkratio, + trans, + rows, + cols, + pos, + elvis, + cyl, + demforbuild, + ani, + wallScheme, + effusivity, +): # with open(folderPath + '/RunInfoSOLWEIG.txt', 'w') as file: #FO# - #FO# + # FO# if metdata[0, 2] < 10: - XH = '0' + XH = "0" else: - XH = '' + XH = "" if metdata[0, 3] < 10: - XM = '0' + XM = "0" else: - XM = '' - with open(folderPath + '/RunInfoSOLWEIG_' + str(int(metdata[0, 0])) + '_' + str(int(metdata[0, 1])) + '_' + - XH + str(int(metdata[0, 2])) + XM + str(int(metdata[0, 3])) + '.txt', 'w') as file: - #FO# - file.write('This file provides run settings for the SOLWEIG run initiated at: ' + strftime("%a, %d %b %Y %H:%M:%S")) - file.write('\n') - file.write('Version: ' + 'SOLWEIG v2022a') - file.write('\n') - file.write('Pyton version: ' + str(sys.version)) - file.write('\n') - file.write('GDAL version: ' + str(gdal.__version__)) - file.write('\n') - file.write('QGIS version: ' + str(qgis.core.Qgis.QGIS_VERSION)) - file.write('\n') - file.write('\n') - file.write('SURFACE DATA') - file.write('\n') - file.write('Digital surface model (DSM): ' + filepath_dsm) - file.write('\n') - file.write('Model domain: rows = ' + str(rows) + ', columns = ' + str(cols)) - file.write('\n') + XM = "" + with open( + folderPath + + "/RunInfoSOLWEIG_" + + str(int(metdata[0, 0])) + + "_" + + str(int(metdata[0, 1])) + + "_" + + XH + + str(int(metdata[0, 2])) + + XM + + str(int(metdata[0, 3])) + + ".txt", + "w", + ) as file: + # FO# + file.write( + "This file provides run settings for the SOLWEIG run initiated at: " + + strftime("%a, %d %b %Y %H:%M:%S") + ) + file.write("\n") + file.write("Version: " + "SOLWEIG v2022a") + file.write("\n") + file.write("Pyton version: " + str(sys.version)) + file.write("\n") + file.write("GDAL version: " + str(gdal.__version__)) + file.write("\n") + file.write("QGIS version: " + str(qgis.core.Qgis.QGIS_VERSION)) + file.write("\n") + file.write("\n") + file.write("SURFACE DATA") + file.write("\n") + file.write("Digital surface model (DSM): " + filepath_dsm) + file.write("\n") + file.write( + "Model domain: rows = " + str(rows) + ", columns = " + str(cols) + ) + file.write("\n") # get CRS prj = gdal_dsm.GetProjection() srs = osr.SpatialReference(wkt=prj) if srs.IsProjected: - file.write('Projected reference system: ' + srs.GetAttrValue('projcs')) - file.write('\n') - file.write('Geographical coordinate system: ' + srs.GetAttrValue('geogcs')) - file.write('\n') - file.write('Latitude: ' + str(lat)) - file.write('\n') - file.write('Longitude: ' + str(lon)) - file.write('\n') - file.write('UTC: ' + str(UTC)) - file.write('\n') + file.write( + "Projected reference system: " + srs.GetAttrValue("projcs") + ) + file.write("\n") + file.write( + "Geographical coordinate system: " + srs.GetAttrValue("geogcs") + ) + file.write("\n") + file.write("Latitude: " + str(lat)) + file.write("\n") + file.write("Longitude: " + str(lon)) + file.write("\n") + file.write("UTC: " + str(UTC)) + file.write("\n") if usevegdem == 1: - file.write('Transmissivity of light through vegetation: ' + str(trans)) - file.write('\n') - file.write('Digital vegetation canopy model (CDSM): ' + filePath_cdsm) - file.write('\n') + file.write( + "Transmissivity of light through vegetation: " + str(trans) + ) + file.write("\n") + file.write( + "Digital vegetation canopy model (CDSM): " + filePath_cdsm + ) + file.write("\n") if trunkfile == 1: - file.write('Digital vegetation trunk zone model (TDSM): ' + filePath_tdsm) #FO# zrunk -> trunk - file.write('\n') + file.write( + "Digital vegetation trunk zone model (TDSM): " + + filePath_tdsm + ) # FO# zrunk -> trunk + file.write("\n") else: - file.write('Trunkzone estimated from CDSM') - file.write('\n') - file.write('Trunkzone as percent of canopy height: ' + str(trunkratio)) - file.write('\n') + file.write("Trunkzone estimated from CDSM") + file.write("\n") + file.write( + "Trunkzone as percent of canopy height: " + str(trunkratio) + ) + file.write("\n") else: - file.write('Vegetation scheme inactive') - file.write('\n') + file.write("Vegetation scheme inactive") + file.write("\n") if landcover == 1: - file.write('Landcover scheme active. Parameters taken from: ' + plugin_dir + "/landcoverclasses_2016a.txt") - file.write('\n') - file.write('Landcover grid: ' + filePath_lc) - file.write('\n') + file.write( + "Landcover scheme active. Parameters taken from: " + + plugin_dir + + "/landcoverclasses_2016a.txt" + ) + file.write("\n") + file.write("Landcover grid: " + filePath_lc) + file.write("\n") else: - file.write('Landcover scheme inactive') - file.write('\n') - file.write('\n') + file.write("Landcover scheme inactive") + file.write("\n") + file.write("\n") if demforbuild == 1: - file.write('DEM used to identify buildings') - file.write('\n') + file.write("DEM used to identify buildings") + file.write("\n") else: - file.write('Land cover used to identify buildings') - file.write('\n') - file.write('\n') - file.write('METEOROLOGICAL FORCING DATA') - file.write('\n') + file.write("Land cover used to identify buildings") + file.write("\n") + file.write("\n") + file.write("METEOROLOGICAL FORCING DATA") + file.write("\n") if metfileexist == 1: - file.write('Meteorological file: ' + filePath_metfile) - file.write('\n') + file.write("Meteorological file: " + filePath_metfile) + file.write("\n") if onlyglobal == 1: - file.write('Diffuse and direct shortwave radiation estimated from global radiation') - file.write('\n') + file.write( + "Diffuse and direct shortwave radiation estimated from global radiation" + ) + file.write("\n") else: - file.write('Meteorological file not used') - file.write('\n') #FO# ' ' -> file.write('\n') - file.write('Year: ' + str(metdata[0, 0])) - file.write('\n') - file.write('Day of Year: ' + str(metdata[0, 1])) - file.write('\n') - file.write('Hour: ' + str(metdata[0, 2])) - file.write('\n') - file.write('Minute: ' + str(metdata[0, 3])) - file.write('\n') - file.write('Air temperature: ' + str(metdata[0, 11])) #FO# Ait -> Air - file.write('\n') - file.write('Relative humidity: ' + str(metdata[0, 10])) - file.write('\n') - file.write('Global radiation: ' + str(metdata[0, 14])) - file.write('\n') - file.write('Diffuse radiation: ' + str(metdata[0, 21])) - file.write('\n') - file.write('Direct radiation: ' + str(metdata[0, 22])) - file.write('\n') - file.write('\n') - file.write('HUMAN EXPOSURE PARAMETERS') - file.write('\n') - file.write('Absorption, shortwave radiation: ' + str(absK)) - file.write('\n') - file.write('Absorption, longwave radiation: ' + str(absL)) - file.write('\n') + file.write("Meteorological file not used") + file.write("\n") # FO# ' ' -> file.write('\n') + file.write("Year: " + str(metdata[0, 0])) + file.write("\n") + file.write("Day of Year: " + str(metdata[0, 1])) + file.write("\n") + file.write("Hour: " + str(metdata[0, 2])) + file.write("\n") + file.write("Minute: " + str(metdata[0, 3])) + file.write("\n") + file.write( + "Air temperature: " + str(metdata[0, 11]) + ) # FO# Ait -> Air + file.write("\n") + file.write("Relative humidity: " + str(metdata[0, 10])) + file.write("\n") + file.write("Global radiation: " + str(metdata[0, 14])) + file.write("\n") + file.write("Diffuse radiation: " + str(metdata[0, 21])) + file.write("\n") + file.write("Direct radiation: " + str(metdata[0, 22])) + file.write("\n") + file.write("\n") + file.write("HUMAN EXPOSURE PARAMETERS") + file.write("\n") + file.write("Absorption, shortwave radiation: " + str(absK)) + file.write("\n") + file.write("Absorption, longwave radiation: " + str(absL)) + file.write("\n") if pos == 0: - file.write('Posture of human body: Standing') + file.write("Posture of human body: Standing") else: - file.write('Posture of human body: Sitting') - file.write('\n') - file.write('ENVIRONMENTAL PARAMETERS') - file.write('\n') - file.write('Albedo of walls: ' + str(albedo_b)) - file.write('\n') - file.write('Albedo of ground (not used if land cover scheme is active): ' + str(albedo_g)) - file.write('\n') - file.write('Emissivity (walls): ' + str(ewall)) - file.write('\n') - file.write('Emissivity of ground (not used if land cover scheme is active): ' + str(eground)) - file.write('\n') - file.write('\n') - file.write('ADDITIONAL SETTINGS') - file.write('\n') + file.write("Posture of human body: Sitting") + file.write("\n") + file.write("ENVIRONMENTAL PARAMETERS") + file.write("\n") + file.write("Albedo of walls: " + str(albedo_b)) + file.write("\n") + file.write( + "Albedo of ground (not used if land cover scheme is active): " + + str(albedo_g) + ) + file.write("\n") + file.write("Emissivity (walls): " + str(ewall)) + file.write("\n") + file.write( + "Emissivity of ground (not used if land cover scheme is active): " + + str(eground) + ) + file.write("\n") + file.write("\n") + file.write("ADDITIONAL SETTINGS") + file.write("\n") if elvis == 1: - file.write('Sky emissivity adjusted according to Jonsson et al. (2005)') - file.write('\n') + file.write( + "Sky emissivity adjusted according to Jonsson et al. (2005)" + ) + file.write("\n") if cyl == 1: - file.write('Human considered as a standing cylinder') #FO# '' -> standing + file.write( + "Human considered as a standing cylinder" + ) # FO# '' -> standing else: - file.write('Human considered as a standing cube') - file.write('\n') + file.write("Human considered as a standing cube") + file.write("\n") if ani == 1: - file.write('Anisotropic sky diffuse shortwave (Perez et al. 1993) and longwave (Martin & Berdahl, 1984) radiation') + file.write( + "Anisotropic sky diffuse shortwave (Perez et al. 1993) and longwave (Martin & Berdahl, 1984) radiation" + ) else: - file.write('Isotropic sky') - file.write('\n') + file.write("Isotropic sky") + file.write("\n") if wallScheme: if effusivity.size == 1: - file.write('Wall surface parameterization scheme. Wall effusivity set to ' + str(effusivity) + ' J m-2 s-0.5 K-1.') + file.write( + "Wall surface parameterization scheme. Wall effusivity set to " + + str(effusivity) + + " J m-2 s-0.5 K-1." + ) else: for i in range(effusivity.size): if i == 0: - file.write('There are ' + str(effusivity.size) + ' wall types:') - file.write('\n') - file.write('Wall surface parameterization scheme. Wall effusivity set to ' + str(effusivity[i]) + ' J m-2 s-0.5 K-1.') - file.write('\n') - file.write('\n') + file.write( + "There are " + + str(effusivity.size) + + " wall types:" + ) + file.write("\n") + file.write( + "Wall surface parameterization scheme. Wall effusivity set to " + + str(effusivity[i]) + + " J m-2 s-0.5 K-1." + ) + file.write("\n") + file.write("\n") file.close() diff --git a/functions/SOLWEIGpython/anisotropic_sky.py b/functions/SOLWEIGpython/anisotropic_sky.py index e60dc33..bd8971b 100644 --- a/functions/SOLWEIGpython/anisotropic_sky.py +++ b/functions/SOLWEIGpython/anisotropic_sky.py @@ -8,11 +8,40 @@ from . import patch_radiation import pandas as pd -def anisotropic_sky(shmat, vegshmat, vbshvegshmat, solar_altitude, solar_azimuth, asvf, cyl, - esky, L_patches, wallScheme, voxelTable, voxelMaps, steradians, Ta, Tgwall, ewall, Lup, - radI, radD, radG, lv, albedo, anisotropic_diffuse, diffsh, shadow, KupE, KupS, KupW, KupN, current_step): - - '''This function estimates short- and longwave radiation from an anisotropic sky....''' + +def anisotropic_sky( + shmat, + vegshmat, + vbshvegshmat, + solar_altitude, + solar_azimuth, + asvf, + cyl, + esky, + L_patches, + wallScheme, + voxelTable, + voxelMaps, + steradians, + Ta, + Tgwall, + ewall, + Lup, + radI, + radD, + radG, + lv, + albedo, + anisotropic_diffuse, + diffsh, + shadow, + KupE, + KupS, + KupW, + KupN, + current_step, +): + """This function estimates short- and longwave radiation from an anisotropic sky....""" # Stefan-Boltzmann's Constant SBC = 5.67051e-8 @@ -67,13 +96,19 @@ def anisotropic_sky(shmat, vegshmat, vbshvegshmat, solar_altitude, solar_azimuth # Unsworth & Monteith (1975) if emis_m == 1: - patch_emissivity_normalized, esky_band = emissivity_models.model1(L_patches, esky, Ta) + patch_emissivity_normalized, esky_band = emissivity_models.model1( + L_patches, esky, Ta + ) # Martin & Berdahl (1984) elif emis_m == 2: - patch_emissivity_normalized, esky_band = emissivity_models.model2(L_patches, esky, Ta) + patch_emissivity_normalized, esky_band = emissivity_models.model2( + L_patches, esky, Ta + ) # Bliss (1961) elif emis_m == 3: - patch_emissivity_normalized, esky_band = emissivity_models.model3(L_patches, esky, Ta) + patch_emissivity_normalized, esky_band = emissivity_models.model3( + L_patches, esky, Ta + ) # True = anisotropic sky, False = isotropic sky anisotropic_sky_ = True @@ -90,17 +125,27 @@ def anisotropic_sky(shmat, vegshmat, vbshvegshmat, solar_altitude, solar_azimuth else: temp_emissivity = esky # Estimate longwave radiation on a horizontal surface (Ldown), vertical surface (Lside) and perpendicular (Lnormal) - Ldown[patch_altitude == temp_altitude] = ((temp_emissivity * SBC * ((Ta + 273.15) ** 4)) / np.pi) * steradians[patch_altitude == temp_altitude] * np.sin(temp_altitude * deg2rad) - Lside[patch_altitude == temp_altitude] = ((temp_emissivity * SBC * ((Ta + 273.15) ** 4)) / np.pi) * steradians[patch_altitude == temp_altitude] * np.cos(temp_altitude * deg2rad) - Lnormal[patch_altitude == temp_altitude] = ((temp_emissivity * SBC * ((Ta + 273.15) ** 4)) / np.pi) * steradians[patch_altitude == temp_altitude] + Ldown[patch_altitude == temp_altitude] = ( + ((temp_emissivity * SBC * ((Ta + 273.15) ** 4)) / np.pi) + * steradians[patch_altitude == temp_altitude] + * np.sin(temp_altitude * deg2rad) + ) + Lside[patch_altitude == temp_altitude] = ( + ((temp_emissivity * SBC * ((Ta + 273.15) ** 4)) / np.pi) + * steradians[patch_altitude == temp_altitude] + * np.cos(temp_altitude * deg2rad) + ) + Lnormal[patch_altitude == temp_altitude] = ( + (temp_emissivity * SBC * ((Ta + 273.15) ** 4)) / np.pi + ) * steradians[patch_altitude == temp_altitude] Lsky_normal = deepcopy(L_patches) Lsky_down = deepcopy(L_patches) Lsky_side = deepcopy(L_patches) - Lsky_normal[:,2] = Lnormal - Lsky_down[:,2] = Ldown - Lsky_side[:,2] = Lside + Lsky_normal[:, 2] = Lnormal + Lsky_down[:, 2] = Ldown + Lsky_side[:, 2] = Lside # Shortwave radiation if solar_altitude > 0: @@ -110,19 +155,23 @@ def anisotropic_sky(shmat, vegshmat, vbshvegshmat, solar_altitude, solar_azimuth radTot = np.zeros(1) # Radiance fraction normalization for i in np.arange(patch_altitude.shape[0]): - radTot += (patch_luminance[i] * steradians[i] * np.sin(patch_altitude[i] * deg2rad)) # Radiance fraction normalization + radTot += ( + patch_luminance[i] + * steradians[i] + * np.sin(patch_altitude[i] * deg2rad) + ) # Radiance fraction normalization lumChi = (patch_luminance * radD) / radTot for i in np.arange(patch_altitude.shape[0]): # Calculations for patches on sky, shmat = 1 = sky is visible - temp_sky = ((shmat[:, :, i] == 1) & (vegshmat[:, :, i] == 1)) - + temp_sky = (shmat[:, :, i] == 1) & (vegshmat[:, :, i] == 1) + # Calculations for patches that are vegetation, vegshmat = 0 = shade from vegetation - temp_vegsh = ((vegshmat[:, :, i] == 0) | (vbshvegshmat[:, :, i] == 0)) + temp_vegsh = (vegshmat[:, :, i] == 0) | (vbshvegshmat[:, :, i] == 0) # Calculations for patches that are buildings, shmat = 0 = shade from buildings temp_vbsh = (1 - shmat[:, :, i]) * vbshvegshmat[:, :, i] - temp_sh = (temp_vbsh == 1) + temp_sh = temp_vbsh == 1 if wallScheme == 1: # temp_vbsh = (voxelMaps[:, :, i] > 0) * vbshvegshmat[:, :, i] # temp_sh_w = (temp_vbsh == 1) * voxelMaps[:, :, i] @@ -130,18 +179,37 @@ def anisotropic_sky(shmat, vegshmat, vbshvegshmat, solar_altitude, solar_azimuth temp_sh_roof = temp_sh * (voxelMaps[:, :, i] == 0) # Estimate sunlit and shaded patches - sunlit_patches, shaded_patches = sunlit_shaded_patches.shaded_or_sunlit(solar_altitude, solar_azimuth, patch_altitude[i], patch_azimuth[i], asvf) + sunlit_patches, shaded_patches = ( + sunlit_shaded_patches.shaded_or_sunlit( + solar_altitude, + solar_azimuth, + patch_altitude[i], + patch_azimuth[i], + asvf, + ) + ) if cyl == 1: # Angle of incidence, np.cos(0) because cylinder - always perpendicular - angle_of_incidence = np.cos(patch_altitude[i] * deg2rad) * np.cos(0) # * np.sin(np.pi / 2) \ + angle_of_incidence = np.cos(patch_altitude[i] * deg2rad) * np.cos( + 0 + ) # * np.sin(np.pi / 2) \ # Angle of incidence to horizontal surface angle_of_incidence_h = np.sin(patch_altitude[i] * deg2rad) ### CALCULATIONS FOR LONGWAVE RADIATION ### # Longwave radiation from sky - Lside_sky_temp, Ldown_sky_temp, Least_temp, Lsouth_temp, Lwest_temp, Lnorth_temp = patch_radiation.longwave_from_sky(temp_sky, Lsky_side[i, 2], Lsky_down[i, 2], patch_azimuth[i]) - + ( + Lside_sky_temp, + Ldown_sky_temp, + Least_temp, + Lsouth_temp, + Lwest_temp, + Lnorth_temp, + ) = patch_radiation.longwave_from_sky( + temp_sky, Lsky_side[i, 2], Lsky_down[i, 2], patch_azimuth[i] + ) + Lside_sky += Lside_sky_temp Ldown_sky += Ldown_sky_temp Least += Least_temp @@ -150,41 +218,104 @@ def anisotropic_sky(shmat, vegshmat, vbshvegshmat, solar_altitude, solar_azimuth Lnorth += Lnorth_temp # Longwave radiation from vegetation - Lside_veg_temp, Ldown_veg_temp, \ - Least_temp, Lsouth_temp, Lwest_temp, Lnorth_temp = patch_radiation.longwave_from_veg(temp_vegsh, steradians[i], angle_of_incidence, angle_of_incidence_h, - patch_altitude[i], patch_azimuth[i], ewall, Ta) - + ( + Lside_veg_temp, + Ldown_veg_temp, + Least_temp, + Lsouth_temp, + Lwest_temp, + Lnorth_temp, + ) = patch_radiation.longwave_from_veg( + temp_vegsh, + steradians[i], + angle_of_incidence, + angle_of_incidence_h, + patch_altitude[i], + patch_azimuth[i], + ewall, + Ta, + ) + Lside_veg += Lside_veg_temp Ldown_veg += Ldown_veg_temp Least += Least_temp Lsouth += Lsouth_temp Lwest += Lwest_temp - Lnorth += Lnorth_temp + Lnorth += Lnorth_temp # Longwave radiation from buildings if wallScheme == 0: azimuth_difference = np.abs(solar_azimuth - patch_azimuth[i]) - Lside_sun_temp, Lside_sh_temp, \ - Ldown_sun_temp, Ldown_sh_temp, \ - Least_temp, Lsouth_temp, Lwest_temp, Lnorth_temp = patch_radiation.longwave_from_buildings(temp_sh, steradians[i], angle_of_incidence, angle_of_incidence_h, - patch_azimuth[i], sunlit_patches, shaded_patches, - azimuth_difference, solar_altitude, ewall, Ta, Tgwall) + ( + Lside_sun_temp, + Lside_sh_temp, + Ldown_sun_temp, + Ldown_sh_temp, + Least_temp, + Lsouth_temp, + Lwest_temp, + Lnorth_temp, + ) = patch_radiation.longwave_from_buildings( + temp_sh, + steradians[i], + angle_of_incidence, + angle_of_incidence_h, + patch_azimuth[i], + sunlit_patches, + shaded_patches, + azimuth_difference, + solar_altitude, + ewall, + Ta, + Tgwall, + ) else: azimuth_difference = np.abs(solar_azimuth - patch_azimuth[i]) # print('Building pixels = ' + str(temp_sh.sum())) # print('Building pixels wall scheme = ' + str(np.sum(temp_sh_w > 0))) - Lside_sun_temp, Lside_sh_temp, \ - Ldown_sun_temp, Ldown_sh_temp, \ - Least_temp, Lsouth_temp, Lwest_temp, Lnorth_temp = patch_radiation.longwave_from_buildings_wallScheme(temp_sh_w, voxelTable, steradians[i], angle_of_incidence, angle_of_incidence_h, - patch_azimuth[i]) - - Lside_sun_r_temp, Lside_sh_r_temp, \ - Ldown_sun_r_temp, Ldown_sh_r_temp, \ - Least_r_temp, Lsouth_r_temp, Lwest_r_temp, Lnorth_r_temp = patch_radiation.longwave_from_buildings(temp_sh_roof, steradians[i], angle_of_incidence, angle_of_incidence_h, - patch_azimuth[i], sunlit_patches, shaded_patches, - azimuth_difference, solar_altitude, ewall, Ta, Tgwall) + ( + Lside_sun_temp, + Lside_sh_temp, + Ldown_sun_temp, + Ldown_sh_temp, + Least_temp, + Lsouth_temp, + Lwest_temp, + Lnorth_temp, + ) = patch_radiation.longwave_from_buildings_wallScheme( + temp_sh_w, + voxelTable, + steradians[i], + angle_of_incidence, + angle_of_incidence_h, + patch_azimuth[i], + ) + + ( + Lside_sun_r_temp, + Lside_sh_r_temp, + Ldown_sun_r_temp, + Ldown_sh_r_temp, + Least_r_temp, + Lsouth_r_temp, + Lwest_r_temp, + Lnorth_r_temp, + ) = patch_radiation.longwave_from_buildings( + temp_sh_roof, + steradians[i], + angle_of_incidence, + angle_of_incidence_h, + patch_azimuth[i], + sunlit_patches, + shaded_patches, + azimuth_difference, + solar_altitude, + ewall, + Ta, + Tgwall, + ) Lside_sun_temp += Lside_sun_r_temp Lside_sh_temp += Lside_sh_r_temp @@ -202,39 +333,82 @@ def anisotropic_sky(shmat, vegshmat, vbshvegshmat, solar_altitude, solar_azimuth Least += Least_temp Lsouth += Lsouth_temp Lwest += Lwest_temp - Lnorth += Lnorth_temp + Lnorth += Lnorth_temp ### CALCULATIONS FOR SHORTWAVE RADIATION ### if solar_altitude > 0: # Shortwave radiation from sky - KsideD += temp_sky * lumChi[i] * angle_of_incidence * steradians[i] - + KsideD += ( + temp_sky * lumChi[i] * angle_of_incidence * steradians[i] + ) + # Shortwave reflected on sunlit surfaces # sunlit_surface = ((albedo * radG) / np.pi) - sunlit_surface = ((albedo * (radI * np.cos(solar_altitude * deg2rad)) + (radD * 0.5)) / np.pi) + sunlit_surface = ( + albedo * (radI * np.cos(solar_altitude * deg2rad)) + + (radD * 0.5) + ) / np.pi # Shortwave reflected on shaded surfaces and vegetation - shaded_surface = ((albedo * radD * 0.5) / np.pi) - + shaded_surface = (albedo * radD * 0.5) / np.pi + # Shortwave radiation from vegetation - Kref_veg += shaded_surface * temp_vegsh * steradians[i] * angle_of_incidence - + Kref_veg += ( + shaded_surface + * temp_vegsh + * steradians[i] + * angle_of_incidence + ) + # Shortwave radiation from buildings - Kref_sun += sunlit_surface * sunlit_patches * temp_sh * steradians[i] * angle_of_incidence - Kref_sh += shaded_surface * shaded_patches * temp_sh * steradians[i] * angle_of_incidence + Kref_sun += ( + sunlit_surface + * sunlit_patches + * temp_sh + * steradians[i] + * angle_of_incidence + ) + Kref_sh += ( + shaded_surface + * shaded_patches + * temp_sh + * steradians[i] + * angle_of_incidence + ) # Calculate reflected longwave in each patch for idx in np.arange(patch_altitude.shape[0]): # Angle of incidence, np.cos(0) because cylinder - always perpendicular - angle_of_incidence = np.cos(patch_altitude[idx] * deg2rad) * np.cos(0) # * np.sin(np.pi / 2) \ + angle_of_incidence = np.cos(patch_altitude[idx] * deg2rad) * np.cos( + 0 + ) # * np.sin(np.pi / 2) \ # Angle of incidence to horizontal surface angle_of_incidence_h = np.sin(patch_altitude[idx] * deg2rad) # Patches with reflected surfaces - temp_sh = ((shmat[:,:,idx] == 0) | (vegshmat[:,:,idx] == 0) | (vbshvegshmat[:,:,idx] == 0)) - - Lside_ref_temp, Ldown_ref_temp, \ - Least_temp, Lsouth_temp, Lwest_temp, Lnorth_temp = patch_radiation.reflected_longwave(temp_sh, steradians[idx], angle_of_incidence, angle_of_incidence_h, patch_azimuth[idx], Ldown_sky, Lup, ewall) + temp_sh = ( + (shmat[:, :, idx] == 0) + | (vegshmat[:, :, idx] == 0) + | (vbshvegshmat[:, :, idx] == 0) + ) + + ( + Lside_ref_temp, + Ldown_ref_temp, + Least_temp, + Lsouth_temp, + Lwest_temp, + Lnorth_temp, + ) = patch_radiation.reflected_longwave( + temp_sh, + steradians[idx], + angle_of_incidence, + angle_of_incidence_h, + patch_azimuth[idx], + Ldown_sky, + Lup, + ewall, + ) Lside_ref += Lside_ref_temp Ldown_ref += Ldown_ref_temp @@ -250,19 +424,37 @@ def anisotropic_sky(shmat, vegshmat, vbshvegshmat, solar_altitude, solar_azimuth Ldown = Ldown_sky + Ldown_veg + Ldown_sh + Ldown_sun + Ldown_ref ### Direct radiation ### - if cyl == 1: ### Kside with cylinder ### + if cyl == 1: ### Kside with cylinder ### KsideI = shadow * radI * np.cos(solar_altitude * deg2rad) if solar_altitude > 0: Kside = KsideI + KsideD + Kref_sun + Kref_sh + Kref_veg - Keast = (KupE * 0.5) - Kwest = (KupW * 0.5) - Knorth = (KupN * 0.5) - Ksouth = (KupS * 0.5) - - return Ldown, Lside, Lside_sky, Lside_veg, Lside_sh, Lside_sun, Lside_ref, Least, Lwest, Lnorth, Lsouth, \ - Keast, Ksouth, Kwest, Knorth, KsideI, KsideD, Kside, steradians, skyalt - - \ No newline at end of file + Keast = KupE * 0.5 + Kwest = KupW * 0.5 + Knorth = KupN * 0.5 + Ksouth = KupS * 0.5 + + return ( + Ldown, + Lside, + Lside_sky, + Lside_veg, + Lside_sh, + Lside_sun, + Lside_ref, + Least, + Lwest, + Lnorth, + Lsouth, + Keast, + Ksouth, + Kwest, + Knorth, + KsideI, + KsideD, + Kside, + steradians, + skyalt, + ) diff --git a/functions/SOLWEIGpython/cylindric_wedge.py b/functions/SOLWEIGpython/cylindric_wedge.py index d11ae97..7564af8 100644 --- a/functions/SOLWEIGpython/cylindric_wedge.py +++ b/functions/SOLWEIGpython/cylindric_wedge.py @@ -1,95 +1,101 @@ import numpy as np + def cylindric_wedge(zen, svfalfa, rows, cols): - np.seterr(divide='ignore', invalid='ignore') + np.seterr(divide="ignore", invalid="ignore") # Fraction of sunlit walls based on sun altitude and svf wieghted building angles - # input: + # input: # sun zenith angle "beta" # svf related angle "alfa" - beta=zen + beta = zen # alfa=svfalfa - alfa=np.zeros((rows, cols)) + svfalfa + alfa = np.zeros((rows, cols)) + svfalfa # measure the size of the image # sizex=size(svfalfa,2) # sizey=size(svfalfa,1) - - xa=1-2./(np.tan(alfa)*np.tan(beta)) - ha=2./(np.tan(alfa)*np.tan(beta)) - ba=(1./np.tan(alfa)) - hkil=2.*ba*ha - - qa=np.zeros((rows, cols)) + + xa = 1 - 2.0 / (np.tan(alfa) * np.tan(beta)) + ha = 2.0 / (np.tan(alfa) * np.tan(beta)) + ba = 1.0 / np.tan(alfa) + hkil = 2.0 * ba * ha + + qa = np.zeros((rows, cols)) # qa(length(svfalfa),length(svfalfa))=0; - qa[xa<0]=np.tan(beta)/2 - - Za=np.zeros((rows, cols)) + qa[xa < 0] = np.tan(beta) / 2 + + Za = np.zeros((rows, cols)) # Za(length(svfalfa),length(svfalfa))=0; - Za[xa<0]=((ba[xa<0]**2)-((qa[xa<0]**2)/4))**0.5 - - phi=np.zeros((rows, cols)) - #phi(length(svfalfa),length(svfalfa))=0; - phi[xa<0]=np.arctan(Za[xa<0]/qa[xa<0]) - - A=np.zeros((rows, cols)) + Za[xa < 0] = ((ba[xa < 0] ** 2) - ((qa[xa < 0] ** 2) / 4)) ** 0.5 + + phi = np.zeros((rows, cols)) + # phi(length(svfalfa),length(svfalfa))=0; + phi[xa < 0] = np.arctan(Za[xa < 0] / qa[xa < 0]) + + A = np.zeros((rows, cols)) # A(length(svfalfa),length(svfalfa))=0; - A[xa<0]=(np.sin(phi[xa<0])-phi[xa<0]*np.cos(phi[xa<0]))/(1-np.cos(phi[xa<0])) - - ukil=np.zeros((rows, cols)) + A[xa < 0] = (np.sin(phi[xa < 0]) - phi[xa < 0] * np.cos(phi[xa < 0])) / ( + 1 - np.cos(phi[xa < 0]) + ) + + ukil = np.zeros((rows, cols)) # ukil(length(svfalfa),length(svfalfa))=0 - ukil[xa<0]=2*ba[xa<0]*xa[xa<0]*A[xa<0] - - Ssurf=hkil+ukil - - F_sh=(2*np.pi*ba-Ssurf)/(2*np.pi*ba)#Xa - + ukil[xa < 0] = 2 * ba[xa < 0] * xa[xa < 0] * A[xa < 0] + + Ssurf = hkil + ukil + + F_sh = (2 * np.pi * ba - Ssurf) / (2 * np.pi * ba) # Xa + return F_sh + def cylindric_wedge_voxel(zen, svfalfa): - np.seterr(divide='ignore', invalid='ignore') + np.seterr(divide="ignore", invalid="ignore") # Fraction of sunlit walls based on sun altitude and svf wieghted building angles - # input: + # input: # sun zenith angle "beta" # svf related angle "alfa" - beta=zen + beta = zen # alfa=svfalfa - #alfa = voxelTable['svfalfa'].to_numpy() + # alfa = voxelTable['svfalfa'].to_numpy() # measure the size of the image # sizex=size(svfalfa,2) # sizey=size(svfalfa,1) - - xa=1-2./(np.tan(svfalfa)*np.tan(beta)) - ha=2./(np.tan(svfalfa)*np.tan(beta)) - ba=(1./np.tan(svfalfa)) - hkil=2.*ba*ha - - qa=np.zeros((svfalfa.shape[0])) + + xa = 1 - 2.0 / (np.tan(svfalfa) * np.tan(beta)) + ha = 2.0 / (np.tan(svfalfa) * np.tan(beta)) + ba = 1.0 / np.tan(svfalfa) + hkil = 2.0 * ba * ha + + qa = np.zeros((svfalfa.shape[0])) # qa(length(svfalfa),length(svfalfa))=0; - qa[xa<0]=np.tan(beta)/2 - - Za=np.zeros((svfalfa.shape[0])) + qa[xa < 0] = np.tan(beta) / 2 + + Za = np.zeros((svfalfa.shape[0])) # Za(length(svfalfa),length(svfalfa))=0; - Za[xa<0]=((ba[xa<0]**2)-((qa[xa<0]**2)/4))**0.5 - - phi=np.zeros((svfalfa.shape[0])) - #phi(length(svfalfa),length(svfalfa))=0; - phi[xa<0]=np.arctan(Za[xa<0]/qa[xa<0]) - - A=np.zeros((svfalfa.shape[0])) + Za[xa < 0] = ((ba[xa < 0] ** 2) - ((qa[xa < 0] ** 2) / 4)) ** 0.5 + + phi = np.zeros((svfalfa.shape[0])) + # phi(length(svfalfa),length(svfalfa))=0; + phi[xa < 0] = np.arctan(Za[xa < 0] / qa[xa < 0]) + + A = np.zeros((svfalfa.shape[0])) # A(length(svfalfa),length(svfalfa))=0; - A[xa<0]=(np.sin(phi[xa<0])-phi[xa<0]*np.cos(phi[xa<0]))/(1-np.cos(phi[xa<0])) - - ukil=np.zeros((svfalfa.shape[0])) + A[xa < 0] = (np.sin(phi[xa < 0]) - phi[xa < 0] * np.cos(phi[xa < 0])) / ( + 1 - np.cos(phi[xa < 0]) + ) + + ukil = np.zeros((svfalfa.shape[0])) # ukil(length(svfalfa),length(svfalfa))=0 - ukil[xa<0]=2*ba[xa<0]*xa[xa<0]*A[xa<0] - - Ssurf=hkil+ukil - - F_sh=(2*np.pi*ba-Ssurf)/(2*np.pi*ba)#Xa - - return F_sh \ No newline at end of file + ukil[xa < 0] = 2 * ba[xa < 0] * xa[xa < 0] * A[xa < 0] + + Ssurf = hkil + ukil + + F_sh = (2 * np.pi * ba - Ssurf) / (2 * np.pi * ba) # Xa + + return F_sh diff --git a/functions/SOLWEIGpython/daylen.py b/functions/SOLWEIGpython/daylen.py index a66cf5d..442185e 100644 --- a/functions/SOLWEIGpython/daylen.py +++ b/functions/SOLWEIGpython/daylen.py @@ -1,22 +1,22 @@ - import numpy as np + def daylen(DOY, XLAT): # Calculation of declination of sun (Eqn. 16). Amplitude= +/-23.45 # deg. Minimum = DOY 355 (DEC 21), maximum = DOY 172.5 (JUN 21/22). # Sun angles. SOC limited for latitudes above polar circles. # Calculate daylength, sunrise and sunset (Eqn. 17) - RAD=np.pi/180.0 + RAD = np.pi / 180.0 - DEC = -23.45 * np.cos(2.0*np.pi*(DOY+10.0)/365.0) + DEC = -23.45 * np.cos(2.0 * np.pi * (DOY + 10.0) / 365.0) - SOC = np.tan(RAD*DEC) * np.tan(RAD*XLAT) - SOC = min(max(SOC,-1.0),1.0) + SOC = np.tan(RAD * DEC) * np.tan(RAD * XLAT) + SOC = min(max(SOC, -1.0), 1.0) # SOC=alt - DAYL = 12.0 + 24.0*np.arcsin(SOC)/np.pi - SNUP = 12.0 - DAYL/2.0 - SNDN = 12.0 + DAYL/2.0 + DAYL = 12.0 + 24.0 * np.arcsin(SOC) / np.pi + SNUP = 12.0 - DAYL / 2.0 + SNDN = 12.0 + DAYL / 2.0 return DAYL, DEC, SNDN, SNUP diff --git a/functions/SOLWEIGpython/emissivity_models.py b/functions/SOLWEIGpython/emissivity_models.py index aff2b0a..1762e50 100644 --- a/functions/SOLWEIGpython/emissivity_models.py +++ b/functions/SOLWEIGpython/emissivity_models.py @@ -1,48 +1,50 @@ import numpy as np -''' Model 1 is based on Unsworth & Monteith, 1975 ''' +""" Model 1 is based on Unsworth & Monteith, 1975 """ + + def model1(sky_patches, esky, Ta): # Stefan-Boltzmann's Constant SBC = 5.67051e-8 # Degrees to radians - deg2rad = np.pi / 180 + deg2rad = np.pi / 180 # Unique altitudes in lv, i.e. unique altitude for the patches skyalt, skyalt_c = np.unique(sky_patches[:, 0], return_counts=True) - + # Unique zeniths for the patches - skyzen = 90-skyalt - + skyzen = 90 - skyalt + # Cosine of the zenith angles cosskyzen = np.cos(skyzen * deg2rad) # Estimate emissivities at different altitudes/zenith angles - + # Constants? a_c = 0.67 b_c = 0.094 # Natural log of the reduced depth of precipitable water - ln_u_prec = esky/b_c-a_c/b_c-0.5 - - # Reduced depth of precipitable water - u_prec = np.exp(ln_u_prec) + ln_u_prec = esky / b_c - a_c / b_c - 0.5 + + # Reduced depth of precipitable water + u_prec = np.exp(ln_u_prec) # Optical water depth - owp = u_prec/cosskyzen + owp = u_prec / cosskyzen # Natural log of optical water depth - log_owp = np.log(owp) + log_owp = np.log(owp) # Emissivity of each zenith angle, i.e. the zenith angle of each band of patches - esky_band = a_c+b_c*log_owp - + esky_band = a_c + b_c * log_owp + # Altitudes of the Robinson & Stone patches - p_alt = sky_patches[:,0] - - # Empty array with length based on number of patches + p_alt = sky_patches[:, 0] + + # Empty array with length based on number of patches patch_emissivity = np.zeros((p_alt.shape[0])) # Fill with emissivities @@ -51,34 +53,39 @@ def model1(sky_patches, esky, Ta): patch_emissivity[p_alt == idx] = temp_emissivity # Normalize - patch_emissivity_normalized = patch_emissivity/np.sum(patch_emissivity) + patch_emissivity_normalized = patch_emissivity / np.sum(patch_emissivity) return patch_emissivity_normalized, esky_band -''' Model 2 is based on Martin & Berdhal, 1984 - Ez = 1 - (1 - Es)*e**(b2*(1.7 - (1/np.cos(z)))) ''' + +""" Model 2 is based on Martin & Berdhal, 1984 + Ez = 1 - (1 - Es)*e**(b2*(1.7 - (1/np.cos(z)))) """ + + def model2(sky_patches, esky, Ta): # Degrees to radians - deg2rad = np.pi / 180 + deg2rad = np.pi / 180 # Unique altitudes in lv, i.e. unique altitude for the patches skyalt, skyalt_c = np.unique(sky_patches[:, 0], return_counts=True) - + # Unique zeniths for the patches - skyzen = 90-skyalt - + skyzen = 90 - skyalt + # Constant (Ångström, 1915), proposed by Nahon et al., 2019 b_c = 0.308 # b_c = 0.1 # Estimate emissivites at different altitudes/zenith angles - esky_band = 1 - (1 - esky) * np.exp(b_c * (1.7 - (1 / np.cos(skyzen * deg2rad)))) - + esky_band = 1 - (1 - esky) * np.exp( + b_c * (1.7 - (1 / np.cos(skyzen * deg2rad))) + ) + # Altitudes of the Robinson & Stone patches - p_alt = sky_patches[:,0] - - # Empty array with length based on number of patches + p_alt = sky_patches[:, 0] + + # Empty array with length based on number of patches patch_emissivity = np.zeros((p_alt.shape[0])) # Fill with emissivities @@ -87,35 +94,38 @@ def model2(sky_patches, esky, Ta): patch_emissivity[p_alt == idx] = temp_emissivity # Normalize - patch_emissivity_normalized = patch_emissivity/np.sum(patch_emissivity) + patch_emissivity_normalized = patch_emissivity / np.sum(patch_emissivity) return patch_emissivity_normalized, esky_band -''' Model 3 is based on Bliss, 1961. - Ez = 1 - (1 - Es)**(1/(b1*np.cos(z))) ''' + +""" Model 3 is based on Bliss, 1961. + Ez = 1 - (1 - Es)**(1/(b1*np.cos(z))) """ + + def model3(sky_patches, esky, Ta): # Degrees to radians - deg2rad = np.pi / 180 + deg2rad = np.pi / 180 # Unique altitudes in lv, i.e. unique altitude for the patches skyalt, skyalt_c = np.unique(sky_patches[:, 0], return_counts=True) - + # Unique zeniths for the patches - skyzen = 90-skyalt - + skyzen = 90 - skyalt + # Constant, can (should?) be changed. Model gave unsatisfactory results in Nahon et al., 2019 b_c = 1.8 # Estimate emissivites at different altitudes/zenith angles - esky_band = 1 - (1 - esky)**(1/(b_c * np.cos(skyzen * deg2rad))) + esky_band = 1 - (1 - esky) ** (1 / (b_c * np.cos(skyzen * deg2rad))) # Estimating longwave radiation for each patch to a horizontal or vertical surface - + # Altitudes of the Robinson & Stone patches - p_alt = sky_patches[:,0] - - # Empty array with length based on number of patches + p_alt = sky_patches[:, 0] + + # Empty array with length based on number of patches patch_emissivity = np.zeros((p_alt.shape[0])) # Fill with emissivities @@ -124,6 +134,6 @@ def model3(sky_patches, esky, Ta): patch_emissivity[p_alt == idx] = temp_emissivity # Normalize - patch_emissivity_normalized = patch_emissivity/np.sum(patch_emissivity) + patch_emissivity_normalized = patch_emissivity / np.sum(patch_emissivity) - return patch_emissivity_normalized, esky_band \ No newline at end of file + return patch_emissivity_normalized, esky_band diff --git a/functions/SOLWEIGpython/ground_surface.py b/functions/SOLWEIGpython/ground_surface.py new file mode 100644 index 0000000..4174f0f --- /dev/null +++ b/functions/SOLWEIGpython/ground_surface.py @@ -0,0 +1,691 @@ +""" +@author: Eliott Bridoux, University of Gothenburg +""" + +import numpy as np +import math +import matplotlib.pyplot as plt + +# Stefan-Boltzmann s constant +SBC = 5.67e-8 + + +def saturated_vp(T): + """ + Used in the calculation of the surface temperature for the water bodies. + + :Parameters: + :T: the temperature just above the water + + :Return: + :qs: the saturated vapor pressure + :slope: the slope of the tangent to the saturated vapor pressure-temperature curve + """ + + L = 2.260e6 # Latent heat of vaporisation + R = 8.314 # Gas constant + + # August-Roche-Magnus approx. in Pa + qs = 6109.4 * np.exp(17.625 * T / (T + 243.04)) + + # Clausius-Clapeyron equation + slope = L * qs / R / (T + 273.15) ** 2 + + return slope, qs + + +def initiate_groundScheme(lc_grid, solweig_parameters, day, Ta, location): + """ + Setup the maps used in the ground scheme calculations depending on the landcover + + :Parameters: + :lc_grid: Array of landcover values + :solweig_param: Dict of physical parameters + :day: day of the year (int) + :Ta: air temperature (float) + :location: Dict containing latitude and longitude + + :Return: Array for the surface temperature, radiation flux and physical parameters + """ + + # Get the landcover data from lc_grid array + lc_grid[lc_grid >= 100] = 2 + id = np.unique(lc_grid) + id = id.astype(int) + + # Physical parameters grids + cap_grid = np.copy(lc_grid) # Heat capacity + diff_grid = np.copy(lc_grid) # Thermal diffusivity + a1_grid = np.copy(lc_grid) + a2_grid = np.copy(lc_grid) + a3_grid = np.copy(lc_grid) # The 3 OHM coefficients + + # Initial fluxes and temperature grids + Rn = np.zeros_like(lc_grid) # Net radiation + Rn_past = np.zeros_like(lc_grid) # Stored net radiation + G = np.zeros_like(lc_grid) # Ground heat flux + Tg = np.copy(lc_grid) # Surface temperature + Tm = np.copy(lc_grid) # Mean daily surface temperature + + for i in id: + cap_grid[cap_grid == i] = solweig_parameters["Heat capacity"]["Value"][ + solweig_parameters["Names"]["Value"][str(i)] + ] + diff_grid[diff_grid == i] = solweig_parameters["Thermal_diffusivity"][ + "Value" + ][solweig_parameters["Names"]["Value"][str(i)]] + + # Coefficients of the OHM per land cover + mean_a1 = solweig_parameters["OHM_coefficients"]["Values"][ + solweig_parameters["Names"]["Value"][str(i)] + ][0] + phi_a1 = solweig_parameters["OHM_coefficients"]["Values"][ + solweig_parameters["Names"]["Value"][str(i)] + ][1] + a1_grid[a1_grid == i] = mean_a1 * ( + 1 + + 0.33 + * np.sin(2 * np.pi / 365.25 * day + phi_a1) + * np.sign(location["latitude"]) + ) + a2_grid[a2_grid == i] = solweig_parameters["OHM_coefficients"][ + "Values" + ][solweig_parameters["Names"]["Value"][str(i)]][2] + a3_grid[a3_grid == i] = solweig_parameters["OHM_coefficients"][ + "Values" + ][solweig_parameters["Names"]["Value"][str(i)]][3] + + # Initial ground surface temperature parameters + offset_Tg = solweig_parameters["Tg_ini coefficients"]["Values"][ + solweig_parameters["Names"]["Value"][str(i)] + ][0] + ratio_Tg = solweig_parameters["Tg_ini coefficients"]["Values"][ + solweig_parameters["Names"]["Value"][str(i)] + ][1] + phi_Tg = solweig_parameters["Tg_ini coefficients"]["Values"][ + solweig_parameters["Names"]["Value"][str(i)] + ][2] + + # Mean daily soil temperature parameters + ampl_Tm = solweig_parameters["Tm_ini coefficients"]["Values"][ + solweig_parameters["Names"]["Value"][str(i)] + ][0] + phi_Tm = solweig_parameters["Tm_ini coefficients"]["Values"][ + solweig_parameters["Names"]["Value"][str(i)] + ][1] + offset_Tm = solweig_parameters["Tm_ini coefficients"]["Values"][ + solweig_parameters["Names"]["Value"][str(i)] + ][2] + + if i == 0 or i == 1: + # For paved and asphalt landcover + Tg[Tg == i] = ( + Ta[0] + + offset_Tg + * ( + 1 + + 1 + / ratio_Tg + * np.sin(2 * np.pi / 365.25 * day + phi_Tg) + * np.sign(location["latitude"]) + ) + + 4 + ) + Tm[Tm == i] = ( + np.mean(Ta) + + ampl_Tm + * np.sin(2 * np.pi / 365.25 * day + phi_Tm) + * np.sign(location["latitude"]) + + offset_Tm + + 4 + ) + + elif i == 2: + # For roofs + Tg[Tg == i] = ( + Ta[0] + + offset_Tg + * ( + 1 + + 1 + / ratio_Tg + * np.sin(2 * np.pi / 365.25 * day + phi_Tg) + * np.sign(location["latitude"]) + ) + + 4 + ) + Tm[Tm == i] = np.mean(Ta) + offset_Tm + + elif i == 5: + # For grass surfaces + Tg[Tg == i] = Ta[0] + offset_Tg * ( + 1 + + 1 + / ratio_Tg + * np.sin(2 * np.pi / 365.25 * day + phi_Tg) + * np.sign(location["latitude"]) + ) + Tm[Tm == i] = ( + np.mean(Ta) + + ampl_Tm + * np.sin(2 * np.pi / 365.25 * day + phi_Tm) + * np.sign(location["latitude"]) + + offset_Tm + ) + + elif i == 6: + # For bare soil landcover + Tg[Tg == i] = ( + Ta[0] + + offset_Tg + * ( + 1 + + 1 + / ratio_Tg + * np.sin(2 * np.pi / 365.25 * day + phi_Tg) + * np.sign(location["latitude"]) + ) + + 2 + ) + Tm[Tm == i] = ( + np.mean(Ta) + + ampl_Tm + * np.sin(2 * np.pi / 365.25 * day + phi_Tm) + * np.sign(location["latitude"]) + + offset_Tm + + 2 + ) + + elif i == 7: + # For water bodies + Tg[Tg == i] = Ta[0] + + return ( + Tg, + Tm, + Rn, + Rn_past, + G, + cap_grid, + diff_grid, + a1_grid, + a2_grid, + a3_grid, + ) + + +def surfaceTemperature_calc( + Kdown, + Ldown, + Rn, + Rn_past, + G, + Tg, + Tm, + alb, + emis, + cap, + diff, + lc_grid, + a1, + a2, + a3, + timestep, + RH, + shadow, + shadow_past, +): + """ + Calculation of the ground surface temperature + + Based on the force restore method with the ground heat flux modeled according to the heat storage flux formulation depicted in the OHM (Grimmond 1991). + A simple model is implemented to assess the surface temperature for water landcover (lc==7) since the OHM fails to model the corresponding ground heat + flux. The temporal integration is done using the Runge-Kutta 2nd order scheme + + :Parameters: + :Kdown: the downwelling shortwave radiation + :Ldown: the downwelling longwave radiation + :Rn_past: net radiation stored to calculate the radiation rate + :G: ground heat flux stored from the previous timestep + :Tg: ground surface temperature grid + :Tm: Temperature of the deep soil + :alb: albedo grid of the ground surface + :emis: emissivity grid of the surface + :cap: heat capacity grid of the material + :diff: thermal diffusivity grid of the material + :lc_grid: landcover grid to identify the water bodies + :a: coefficient grids related to the OHM + :timestep: the timestep between 2 iteration of the simulation in min + + :Return: + :Tg: Surface temperature + :Rn: Net radiation for the current timestep + :Rn_past: Stored net radiation for upcoming radiation rate calc + :G: Ground heat flux for the current timestep + """ + + # Store the past ground surface temperature + Tg_stored = Tg + + # Damping depths of the daily surface temperature wave + D = np.sqrt((2 * diff) / (2 * math.pi / 86400)) + + ### Runge Kutta method for the surface temperature calc + # First estimate of the surface temperature and of the deep soil temperature given past ground heat flux + k1 = 2 * G / cap / D - 2 * math.pi / 86400 * (Tg - Tm) + Tg_temp = Tg + k1 * timestep + + ### Estimate k2 the surface temperature step based on updated heat fluxes + # The fluxes involved are calculated using the estimated Ts + Lup_temp = SBC * emis * (Tg_temp + 273.15) ** 4 + Ldown * ( + 1 - emis + ) # Temporary outgoing longwave rad (W.m-2) + Rn_temp = Kdown * (1 - alb) + Ldown - Lup_temp # Temporary net rad (W.m-2) + RnStar_temp = (Rn_temp - Rn) / 1 # Temporary radiation rate (W.m-2.h-1) + G_temp = ( + a1 * Rn_temp + a2 * RnStar_temp + a3 + ) # Temporary ground heat flux (W.m-2) + + # Damping of the ground heat flux if it increases (or drops) too quickly + deltaG = abs(G_temp - G) + radCriterion = abs( + a1 * (Rn_temp - Rn_past) + ) # Criterion regarding the radiation step + mask = np.logical_and( + deltaG > radCriterion, abs(shadow - shadow_past) > 0.5 + ) # Grid of the pixels where the ground heat flux spikes + G_temp[mask] = G[mask] + np.sign(G_temp - G)[mask] * radCriterion[mask] + + # Correction of the temperature estimates + k2 = 2 * G_temp / cap / D - 2 * math.pi / 86400 * (Tg_temp - Tm) + Tg += (k1 + k2) / 2 * timestep + + ### Finally calculation of the updated heat fluxes + Rn_past = Rn + G_past = G + Lup = SBC * emis * (Tg + 273.15) ** 4 + (1 - emis) * Ldown + Rn = (1 - alb) * Kdown + Ldown - Lup + Rn_star = (Rn - Rn_past) / 1 + G = a1 * Rn + a2 * Rn_star + a3 + + # Damping of the ground heat flux if it increases (or decreases) too quickly + deltaG = abs(G - G_past) + radCriterion = abs( + a1 * (Rn - Rn_past) + ) # Criterion regarding the radiation step + mask = np.logical_and( + deltaG > radCriterion, abs(shadow - shadow_past) > 0.5 + ) # Grid of the pixels where the ground heat flux spikes + G[mask] = G_past[mask] + np.sign(G - G_past)[mask] * radCriterion[mask] + + ### Water bodies surface temperature estimate + beta = 0.45 # Amount of shortwave rad absorbed by the water surface layer + thickness = 1 # Depth of the water layer + lamb = 2.260e6 # Latent heat of vaporisation + rho = 1000 # Density of water (kg.m-3) + Rn_water = ( + Kdown * (1 - alb) * (beta + (1 - beta) * (1 - np.exp(-1))) + + Ldown + - Lup + ) # Net radiation for the top water layer beta described the transmitted rad + _, es = saturated_vp(Tg) + E = 0.0858 * (es / 1000) * (1 - RH / 100) / 3600 / 1000 * rho * lamb + deltaTg = np.copy(lc_grid) + deltaTg = ( + timestep + / cap + / thickness + * (Rn_water - E - diff * cap / thickness * (Tg - Tm)) + ) + Tg[lc_grid == 7] = Tg_stored[lc_grid == 7] + deltaTg[lc_grid == 7] + + return Tg, Rn, Rn_past, G + + +def outgoingLongwave_calc( + Tg, + Tgwall, + Ta, + Ldown, + emis, + alb, + buildings, + shadow, + sunwall, + walls, + rows, + cols, + sizepx, +): + """ + Calculation of the outgoing longwave radiation from the ground, + + :Parameters: + :Tg: ground surface temperature grid + :Tgwall: wall surface temperature grid + :Ta: air temperature grid + :emis: emissivity grid of the surface + :alb: albedo grid of the surface + :emis_wall: emissivity of the wall (for now float = 0.9) + :buildings: boolean grid, 0 if the landcover is roof and 1 if there is no building + :shadow: boolean grid, 0 when the pixel is shadowed and 1 when sunlit + :sunwall: Grid where non zero values indicate a sunlit wall and its height + :walls: grid containing the heights of the walls + :aspect: grid containing the angles of the normal dir to walls + :rows: number of rows in the grids + :cols: number of columns in the grids + :sizepx: size of a pixel in m (1/scale) + + :Return: + """ + + # Assessment of the distance from a pixel at which most of the radiation are received (cf view factor Lambert) + factor = 0.99 # Percentage of radiation accounted for + zs = 1.1 # in m + r_max = zs * np.sqrt( + factor / (1 - factor) + ) # in m, maximum radius for the radiation calc + + # Emissivity of the wall + emis_wall = 0.9 + alb_wall = 0.2 + + # Copy of the sunlit wall grid and replacement of the wall height with 1 if sunlit + sunlitwall = sunwall + sunlitwall[sunlitwall > 0] = 1 + + # The alb grids only take into account the sunlit surfaces in the alb calculation albnosh calculate it for all the surfaces + albsunlit = alb * shadow + + # Boolean array 1 if the pixel is a wall, 0 if not + wallbol = (walls > 0) * 1 + + # step in meters between every iteration + step = 1 + + ### Initialize the ground view factor grids as np.zeros() + # Upwelling longwave radiation + gvfLup = np.zeros((rows, cols)) + gvfLupE = np.zeros((rows, cols)) + gvfLupS = np.zeros((rows, cols)) + gvfLupW = np.zeros((rows, cols)) + gvfLupN = np.zeros((rows, cols)) + + # Albedo of the sunlit surfaces + gvfalbsun = np.zeros((rows, cols)) + gvfalbsunE = np.zeros((rows, cols)) + gvfalbsunS = np.zeros((rows, cols)) + gvfalbsunW = np.zeros((rows, cols)) + gvfalbsunN = np.zeros((rows, cols)) + + # Albedo complete + gvfalbtot = np.zeros((rows, cols)) + gvfalbtotE = np.zeros((rows, cols)) + gvfalbtotS = np.zeros((rows, cols)) + gvfalbtotW = np.zeros((rows, cols)) + gvfalbtotN = np.zeros((rows, cols)) + + # Longwave radiation coming from the side + gvfLsideE = np.zeros((rows, cols)) + gvfLsideS = np.zeros((rows, cols)) + gvfLsideW = np.zeros((rows, cols)) + gvfLsideN = np.zeros((rows, cols)) + + # Add the radiation from the pixel directly below, only for the total gvf + # Do not take the roofs into account for now + view_factor = (sizepx / 2) ** 2 / ((sizepx / 2) ** 2 + zs**2) + gvfLup = ( + gvfLup + (SBC * emis * (Tg + 273.15) ** 4) * view_factor * buildings + ) + gvfalbsun = gvfalbsun + albsunlit * view_factor * buildings + gvfalbtot = gvfalbtot + alb * view_factor * buildings + + # Division of the 360° field of view in 20 and convert the array in radian + azimuths = np.linspace(18, 360, num=20, endpoint=True) + azimuths = azimuths * (np.pi / 180) + + ### Loop for the number of azimuth values + for azimuth in azimuths: + # Copy of the building grid + building_copy = buildings + + # Initialisation of the tables + # First the ones containing the translated rasters (temporary) + building_temp = np.zeros((rows, cols)) + Lup_temp = np.zeros((rows, cols)) + Lwall_temp = np.zeros((rows, cols)) + albsun_temp = np.zeros((rows, cols)) + albtot_temp = np.zeros((rows, cols)) + sunlitwall_temp = np.zeros((rows, cols)) + + # Then the tables containing the sum of the radiations (or albedo) for this azimuth + Lup_sum = np.zeros((rows, cols)) + LsideE_sum = np.zeros((rows, cols)) + LsideN_sum = np.zeros((rows, cols)) + LsideW_sum = np.zeros((rows, cols)) + LsideS_sum = np.zeros((rows, cols)) + albsun_sum = np.zeros((rows, cols)) + albtot_sum = np.zeros((rows, cols)) + + ### Shadow casting algorithm + # Translation ranges from 1/2 a pixel to the max radius r_max + for r in np.arange(sizepx / 2, r_max, step=step): + # Longwave radiation grids both at the ground level and from the walls + Lup = ( + SBC * emis * (Tg + 273.15) ** 4 + Ldown * (1 - emis) + ) * building_copy + Lwall = ( + SBC * emis_wall * (Tgwall + Ta + 273.15) ** 4 * building_copy + ) + + # Step of the raster translation + dx = -np.cos(azimuth) + dy = -np.sin(azimuth) + + # Scale so that the grid is at least translated from 1px + if abs(dx) > abs(dy): + dx = -r * np.sign(np.cos(azimuth)) + dy = -r * abs(np.tan(azimuth)) * np.sign(np.sin(azimuth)) + else: + dx = -r / abs(np.tan(azimuth)) * np.sign(np.cos(azimuth)) + dy = -r * np.sign(np.sin(azimuth)) + + # Select the interested part of the initial raster and the translated one from their four corners and + # translating toward the direction azimuth = 0° for dx > 0 + if dx > 0: + x_select_start = dx + x_select_end = rows + x_transl_start = 0 + x_transl_end = rows - dx + else: + x_select_start = 0 + x_select_end = rows + dx + x_transl_start = -dx + x_transl_end = rows + + # translating toward the direction azimuth = 90° for dy > 0 + if dy > 0: + y_select_start = dy + y_select_end = cols + y_transl_start = 0 + y_transl_end = cols - dy + else: + y_select_start = 0 + y_select_end = cols + dy + y_transl_start = -dy + y_transl_end = cols + + # Copy the initial rasters and input inside translated raster temporary and changing every iteration + # Building grid + building_temp[ + int(x_transl_start) : math.ceil(x_transl_end), + int(y_transl_start) : math.ceil(y_transl_end), + ] = buildings[ + int(x_select_start) : math.ceil(x_select_end), + int(y_select_start) : math.ceil(y_select_end), + ] + + # Ground longwave radiation grid + Lup_temp[ + int(x_transl_start) : math.ceil(x_transl_end), + int(y_transl_start) : math.ceil(y_transl_end), + ] = Lup[ + int(x_select_start) : math.ceil(x_select_end), + int(y_select_start) : math.ceil(y_select_end), + ] + + # Wall longwave radiation grid + Lwall_temp[ + int(x_transl_start) : math.ceil(x_transl_end), + int(y_transl_start) : math.ceil(y_transl_end), + ] = Lwall[ + int(x_select_start) : math.ceil(x_select_end), + int(y_select_start) : math.ceil(y_select_end), + ] + + # Albedo grid for the sunlit area + albsun_temp[ + int(x_transl_start) : math.ceil(x_transl_end), + int(y_transl_start) : math.ceil(y_transl_end), + ] = albsunlit[ + int(x_select_start) : math.ceil(x_select_end), + int(y_select_start) : math.ceil(y_select_end), + ] + + # Albedo grid for all the area + albtot_temp[ + int(x_transl_start) : math.ceil(x_transl_end), + int(y_transl_start) : math.ceil(y_transl_end), + ] = alb[ + int(x_select_start) : math.ceil(x_select_end), + int(y_select_start) : math.ceil(y_select_end), + ] + + # Sunlit wall grid + sunlitwall_temp[ + int(x_transl_start) : math.ceil(x_transl_end), + int(y_transl_start) : math.ceil(y_transl_end), + ] = sunlitwall[ + int(x_select_start) : math.ceil(x_select_end), + int(y_select_start) : math.ceil(y_select_end), + ] + + # Change the boolean building grid, if the px was already a building it remains one (px value = 0) + building_copy = np.min([building_copy, building_temp], axis=0) + + # For each pixel add the translated Lup to the received rad if there where there is no building + view_factor = ((r + step) ** 2 / (zs**2 + (r + step) ** 2)) - ( + r**2 / (zs**2 + r**2) + ) + Lup_sum += Lup_temp * view_factor * building_copy / 20 + albsun_sum += albsun_temp * view_factor * building_copy / 20 + albtot_sum += albtot_temp * view_factor * building_copy / 20 + + # Create a boolean grid to assert that the sunlit walls are not inside a building + wall_temp = wallbol * building_copy + onlysunwall_temp = sunlitwall_temp * building_copy + + # Then add the radiation incoming from those walls + Lup_sum += ( + wall_temp * Lwall_temp * zs**2 / ((r + step) ** 2 + zs**2) / 20 + ) + albsun_sum += ( + onlysunwall_temp + * alb_wall + * zs**2 + / ((r + step) ** 2 + zs**2) + / 20 + ) + albtot_sum += ( + wall_temp * alb_wall * zs**2 / ((r + step) ** 2 + zs**2) / 20 + ) + + # Finally add the radiation received from the side + dphi = np.arctan((r + step) / zs) - np.arctan(r / zs) + dtrigo = zs / np.sqrt(r**2 + zs**2) * r / np.sqrt( + r**2 + zs**2 + ) - zs / np.sqrt((r + step) ** 2 + zs**2) * (r + step) / np.sqrt( + (r + step) ** 2 + zs**2 + ) + + # Calculation of the solid angle for each of the cardinal points + steradiansW, steradiansS, steradiansE, steradiansN = 0, 0, 0, 0 + if (azimuth >= 0) and (azimuth < np.pi): + dthetaW = 2 * np.pi / 20 + steradiansW += dthetaW * (dphi + dtrigo) / 2 + + if (azimuth >= np.pi / 2) and (azimuth < 3 * np.pi / 2): + dthetaS = 2 * np.pi / 20 + steradiansS += dthetaS * (dphi + dtrigo) / 2 + + if (azimuth >= np.pi) and (azimuth < 2 * np.pi): + dthetaE = 2 * np.pi / 20 + steradiansE += dthetaE * (dphi + dtrigo) / 2 + + if (azimuth >= 3 * np.pi / 2) or (azimuth < np.pi / 2): + dthetaN = 2 * np.pi / 20 + steradiansN += dthetaN * (dphi + dtrigo) / 2 + + LsideW_sum += Lup_temp / np.pi * steradiansW * building_copy + LsideS_sum += Lup_temp / np.pi * steradiansS * building_copy + LsideE_sum += Lup_temp / np.pi * steradiansE * building_copy + LsideN_sum += Lup_temp / np.pi * steradiansN * building_copy + + ### Add the value for the computed part of the field of view + gvfLup += Lup_sum + gvfalbsun += albsun_sum + gvfalbtot += albtot_sum + + # Add the value if the azimuth correspond to the side of the compass + if (azimuth >= 0) and (azimuth < np.pi): + gvfLupW += Lup_sum + gvfalbsunW += albsun_sum + gvfalbtotW += albtot_sum + gvfLsideW += LsideW_sum + + if (azimuth >= np.pi / 2) and (azimuth < 3 * np.pi / 2): + gvfLupS += Lup_sum + gvfalbsunS += albsun_sum + gvfalbtotS += albtot_sum + gvfLsideS += LsideS_sum + + if (azimuth >= np.pi) and (azimuth < 2 * np.pi): + gvfLupE += Lup_sum + gvfalbsunE += albsun_sum + gvfalbtotE += albtot_sum + gvfLsideE += LsideE_sum + + if (azimuth >= 3 * np.pi / 2) or (azimuth < np.pi / 2): + gvfLupN += Lup_sum + gvfalbsunN += albsun_sum + gvfalbtotN += albtot_sum + gvfLsideN += LsideN_sum + + # If the px is associated with a roof landcover, for now Lup = 0 + # Here their Lup value is allocated to those px + gvfLup += (SBC * emis * (Tg + 273.15) ** 4) * (buildings * -1 + 1) + + # # Finally add the reflection from the downwelling longwave radiation + # gvfLup += Ldown * (1-emis) + + return ( + gvfLup, + gvfalbsun, + gvfalbtot, + gvfLupE, + gvfalbsunE, + gvfalbtotE, + gvfLupS, + gvfalbsunS, + gvfalbtotS, + gvfLupW, + gvfalbsunW, + gvfalbtotW, + gvfLupN, + gvfalbsunN, + gvfalbtotN, + gvfLsideW, + gvfLsideS, + gvfLsideE, + gvfLsideN, + ) diff --git a/functions/SOLWEIGpython/gvf_2015a.py b/functions/SOLWEIGpython/gvf_2015a.py index 8f87320..b73f426 100644 --- a/functions/SOLWEIGpython/gvf_2015a.py +++ b/functions/SOLWEIGpython/gvf_2015a.py @@ -2,77 +2,148 @@ import numpy as np from .sunonsurface_2015a import sunonsurface_2015a -def gvf_2015a(wallsun,walls,buildings,scale,shadow,first,second,dirwalls,Tg,Tgwall,Ta,emis_grid,ewall,alb_grid,SBC,albedo_b,rows, cols,Twater,lc_grid,landcover): - - azimuthA=np.arange(5, 359, 20) #Search directions for Ground View Factors (GVF) - + +def gvf_2015a( + wallsun, + walls, + buildings, + scale, + shadow, + first, + second, + dirwalls, + Tg, + Tgwall, + Ta, + emis_grid, + ewall, + alb_grid, + SBC, + albedo_b, + rows, + cols, + Twater, + lc_grid, + landcover, +): + + azimuthA = np.arange( + 5, 359, 20 + ) # Search directions for Ground View Factors (GVF) + #### Ground View Factors #### - gvfLup=np.zeros((rows,cols)) - gvfalb=np.zeros((rows,cols)) - gvfalbnosh=np.zeros((rows,cols)) - gvfLupE=np.zeros((rows,cols)) - gvfLupS=np.zeros((rows,cols)) - gvfLupW=np.zeros((rows,cols)) - gvfLupN=np.zeros((rows,cols)) - gvfalbE=np.zeros((rows,cols)) - gvfalbS=np.zeros((rows,cols)) - gvfalbW=np.zeros((rows,cols)) - gvfalbN=np.zeros((rows,cols)) - gvfalbnoshE=np.zeros((rows,cols)) - gvfalbnoshS=np.zeros((rows,cols)) - gvfalbnoshW=np.zeros((rows,cols)) - gvfalbnoshN=np.zeros((rows,cols)) + gvfLup = np.zeros((rows, cols)) + gvfalb = np.zeros((rows, cols)) + gvfalbnosh = np.zeros((rows, cols)) + gvfLupE = np.zeros((rows, cols)) + gvfLupS = np.zeros((rows, cols)) + gvfLupW = np.zeros((rows, cols)) + gvfLupN = np.zeros((rows, cols)) + gvfalbE = np.zeros((rows, cols)) + gvfalbS = np.zeros((rows, cols)) + gvfalbW = np.zeros((rows, cols)) + gvfalbN = np.zeros((rows, cols)) + gvfalbnoshE = np.zeros((rows, cols)) + gvfalbnoshS = np.zeros((rows, cols)) + gvfalbnoshW = np.zeros((rows, cols)) + gvfalbnoshN = np.zeros((rows, cols)) # sunwall=wallinsun_2015a(buildings,azimuth(i),shadow,psi(i),dirwalls,walls); - sunwall = (wallsun/walls*buildings) == 1 # new as from 2015a + sunwall = (wallsun / walls * buildings) == 1 # new as from 2015a for j in np.arange(0, azimuthA.__len__()): - _, gvfLupi, gvfalbi, gvfalbnoshi = sunonsurface_2015a(azimuthA[j], scale, buildings, shadow, sunwall, first, - second, dirwalls*np.pi/180, walls, Tg, Tgwall, Ta, - emis_grid, ewall, alb_grid, SBC, albedo_b, Twater, - lc_grid, landcover) - - gvfLup=gvfLup+gvfLupi - gvfalb=gvfalb+gvfalbi - gvfalbnosh=gvfalbnosh+gvfalbnoshi - + _, gvfLupi, gvfalbi, gvfalbnoshi = sunonsurface_2015a( + azimuthA[j], + scale, + buildings, + shadow, + sunwall, + first, + second, + dirwalls * np.pi / 180, + walls, + Tg, + Tgwall, + Ta, + emis_grid, + ewall, + alb_grid, + SBC, + albedo_b, + Twater, + lc_grid, + landcover, + ) + + gvfLup = gvfLup + gvfLupi + gvfalb = gvfalb + gvfalbi + gvfalbnosh = gvfalbnosh + gvfalbnoshi + if (azimuthA[j] >= 0) and (azimuthA[j] < 180): - gvfLupE=gvfLupE+gvfLupi - gvfalbE=gvfalbE+gvfalbi - gvfalbnoshE=gvfalbnoshE+gvfalbnoshi + gvfLupE = gvfLupE + gvfLupi + gvfalbE = gvfalbE + gvfalbi + gvfalbnoshE = gvfalbnoshE + gvfalbnoshi if (azimuthA[j] >= 90) and (azimuthA[j] < 270): - gvfLupS=gvfLupS+gvfLupi - gvfalbS=gvfalbS+gvfalbi - gvfalbnoshS=gvfalbnoshS+gvfalbnoshi + gvfLupS = gvfLupS + gvfLupi + gvfalbS = gvfalbS + gvfalbi + gvfalbnoshS = gvfalbnoshS + gvfalbnoshi if (azimuthA[j] >= 180) and (azimuthA[j] < 360): - gvfLupW=gvfLupW+gvfLupi - gvfalbW=gvfalbW+gvfalbi - gvfalbnoshW=gvfalbnoshW+gvfalbnoshi + gvfLupW = gvfLupW + gvfLupi + gvfalbW = gvfalbW + gvfalbi + gvfalbnoshW = gvfalbnoshW + gvfalbnoshi if (azimuthA[j] >= 270) or (azimuthA[j] < 90): - gvfLupN=gvfLupN+gvfLupi - gvfalbN=gvfalbN+gvfalbi - gvfalbnoshN=gvfalbnoshN+gvfalbnoshi - - gvfLup=gvfLup/azimuthA.__len__()+SBC*emis_grid*(Ta+273.15)**4 - gvfalb=gvfalb/azimuthA.__len__() - gvfalbnosh=gvfalbnosh/azimuthA.__len__() - - gvfLupE=gvfLupE/(azimuthA.__len__()/2)+SBC*emis_grid*(Ta+273.15)**4 - gvfLupS=gvfLupS/(azimuthA.__len__()/2)+SBC*emis_grid*(Ta+273.15)**4 - gvfLupW=gvfLupW/(azimuthA.__len__()/2)+SBC*emis_grid*(Ta+273.15)**4 - gvfLupN=gvfLupN/(azimuthA.__len__()/2)+SBC*emis_grid*(Ta+273.15)**4 - - gvfalbE=gvfalbE/(azimuthA.__len__()/2) - gvfalbS=gvfalbS/(azimuthA.__len__()/2) - gvfalbW=gvfalbW/(azimuthA.__len__()/2) - gvfalbN=gvfalbN/(azimuthA.__len__()/2) - - gvfalbnoshE=gvfalbnoshE/(azimuthA.__len__()/2) - gvfalbnoshS=gvfalbnoshS/(azimuthA.__len__()/2) - gvfalbnoshW=gvfalbnoshW/(azimuthA.__len__()/2) - gvfalbnoshN=gvfalbnoshN/(azimuthA.__len__()/2) - - return gvfLup,gvfalb,gvfalbnosh,gvfLupE,gvfalbE,gvfalbnoshE,gvfLupS,gvfalbS,gvfalbnoshS,gvfLupW,gvfalbW,gvfalbnoshW,gvfLupN,gvfalbN,gvfalbnoshN \ No newline at end of file + gvfLupN = gvfLupN + gvfLupi + gvfalbN = gvfalbN + gvfalbi + gvfalbnoshN = gvfalbnoshN + gvfalbnoshi + + gvfLup = gvfLup / azimuthA.__len__() + SBC * emis_grid * (Ta + 273.15) ** 4 + gvfalb = gvfalb / azimuthA.__len__() + gvfalbnosh = gvfalbnosh / azimuthA.__len__() + + gvfLupE = ( + gvfLupE / (azimuthA.__len__() / 2) + + SBC * emis_grid * (Ta + 273.15) ** 4 + ) + gvfLupS = ( + gvfLupS / (azimuthA.__len__() / 2) + + SBC * emis_grid * (Ta + 273.15) ** 4 + ) + gvfLupW = ( + gvfLupW / (azimuthA.__len__() / 2) + + SBC * emis_grid * (Ta + 273.15) ** 4 + ) + gvfLupN = ( + gvfLupN / (azimuthA.__len__() / 2) + + SBC * emis_grid * (Ta + 273.15) ** 4 + ) + + gvfalbE = gvfalbE / (azimuthA.__len__() / 2) + gvfalbS = gvfalbS / (azimuthA.__len__() / 2) + gvfalbW = gvfalbW / (azimuthA.__len__() / 2) + gvfalbN = gvfalbN / (azimuthA.__len__() / 2) + + gvfalbnoshE = gvfalbnoshE / (azimuthA.__len__() / 2) + gvfalbnoshS = gvfalbnoshS / (azimuthA.__len__() / 2) + gvfalbnoshW = gvfalbnoshW / (azimuthA.__len__() / 2) + gvfalbnoshN = gvfalbnoshN / (azimuthA.__len__() / 2) + + return ( + gvfLup, + gvfalb, + gvfalbnosh, + gvfLupE, + gvfalbE, + gvfalbnoshE, + gvfLupS, + gvfalbS, + gvfalbnoshS, + gvfLupW, + gvfalbW, + gvfalbnoshW, + gvfLupN, + gvfalbN, + gvfalbnoshN, + ) diff --git a/functions/SOLWEIGpython/gvf_2018a.py b/functions/SOLWEIGpython/gvf_2018a.py index d3a25cd..d3bbfd1 100644 --- a/functions/SOLWEIGpython/gvf_2018a.py +++ b/functions/SOLWEIGpython/gvf_2018a.py @@ -1,11 +1,35 @@ import numpy as np from .sunonsurface_2018a import sunonsurface_2018a + # import matplotlib.pyplot as plt -def gvf_2018a(wallsun, walls, buildings, scale, shadow, first, second, dirwalls, Tg, Tgwall, Ta, emis_grid, ewall, - alb_grid, SBC, albedo_b, rows, cols, Twater, lc_grid, landcover): - azimuthA = np.arange(5, 359, 20) # Search directions for Ground View Factors (GVF) +def gvf_2018a( + wallsun, + walls, + buildings, + scale, + shadow, + first, + second, + dirwalls, + Tg, + Tgwall, + Ta, + emis_grid, + ewall, + alb_grid, + SBC, + albedo_b, + rows, + cols, + Twater, + lc_grid, + landcover, +): + azimuthA = np.arange( + 5, 359, 20 + ) # Search directions for Ground View Factors (GVF) #### Ground View Factors #### gvfLup = np.zeros((rows, cols)) @@ -29,12 +53,28 @@ def gvf_2018a(wallsun, walls, buildings, scale, shadow, first, second, dirwalls, sunwall = (wallsun / walls * buildings) == 1 # new as from 2015a for j in np.arange(0, azimuthA.__len__()): - _, gvfLupi, gvfalbi, gvfalbnoshi, gvf2 = sunonsurface_2018a(azimuthA[j], scale, buildings, shadow, sunwall, - first, - second, dirwalls * np.pi / 180, walls, Tg, Tgwall, - Ta, - emis_grid, ewall, alb_grid, SBC, albedo_b, Twater, - lc_grid, landcover) + _, gvfLupi, gvfalbi, gvfalbnoshi, gvf2 = sunonsurface_2018a( + azimuthA[j], + scale, + buildings, + shadow, + sunwall, + first, + second, + dirwalls * np.pi / 180, + walls, + Tg, + Tgwall, + Ta, + emis_grid, + ewall, + alb_grid, + SBC, + albedo_b, + Twater, + lc_grid, + landcover, + ) gvfLup = gvfLup + gvfLupi gvfalb = gvfalb + gvfalbi @@ -65,10 +105,22 @@ def gvf_2018a(wallsun, walls, buildings, scale, shadow, first, second, dirwalls, gvfalb = gvfalb / azimuthA.__len__() gvfalbnosh = gvfalbnosh / azimuthA.__len__() - gvfLupE = gvfLupE / (azimuthA.__len__() / 2) + SBC * emis_grid * (Ta + 273.15) ** 4 - gvfLupS = gvfLupS / (azimuthA.__len__() / 2) + SBC * emis_grid * (Ta + 273.15) ** 4 - gvfLupW = gvfLupW / (azimuthA.__len__() / 2) + SBC * emis_grid * (Ta + 273.15) ** 4 - gvfLupN = gvfLupN / (azimuthA.__len__() / 2) + SBC * emis_grid * (Ta + 273.15) ** 4 + gvfLupE = ( + gvfLupE / (azimuthA.__len__() / 2) + + SBC * emis_grid * (Ta + 273.15) ** 4 + ) + gvfLupS = ( + gvfLupS / (azimuthA.__len__() / 2) + + SBC * emis_grid * (Ta + 273.15) ** 4 + ) + gvfLupW = ( + gvfLupW / (azimuthA.__len__() / 2) + + SBC * emis_grid * (Ta + 273.15) ** 4 + ) + gvfLupN = ( + gvfLupN / (azimuthA.__len__() / 2) + + SBC * emis_grid * (Ta + 273.15) ** 4 + ) gvfalbE = gvfalbE / (azimuthA.__len__() / 2) gvfalbS = gvfalbS / (azimuthA.__len__() / 2) @@ -82,5 +134,23 @@ def gvf_2018a(wallsun, walls, buildings, scale, shadow, first, second, dirwalls, gvfNorm = gvfSum / (azimuthA.__len__()) gvfNorm[buildings == 0] = 1 - - return gvfLup, gvfalb, gvfalbnosh, gvfLupE, gvfalbE, gvfalbnoshE, gvfLupS, gvfalbS, gvfalbnoshS, gvfLupW, gvfalbW, gvfalbnoshW, gvfLupN, gvfalbN, gvfalbnoshN, gvfSum, gvfNorm \ No newline at end of file + + return ( + gvfLup, + gvfalb, + gvfalbnosh, + gvfLupE, + gvfalbE, + gvfalbnoshE, + gvfLupS, + gvfalbS, + gvfalbnoshS, + gvfLupW, + gvfalbW, + gvfalbnoshW, + gvfLupN, + gvfalbN, + gvfalbnoshN, + gvfSum, + gvfNorm, + ) diff --git a/functions/SOLWEIGpython/patch_characteristics.py b/functions/SOLWEIGpython/patch_characteristics.py index 208381a..2a1ce27 100644 --- a/functions/SOLWEIGpython/patch_characteristics.py +++ b/functions/SOLWEIGpython/patch_characteristics.py @@ -2,18 +2,32 @@ from copy import deepcopy from . import sunlit_shaded_patches -''' This function defines if a patch seen from a pixel is sky, building or vegetation. +""" This function defines if a patch seen from a pixel is sky, building or vegetation. It also calculates if a building patch is sunlit or shaded. From this it estimates - corresponding longwave radiation originating from each surface.''' - -def define_patch_characteristics(solar_altitude, solar_azimuth, - patch_altitude, patch_azimuth, steradian, - asvf, - shmat, vegshmat, vbshvegshmat, - Lsky_down, Lsky_side, Lsky, Lup, - Ta, Tgwall, ewall, - rows, cols): - + corresponding longwave radiation originating from each surface.""" + + +def define_patch_characteristics( + solar_altitude, + solar_azimuth, + patch_altitude, + patch_azimuth, + steradian, + asvf, + shmat, + vegshmat, + vbshvegshmat, + Lsky_down, + Lsky_side, + Lsky, + Lup, + Ta, + Tgwall, + ewall, + rows, + cols, +): + # Stefan-Boltzmann's Constant SBC = 5.67051e-8 @@ -21,135 +35,351 @@ def define_patch_characteristics(solar_altitude, solar_azimuth, deg2rad = np.pi / 180 # Define variables - Ldown = np.zeros((rows,cols)) - Ldown_sky = np.zeros((rows,cols)) - Ldown_veg = np.zeros((rows,cols)) - Ldown_sun = np.zeros((rows,cols)) - Ldown_sh = np.zeros((rows,cols)) - Ldown_ref = np.zeros((rows,cols)) - - Lside = np.zeros((rows,cols)) - Lside_sky = np.zeros((rows,cols)) - Lside_veg = np.zeros((rows,cols)) - Lside_sun = np.zeros((rows,cols)) - Lside_sh = np.zeros((rows,cols)) - Lside_ref = np.zeros((rows,cols)) - - Least = np.zeros((rows,cols)) - Lwest = np.zeros((rows,cols)) - Lnorth = np.zeros((rows,cols)) - Lsouth = np.zeros((rows,cols)) + Ldown = np.zeros((rows, cols)) + Ldown_sky = np.zeros((rows, cols)) + Ldown_veg = np.zeros((rows, cols)) + Ldown_sun = np.zeros((rows, cols)) + Ldown_sh = np.zeros((rows, cols)) + Ldown_ref = np.zeros((rows, cols)) + + Lside = np.zeros((rows, cols)) + Lside_sky = np.zeros((rows, cols)) + Lside_veg = np.zeros((rows, cols)) + Lside_sun = np.zeros((rows, cols)) + Lside_sh = np.zeros((rows, cols)) + Lside_ref = np.zeros((rows, cols)) + + Least = np.zeros((rows, cols)) + Lwest = np.zeros((rows, cols)) + Lnorth = np.zeros((rows, cols)) + Lsouth = np.zeros((rows, cols)) # Define patch characteristics (sky, vegetation or building, and sunlit or shaded if building) for idx in range(patch_altitude.shape[0]): # Calculations for patches on sky, shmat = 1 = sky is visible - temp_sky = ((shmat[:,:,idx] == 1) & (vegshmat[:,:,idx] == 1)) - + temp_sky = (shmat[:, :, idx] == 1) & (vegshmat[:, :, idx] == 1) + # Longwave radiation from sky to vertical surface - Ldown_sky += temp_sky * Lsky_down[idx,2] - + Ldown_sky += temp_sky * Lsky_down[idx, 2] + # Longwave radiation from sky to horizontal surface - Lside_sky += temp_sky * Lsky_side[idx,2] + Lside_sky += temp_sky * Lsky_side[idx, 2] # Calculations for patches that are vegetation, vegshmat = 0 = shade from vegetation - temp_vegsh = ((vegshmat[:,:,idx] == 0) | (vbshvegshmat[:,:,idx] == 0)) + temp_vegsh = (vegshmat[:, :, idx] == 0) | ( + vbshvegshmat[:, :, idx] == 0 + ) # Longwave radiation from vegetation surface (considered vertical) - vegetation_surface = ((ewall * SBC * ((Ta + 273.15) ** 4)) / np.pi) + vegetation_surface = (ewall * SBC * ((Ta + 273.15) ** 4)) / np.pi # Longwave radiation reaching a vertical surface - Lside_veg += vegetation_surface * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_vegsh - + Lside_veg += ( + vegetation_surface + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_vegsh + ) + # Longwave radiation reaching a horizontal surface - Ldown_veg += vegetation_surface * steradian[idx] * np.sin(patch_altitude[idx] * deg2rad) * temp_vegsh - + Ldown_veg += ( + vegetation_surface + * steradian[idx] + * np.sin(patch_altitude[idx] * deg2rad) + * temp_vegsh + ) + # Portion into cardinal directions to be used for standing box or POI output if (patch_azimuth[idx] > 360) or (patch_azimuth[idx] < 180): - Least += temp_sky * Lsky_side[idx,2] * np.cos((90 - patch_azimuth[idx]) * deg2rad) - Least += vegetation_surface * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_vegsh * np.cos((90 - patch_azimuth[idx]) * deg2rad) + Least += ( + temp_sky + * Lsky_side[idx, 2] + * np.cos((90 - patch_azimuth[idx]) * deg2rad) + ) + Least += ( + vegetation_surface + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_vegsh + * np.cos((90 - patch_azimuth[idx]) * deg2rad) + ) if (patch_azimuth[idx] > 90) and (patch_azimuth[idx] < 270): - Lsouth += temp_sky * Lsky_side[idx,2] * np.cos((180 - patch_azimuth[idx]) * deg2rad) - Lsouth += vegetation_surface * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_vegsh * np.cos((180 - patch_azimuth[idx]) * deg2rad) + Lsouth += ( + temp_sky + * Lsky_side[idx, 2] + * np.cos((180 - patch_azimuth[idx]) * deg2rad) + ) + Lsouth += ( + vegetation_surface + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_vegsh + * np.cos((180 - patch_azimuth[idx]) * deg2rad) + ) if (patch_azimuth[idx] > 180) and (patch_azimuth[idx] < 360): - Lwest += temp_sky * Lsky_side[idx,2] * np.cos((270 - patch_azimuth[idx]) * deg2rad) - Lwest += vegetation_surface * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_vegsh * np.cos((270 - patch_azimuth[idx]) * deg2rad) + Lwest += ( + temp_sky + * Lsky_side[idx, 2] + * np.cos((270 - patch_azimuth[idx]) * deg2rad) + ) + Lwest += ( + vegetation_surface + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_vegsh + * np.cos((270 - patch_azimuth[idx]) * deg2rad) + ) if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] < 90): - Lnorth += temp_sky * Lsky_side[idx,2] * np.cos((0 - patch_azimuth[idx]) * deg2rad) - Lnorth += vegetation_surface * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_vegsh * np.cos((0 - patch_azimuth[idx]) * deg2rad) + Lnorth += ( + temp_sky + * Lsky_side[idx, 2] + * np.cos((0 - patch_azimuth[idx]) * deg2rad) + ) + Lnorth += ( + vegetation_surface + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_vegsh + * np.cos((0 - patch_azimuth[idx]) * deg2rad) + ) # Calculations for patches that are buildings, shmat = 0 = shade from buildings - temp_vbsh = (1 - shmat[:,:,idx]) * vbshvegshmat[:,:,idx] - temp_sh = (temp_vbsh == 1) + temp_vbsh = (1 - shmat[:, :, idx]) * vbshvegshmat[:, :, idx] + temp_sh = temp_vbsh == 1 azimuth_difference = np.abs(solar_azimuth - patch_azimuth[idx]) # Longwave radiation from sunlit surfaces - sunlit_surface = ((ewall * SBC * ((Ta + Tgwall + 273.15) ** 4)) / np.pi) + sunlit_surface = (ewall * SBC * ((Ta + Tgwall + 273.15) ** 4)) / np.pi # Longwave radiation from shaded surfaces - shaded_surface = ((ewall * SBC * ((Ta + 273.15) ** 4)) / np.pi) - if ((azimuth_difference > 90) and (azimuth_difference < 270) and (solar_altitude > 0)): + shaded_surface = (ewall * SBC * ((Ta + 273.15) ** 4)) / np.pi + if ( + (azimuth_difference > 90) + and (azimuth_difference < 270) + and (solar_altitude > 0) + ): # Calculate which patches defined as buildings that are sunlit or shaded - sunlit_patches, shaded_patches = sunlit_shaded_patches.shaded_or_sunlit(solar_altitude, solar_azimuth, patch_altitude[idx], patch_azimuth[idx], asvf) - + sunlit_patches, shaded_patches = ( + sunlit_shaded_patches.shaded_or_sunlit( + solar_altitude, + solar_azimuth, + patch_altitude[idx], + patch_azimuth[idx], + asvf, + ) + ) + # Calculate longwave radiation from sunlit walls to vertical surface - Lside_sun += sunlit_surface * sunlit_patches * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh + Lside_sun += ( + sunlit_surface + * sunlit_patches + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + ) # Calculate longwave radiation from shaded walls to vertical surface - Lside_sh += shaded_surface * shaded_patches * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh - + Lside_sh += ( + shaded_surface + * shaded_patches + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + ) + # Calculate longwave radiation from sunlit walls to horizontal surface - Ldown_sun += sunlit_surface * sunlit_patches * steradian[idx] * np.sin(patch_altitude[idx] * deg2rad) * temp_sh + Ldown_sun += ( + sunlit_surface + * sunlit_patches + * steradian[idx] + * np.sin(patch_altitude[idx] * deg2rad) + * temp_sh + ) # Calculate longwave radiation from shaded walls to horizontal surface - Ldown_sh += shaded_surface * shaded_patches * steradian[idx] * np.sin(patch_altitude[idx] * deg2rad) * temp_sh - + Ldown_sh += ( + shaded_surface + * shaded_patches + * steradian[idx] + * np.sin(patch_altitude[idx] * deg2rad) + * temp_sh + ) + # Portion into cardinal directions to be used for standing box or POI output if (patch_azimuth[idx] > 360) or (patch_azimuth[idx] < 180): - Least += sunlit_surface * sunlit_patches * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((90 - patch_azimuth[idx]) * deg2rad) - Least += shaded_surface * shaded_patches * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((90 - patch_azimuth[idx]) * deg2rad) + Least += ( + sunlit_surface + * sunlit_patches + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((90 - patch_azimuth[idx]) * deg2rad) + ) + Least += ( + shaded_surface + * shaded_patches + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((90 - patch_azimuth[idx]) * deg2rad) + ) if (patch_azimuth[idx] > 90) and (patch_azimuth[idx] < 270): - Lsouth += sunlit_surface * sunlit_patches * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((180 - patch_azimuth[idx]) * deg2rad) - Lsouth += shaded_surface * shaded_patches * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((180 - patch_azimuth[idx]) * deg2rad) + Lsouth += ( + sunlit_surface + * sunlit_patches + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((180 - patch_azimuth[idx]) * deg2rad) + ) + Lsouth += ( + shaded_surface + * shaded_patches + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((180 - patch_azimuth[idx]) * deg2rad) + ) if (patch_azimuth[idx] > 180) and (patch_azimuth[idx] < 360): - Lwest += sunlit_surface * sunlit_patches * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((270 - patch_azimuth[idx]) * deg2rad) - Lwest += shaded_surface * shaded_patches * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((270 - patch_azimuth[idx]) * deg2rad) + Lwest += ( + sunlit_surface + * sunlit_patches + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((270 - patch_azimuth[idx]) * deg2rad) + ) + Lwest += ( + shaded_surface + * shaded_patches + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((270 - patch_azimuth[idx]) * deg2rad) + ) if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] < 90): - Lnorth += sunlit_surface * sunlit_patches * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((0 - patch_azimuth[idx]) * deg2rad) - Lnorth += shaded_surface * shaded_patches * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((0 - patch_azimuth[idx]) * deg2rad) + Lnorth += ( + sunlit_surface + * sunlit_patches + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((0 - patch_azimuth[idx]) * deg2rad) + ) + Lnorth += ( + shaded_surface + * shaded_patches + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((0 - patch_azimuth[idx]) * deg2rad) + ) else: # Calculate longwave radiation from shaded walls reaching a vertical surface - Lside_sh += shaded_surface * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh - + Lside_sh += ( + shaded_surface + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + ) + # Calculate longwave radiation from shaded walls reaching a horizontal surface - Ldown_sh += shaded_surface * steradian[idx] * np.sin(patch_altitude[idx] * deg2rad) * temp_sh + Ldown_sh += ( + shaded_surface + * steradian[idx] + * np.sin(patch_altitude[idx] * deg2rad) + * temp_sh + ) # Portion into cardinal directions to be used for standing box or POI output if (patch_azimuth[idx] > 360) or (patch_azimuth[idx] < 180): - Least += shaded_surface * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((90 - patch_azimuth[idx]) * deg2rad) + Least += ( + shaded_surface + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((90 - patch_azimuth[idx]) * deg2rad) + ) if (patch_azimuth[idx] > 90) and (patch_azimuth[idx] < 270): - Lsouth += shaded_surface * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((180 - patch_azimuth[idx]) * deg2rad) + Lsouth += ( + shaded_surface + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((180 - patch_azimuth[idx]) * deg2rad) + ) if (patch_azimuth[idx] > 180) and (patch_azimuth[idx] < 360): - Lwest += shaded_surface * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((270 - patch_azimuth[idx]) * deg2rad) + Lwest += ( + shaded_surface + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((270 - patch_azimuth[idx]) * deg2rad) + ) if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] < 90): - Lnorth += shaded_surface * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((0 - patch_azimuth[idx]) * deg2rad) + Lnorth += ( + shaded_surface + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((0 - patch_azimuth[idx]) * deg2rad) + ) # Calculate reflected longwave in each patch - reflected_on_surfaces = (((Ldown_sky+Lup)*(1-ewall)*0.5) / np.pi) + reflected_on_surfaces = ((Ldown_sky + Lup) * (1 - ewall) * 0.5) / np.pi for idx in range(patch_altitude.shape[0]): - temp_sh = ((shmat[:,:,idx] == 0) | (vegshmat[:,:,idx] == 0) | (vbshvegshmat[:,:,idx] == 0)) - + temp_sh = ( + (shmat[:, :, idx] == 0) + | (vegshmat[:, :, idx] == 0) + | (vbshvegshmat[:, :, idx] == 0) + ) + # Reflected longwave radiation reaching vertical surfaces - Lside_ref += reflected_on_surfaces * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh - + Lside_ref += ( + reflected_on_surfaces + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + ) + # Reflected longwave radiation reaching horizontal surfaces - Ldown_ref += reflected_on_surfaces * steradian[idx] * np.sin(patch_altitude[idx] * deg2rad) * temp_sh - + Ldown_ref += ( + reflected_on_surfaces + * steradian[idx] + * np.sin(patch_altitude[idx] * deg2rad) + * temp_sh + ) + # Portion into cardinal directions to be used for standing box or POI output if (patch_azimuth[idx] > 360) or (patch_azimuth[idx] < 180): - Least += reflected_on_surfaces * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((90 - patch_azimuth[idx]) * deg2rad) + Least += ( + reflected_on_surfaces + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((90 - patch_azimuth[idx]) * deg2rad) + ) if (patch_azimuth[idx] > 90) and (patch_azimuth[idx] < 270): - Lsouth += reflected_on_surfaces * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((180 - patch_azimuth[idx]) * deg2rad) + Lsouth += ( + reflected_on_surfaces + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((180 - patch_azimuth[idx]) * deg2rad) + ) if (patch_azimuth[idx] > 180) and (patch_azimuth[idx] < 360): - Lwest += reflected_on_surfaces * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((270 - patch_azimuth[idx]) * deg2rad) + Lwest += ( + reflected_on_surfaces + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((270 - patch_azimuth[idx]) * deg2rad) + ) if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] < 90): - Lnorth += reflected_on_surfaces * steradian[idx] * np.cos(patch_altitude[idx] * deg2rad) * temp_sh * np.cos((0 - patch_azimuth[idx]) * deg2rad) + Lnorth += ( + reflected_on_surfaces + * steradian[idx] + * np.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * np.cos((0 - patch_azimuth[idx]) * deg2rad) + ) # Sum of all Lside components (sky, vegetation, sunlit and shaded buildings, reflected) Lside = Lside_sky + Lside_veg + Lside_sh + Lside_sun + Lside_ref @@ -157,21 +387,35 @@ def define_patch_characteristics(solar_altitude, solar_azimuth, # Sum of all Lside components (sky, vegetation, sunlit and shaded buildings, reflected) Ldown = Ldown_sky + Ldown_veg + Ldown_sh + Ldown_sun + Ldown_ref - return Ldown, Lside, Lside_sky, Lside_veg, Lside_sh, Lside_sun, Lside_ref, Least, Lwest, Lnorth, Lsouth + return ( + Ldown, + Lside, + Lside_sky, + Lside_veg, + Lside_sh, + Lside_sun, + Lside_ref, + Least, + Lwest, + Lnorth, + Lsouth, + ) + + +""" This function creates a hemispheric image based on patch characteristics """ -''' This function creates a hemispheric image based on patch characteristics ''' def hemispheric_image(poi, sh, vegsh, vbshvegsh, voxelmat, wallScheme): patch_characteristics = np.zeros((sh.shape[2], poi.shape[0])) for idx in range(poi.shape[0]): for idy in range(sh.shape[2]): # Calculations for patches on sky, shmat = 1 = sky is visible - temp_sky = ((sh[:,:,idy] == 1) & (vegsh[:,:,idy] == 1)) + temp_sky = (sh[:, :, idy] == 1) & (vegsh[:, :, idy] == 1) # Calculations for patches that are vegetation, vegshmat = 0 = shade from vegetation - temp_vegsh = ((vegsh[:,:,idy] == 0) | (vbshvegsh[:,:,idy] == 0)) + temp_vegsh = (vegsh[:, :, idy] == 0) | (vbshvegsh[:, :, idy] == 0) # Calculations for patches that are buildings, shmat = 0 = shade from buildings - temp_vbsh = (1 - sh[:,:,idy]) * vbshvegsh[:,:,idy] - temp_sh = (temp_vbsh == 1) + temp_vbsh = (1 - sh[:, :, idy]) * vbshvegsh[:, :, idy] + temp_sh = temp_vbsh == 1 if wallScheme: temp_sh_w = temp_sh * voxelmat[:, :, idy] temp_sh_roof = temp_sh * (voxelmat[:, :, idy] == 0) @@ -180,20 +424,20 @@ def hemispheric_image(poi, sh, vegsh, vbshvegsh, voxelmat, wallScheme): temp_sh_roof = 0 # Sky patch if temp_sky[int(poi[idx, 2]), int(poi[idx, 1])]: - patch_characteristics[idy,idx] = 1.8 + patch_characteristics[idy, idx] = 1.8 # Vegetation patch - elif (temp_vegsh[int(poi[idx, 2]), int(poi[idx, 1])]): - patch_characteristics[idy,idx] = 2.5 + elif temp_vegsh[int(poi[idx, 2]), int(poi[idx, 1])]: + patch_characteristics[idy, idx] = 2.5 # Building patch - elif (temp_sh[int(poi[idx, 2]), int(poi[idx, 1])]): + elif temp_sh[int(poi[idx, 2]), int(poi[idx, 1])]: if wallScheme: - if (temp_sh_w[int(poi[idx, 2]), int(poi[idx, 1])]): + if temp_sh_w[int(poi[idx, 2]), int(poi[idx, 1])]: patch_characteristics[idy, idx] = 4.5 - elif (temp_sh_roof[int(poi[idx, 2]), int(poi[idx, 1])]): + elif temp_sh_roof[int(poi[idx, 2]), int(poi[idx, 1])]: # patch_characteristics[idy, idx] = 6.0 patch_characteristics[idy, idx] = 4.5 else: - patch_characteristics[idy,idx] = 4.5 + patch_characteristics[idy, idx] = 4.5 # Roof patch - #elif + # elif return patch_characteristics diff --git a/functions/SOLWEIGpython/patch_radiation.py b/functions/SOLWEIGpython/patch_radiation.py index 3aa6780..4787f1f 100644 --- a/functions/SOLWEIGpython/patch_radiation.py +++ b/functions/SOLWEIGpython/patch_radiation.py @@ -1,21 +1,25 @@ import numpy as np from . import sunlit_shaded_patches -def shortwave_from_sky(sky, angle_of_incidence, lumChi, steradian, patch_azimuth, cyl): - '''Calculates the amount of diffuse shortwave radiation from the sky for a patch with: + +def shortwave_from_sky( + sky, angle_of_incidence, lumChi, steradian, patch_azimuth, cyl +): + """Calculates the amount of diffuse shortwave radiation from the sky for a patch with: angle of incidence = angle_of_incidence luminance = lumChi - steradian = steradian''' + steradian = steradian""" # Diffuse vertical radiation diffuse_shortwave_radiation = sky * lumChi * angle_of_incidence * steradian return diffuse_shortwave_radiation + def longwave_from_sky(sky, Lsky_side, Lsky_down, patch_azimuth): # Degrees to radians - deg2rad = np.pi / 180 + deg2rad = np.pi / 180 # Longwave radiation from sky to vertical surface Ldown_sky = sky * Lsky_down @@ -24,7 +28,7 @@ def longwave_from_sky(sky, Lsky_side, Lsky_down, patch_azimuth): Lside_sky = sky * Lsky_side # - Least = np.zeros((sky.shape[0], sky.shape[1])) + Least = np.zeros((sky.shape[0], sky.shape[1])) Lsouth = np.zeros((sky.shape[0], sky.shape[1])) Lwest = np.zeros((sky.shape[0], sky.shape[1])) Lnorth = np.zeros((sky.shape[0], sky.shape[1])) @@ -41,133 +45,310 @@ def longwave_from_sky(sky, Lsky_side, Lsky_down, patch_azimuth): return Lside_sky, Ldown_sky, Least, Lsouth, Lwest, Lnorth -def longwave_from_veg(vegetation, steradian, angle_of_incidence, angle_of_incidence_h, patch_altitude, patch_azimuth, ewall, Ta): - '''Calculates the amount of longwave radiation from vegetation for a patch with: + +def longwave_from_veg( + vegetation, + steradian, + angle_of_incidence, + angle_of_incidence_h, + patch_altitude, + patch_azimuth, + ewall, + Ta, +): + """Calculates the amount of longwave radiation from vegetation for a patch with: angle of incidence = angle_of_incidence steradian = steradian if a patch is vegetation = vegetation - amount of radiation from vegetated patch = vegetation_surface''' + amount of radiation from vegetated patch = vegetation_surface""" # Stefan-Boltzmann's Constant SBC = 5.67051e-8 # Degrees to radians - deg2rad = np.pi / 180 - + deg2rad = np.pi / 180 + # Longwave radiation from vegetation surface (considered vertical) - vegetation_surface = ((ewall * SBC * ((Ta + 273.15) ** 4)) / np.pi) + vegetation_surface = (ewall * SBC * ((Ta + 273.15) ** 4)) / np.pi # Longwave radiation reaching a vertical surface - Lside_veg = vegetation_surface * steradian * angle_of_incidence * vegetation + Lside_veg = ( + vegetation_surface * steradian * angle_of_incidence * vegetation + ) # Longwave radiation reaching a horizontal surface - Ldown_veg = vegetation_surface * steradian * angle_of_incidence_h * vegetation + Ldown_veg = ( + vegetation_surface * steradian * angle_of_incidence_h * vegetation + ) # - Least = np.zeros((vegetation.shape[0], vegetation.shape[1])) + Least = np.zeros((vegetation.shape[0], vegetation.shape[1])) Lsouth = np.zeros((vegetation.shape[0], vegetation.shape[1])) Lwest = np.zeros((vegetation.shape[0], vegetation.shape[1])) Lnorth = np.zeros((vegetation.shape[0], vegetation.shape[1])) # Portion into cardinal directions to be used for standing box or POI output if (patch_azimuth > 360) or (patch_azimuth < 180): - Least = vegetation_surface * steradian * np.cos(patch_altitude * deg2rad) * vegetation * np.cos((90 - patch_azimuth) * deg2rad) + Least = ( + vegetation_surface + * steradian + * np.cos(patch_altitude * deg2rad) + * vegetation + * np.cos((90 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 90) and (patch_azimuth < 270): - Lsouth = vegetation_surface * steradian * np.cos(patch_altitude * deg2rad) * vegetation * np.cos((180 - patch_azimuth) * deg2rad) + Lsouth = ( + vegetation_surface + * steradian + * np.cos(patch_altitude * deg2rad) + * vegetation + * np.cos((180 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 180) and (patch_azimuth < 360): - Lwest = vegetation_surface * steradian * np.cos(patch_altitude * deg2rad) * vegetation * np.cos((270 - patch_azimuth) * deg2rad) + Lwest = ( + vegetation_surface + * steradian + * np.cos(patch_altitude * deg2rad) + * vegetation + * np.cos((270 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 270) or (patch_azimuth < 90): - Lnorth = vegetation_surface * steradian * np.cos(patch_altitude * deg2rad) * vegetation * np.cos((0 - patch_azimuth) * deg2rad) + Lnorth = ( + vegetation_surface + * steradian + * np.cos(patch_altitude * deg2rad) + * vegetation + * np.cos((0 - patch_azimuth) * deg2rad) + ) return Lside_veg, Ldown_veg, Least, Lsouth, Lwest, Lnorth -def longwave_from_buildings(building, steradian, angle_of_incidence, angle_of_incidence_h, patch_azimuth, sunlit_patches, shaded_patches, azimuth_difference, solar_altitude, ewall, Ta, Tgwall): + +def longwave_from_buildings( + building, + steradian, + angle_of_incidence, + angle_of_incidence_h, + patch_azimuth, + sunlit_patches, + shaded_patches, + azimuth_difference, + solar_altitude, + ewall, + Ta, + Tgwall, +): # Stefan-Boltzmann's Constant SBC = 5.67051e-8 # Degrees to radians - deg2rad = np.pi / 180 + deg2rad = np.pi / 180 # - Least = np.zeros((building.shape[0], building.shape[1])) + Least = np.zeros((building.shape[0], building.shape[1])) Lsouth = np.zeros((building.shape[0], building.shape[1])) Lwest = np.zeros((building.shape[0], building.shape[1])) Lnorth = np.zeros((building.shape[0], building.shape[1])) # Longwave radiation from sunlit surfaces - sunlit_surface = ((ewall * SBC * ((Ta + Tgwall + 273.15) ** 4)) / np.pi) + sunlit_surface = (ewall * SBC * ((Ta + Tgwall + 273.15) ** 4)) / np.pi # Longwave radiation from shaded surfaces - shaded_surface = ((ewall * SBC * ((Ta + 273.15) ** 4)) / np.pi) - if ((azimuth_difference > 90) and (azimuth_difference < 270) and (solar_altitude > 0)): + shaded_surface = (ewall * SBC * ((Ta + 273.15) ** 4)) / np.pi + if ( + (azimuth_difference > 90) + and (azimuth_difference < 270) + and (solar_altitude > 0) + ): # Calculate which patches defined as buildings that are sunlit or shaded # sunlit_patches, shaded_patches = sunlit_shaded_patches.shaded_or_sunlit(solar_altitude, solar_azimuth, patch_altitude, patch_azimuth, asvf) - + # Calculate longwave radiation from sunlit walls to vertical surface - Lside_sun = sunlit_surface * sunlit_patches * steradian * angle_of_incidence * building + Lside_sun = ( + sunlit_surface + * sunlit_patches + * steradian + * angle_of_incidence + * building + ) # Calculate longwave radiation from shaded walls to vertical surface - Lside_sh = shaded_surface * shaded_patches * steradian * angle_of_incidence * building - + Lside_sh = ( + shaded_surface + * shaded_patches + * steradian + * angle_of_incidence + * building + ) + # Calculate longwave radiation from sunlit walls to horizontal surface - Ldown_sun = sunlit_surface * sunlit_patches * steradian * angle_of_incidence_h * building + Ldown_sun = ( + sunlit_surface + * sunlit_patches + * steradian + * angle_of_incidence_h + * building + ) # Calculate longwave radiation from shaded walls to horizontal surface - Ldown_sh = shaded_surface * shaded_patches * steradian * angle_of_incidence_h * building - + Ldown_sh = ( + shaded_surface + * shaded_patches + * steradian + * angle_of_incidence_h + * building + ) + # Portion into cardinal directions to be used for standing box or POI output if (patch_azimuth > 360) or (patch_azimuth < 180): - Least = sunlit_surface * sunlit_patches * steradian * angle_of_incidence * building * np.cos((90 - patch_azimuth) * deg2rad) - Least += shaded_surface * shaded_patches * steradian * angle_of_incidence * building * np.cos((90 - patch_azimuth) * deg2rad) + Least = ( + sunlit_surface + * sunlit_patches + * steradian + * angle_of_incidence + * building + * np.cos((90 - patch_azimuth) * deg2rad) + ) + Least += ( + shaded_surface + * shaded_patches + * steradian + * angle_of_incidence + * building + * np.cos((90 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 90) and (patch_azimuth < 270): - Lsouth = sunlit_surface * sunlit_patches * steradian * angle_of_incidence * building * np.cos((180 - patch_azimuth) * deg2rad) - Lsouth += shaded_surface * shaded_patches * steradian * angle_of_incidence * building * np.cos((180 - patch_azimuth) * deg2rad) + Lsouth = ( + sunlit_surface + * sunlit_patches + * steradian + * angle_of_incidence + * building + * np.cos((180 - patch_azimuth) * deg2rad) + ) + Lsouth += ( + shaded_surface + * shaded_patches + * steradian + * angle_of_incidence + * building + * np.cos((180 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 180) and (patch_azimuth < 360): - Lwest = sunlit_surface * sunlit_patches * steradian * angle_of_incidence * building * np.cos((270 - patch_azimuth) * deg2rad) - Lwest += shaded_surface * shaded_patches * steradian * angle_of_incidence * building * np.cos((270 - patch_azimuth) * deg2rad) + Lwest = ( + sunlit_surface + * sunlit_patches + * steradian + * angle_of_incidence + * building + * np.cos((270 - patch_azimuth) * deg2rad) + ) + Lwest += ( + shaded_surface + * shaded_patches + * steradian + * angle_of_incidence + * building + * np.cos((270 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 270) or (patch_azimuth < 90): - Lnorth = sunlit_surface * sunlit_patches * steradian * angle_of_incidence * building * np.cos((0 - patch_azimuth) * deg2rad) - Lnorth += shaded_surface * shaded_patches * steradian * angle_of_incidence * building * np.cos((0 - patch_azimuth) * deg2rad) + Lnorth = ( + sunlit_surface + * sunlit_patches + * steradian + * angle_of_incidence + * building + * np.cos((0 - patch_azimuth) * deg2rad) + ) + Lnorth += ( + shaded_surface + * shaded_patches + * steradian + * angle_of_incidence + * building + * np.cos((0 - patch_azimuth) * deg2rad) + ) else: # Calculate longwave radiation from shaded walls reaching a vertical surface Lside_sh = shaded_surface * steradian * angle_of_incidence * building Lside_sun = np.zeros((Lside_sh.shape[0], Lside_sh.shape[1])) - + # Calculate longwave radiation from shaded walls reaching a horizontal surface Ldown_sh = shaded_surface * steradian * angle_of_incidence_h * building Ldown_sun = np.zeros((Lside_sh.shape[0], Lside_sh.shape[1])) # Portion into cardinal directions to be used for standing box or POI output if (patch_azimuth > 360) or (patch_azimuth < 180): - Least = shaded_surface * steradian * angle_of_incidence * building * np.cos((90 - patch_azimuth) * deg2rad) + Least = ( + shaded_surface + * steradian + * angle_of_incidence + * building + * np.cos((90 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 90) and (patch_azimuth < 270): - Lsouth = shaded_surface * steradian * angle_of_incidence * building * np.cos((180 - patch_azimuth) * deg2rad) + Lsouth = ( + shaded_surface + * steradian + * angle_of_incidence + * building + * np.cos((180 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 180) and (patch_azimuth < 360): - Lwest = shaded_surface * steradian * angle_of_incidence * building * np.cos((270 - patch_azimuth) * deg2rad) + Lwest = ( + shaded_surface + * steradian + * angle_of_incidence + * building + * np.cos((270 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 270) or (patch_azimuth < 90): - Lnorth = shaded_surface * steradian * angle_of_incidence * building * np.cos((0 - patch_azimuth) * deg2rad) - - return Lside_sun, Lside_sh, Ldown_sun, Ldown_sh, Least, Lsouth, Lwest, Lnorth - -def longwave_from_buildings_wallScheme(voxelMaps, voxelTable, steradian, angle_of_incidence, angle_of_incidence_h, patch_azimuth): + Lnorth = ( + shaded_surface + * steradian + * angle_of_incidence + * building + * np.cos((0 - patch_azimuth) * deg2rad) + ) + + return ( + Lside_sun, + Lside_sh, + Ldown_sun, + Ldown_sh, + Least, + Lsouth, + Lwest, + Lnorth, + ) + + +def longwave_from_buildings_wallScheme( + voxelMaps, + voxelTable, + steradian, + angle_of_incidence, + angle_of_incidence_h, + patch_azimuth, +): # Degrees to radians - deg2rad = np.pi / 180 + deg2rad = np.pi / 180 # Lside = np.zeros((voxelMaps.shape[0], voxelMaps.shape[1])) Lside_sh = np.zeros((voxelMaps.shape[0], voxelMaps.shape[1])) Ldown = np.zeros((voxelMaps.shape[0], voxelMaps.shape[1])) Ldown_sh = np.zeros((voxelMaps.shape[0], voxelMaps.shape[1])) - Least = np.zeros((voxelMaps.shape[0], voxelMaps.shape[1])) + Least = np.zeros((voxelMaps.shape[0], voxelMaps.shape[1])) Lsouth = np.zeros((voxelMaps.shape[0], voxelMaps.shape[1])) Lwest = np.zeros((voxelMaps.shape[0], voxelMaps.shape[1])) - Lnorth = np.zeros((voxelMaps.shape[0], voxelMaps.shape[1])) - + Lnorth = np.zeros((voxelMaps.shape[0], voxelMaps.shape[1])) + # print(voxelMaps) - #print(voxelTable.head()) + # print(voxelTable.head()) l = list(np.unique(voxelMaps)[1:]) # print(l) - a = dict(voxelTable.loc[l, 'LongwaveRadiation']) + a = dict(voxelTable.loc[l, "LongwaveRadiation"]) # print(a) patch_radiation = np.vectorize(a.get)(voxelMaps).astype(float) patch_radiation[np.isnan(patch_radiation)] = 0 @@ -176,53 +357,126 @@ def longwave_from_buildings_wallScheme(voxelMaps, voxelTable, steradian, angle_o # Portion into cardinal directions to be used for standing box or POI output if (patch_azimuth > 360) or (patch_azimuth < 180): - Least = patch_radiation * steradian * angle_of_incidence * np.cos((90 - patch_azimuth) * deg2rad) + Least = ( + patch_radiation + * steradian + * angle_of_incidence + * np.cos((90 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 90) and (patch_azimuth < 270): - Lsouth = patch_radiation * steradian * angle_of_incidence * np.cos((180 - patch_azimuth) * deg2rad) + Lsouth = ( + patch_radiation + * steradian + * angle_of_incidence + * np.cos((180 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 180) and (patch_azimuth < 360): - Lwest = patch_radiation * steradian * angle_of_incidence * np.cos((270 - patch_azimuth) * deg2rad) + Lwest = ( + patch_radiation + * steradian + * angle_of_incidence + * np.cos((270 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 270) or (patch_azimuth < 90): - Lnorth = patch_radiation * steradian * angle_of_incidence * np.cos((0 - patch_azimuth) * deg2rad) + Lnorth = ( + patch_radiation + * steradian + * angle_of_incidence + * np.cos((0 - patch_azimuth) * deg2rad) + ) return Lside, Lside_sh, Ldown, Ldown_sh, Least, Lsouth, Lwest, Lnorth -def reflected_longwave(reflecting_surface, steradian, angle_of_incidence, angle_of_incidence_h, patch_azimuth, Ldown_sky, Lup, ewall): + +def reflected_longwave( + reflecting_surface, + steradian, + angle_of_incidence, + angle_of_incidence_h, + patch_azimuth, + Ldown_sky, + Lup, + ewall, +): # Degrees to radians - deg2rad = np.pi / 180 + deg2rad = np.pi / 180 # Calculate reflected longwave in each patch - reflected_radiation = (((Ldown_sky+Lup) * (1-ewall)*0.5) / np.pi) + reflected_radiation = ((Ldown_sky + Lup) * (1 - ewall) * 0.5) / np.pi # Reflected longwave radiation reaching vertical surfaces - Lside_ref = reflected_radiation * steradian * angle_of_incidence * reflecting_surface - + Lside_ref = ( + reflected_radiation + * steradian + * angle_of_incidence + * reflecting_surface + ) + # Reflected longwave radiation reaching horizontal surfaces - Ldown_ref = reflected_radiation * steradian * angle_of_incidence_h * reflecting_surface + Ldown_ref = ( + reflected_radiation + * steradian + * angle_of_incidence_h + * reflecting_surface + ) # - Least = np.zeros((reflecting_surface.shape[0], reflecting_surface.shape[1])) - Lsouth = np.zeros((reflecting_surface.shape[0], reflecting_surface.shape[1])) - Lwest = np.zeros((reflecting_surface.shape[0], reflecting_surface.shape[1])) - Lnorth = np.zeros((reflecting_surface.shape[0], reflecting_surface.shape[1])) + Least = np.zeros( + (reflecting_surface.shape[0], reflecting_surface.shape[1]) + ) + Lsouth = np.zeros( + (reflecting_surface.shape[0], reflecting_surface.shape[1]) + ) + Lwest = np.zeros( + (reflecting_surface.shape[0], reflecting_surface.shape[1]) + ) + Lnorth = np.zeros( + (reflecting_surface.shape[0], reflecting_surface.shape[1]) + ) # Portion into cardinal directions to be used for standing box or POI output if (patch_azimuth > 360) or (patch_azimuth < 180): - Least = reflected_radiation * steradian * angle_of_incidence * reflecting_surface * np.cos((90 - patch_azimuth) * deg2rad) + Least = ( + reflected_radiation + * steradian + * angle_of_incidence + * reflecting_surface + * np.cos((90 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 90) and (patch_azimuth < 270): - Lsouth = reflected_radiation * steradian * angle_of_incidence * reflecting_surface * np.cos((180 - patch_azimuth) * deg2rad) + Lsouth = ( + reflected_radiation + * steradian + * angle_of_incidence + * reflecting_surface + * np.cos((180 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 180) and (patch_azimuth < 360): - Lwest = reflected_radiation * steradian * angle_of_incidence * reflecting_surface * np.cos((270 - patch_azimuth) * deg2rad) + Lwest = ( + reflected_radiation + * steradian + * angle_of_incidence + * reflecting_surface + * np.cos((270 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 270) or (patch_azimuth < 90): - Lnorth = reflected_radiation * steradian * angle_of_incidence * reflecting_surface * np.cos((0 - patch_azimuth) * deg2rad) + Lnorth = ( + reflected_radiation + * steradian + * angle_of_incidence + * reflecting_surface + * np.cos((0 - patch_azimuth) * deg2rad) + ) return Lside_ref, Ldown_ref, Least, Lsouth, Lwest, Lnorth + def patch_steradians(L_patches): - ''''This function calculates the steradians of the patches''' + """'This function calculates the steradians of the patches""" # Degrees to radians - deg2rad = np.pi / 180 + deg2rad = np.pi / 180 # Unique altitudes for patches skyalt, skyalt_c = np.unique(L_patches[:, 0], return_counts=True) @@ -235,11 +489,19 @@ def patch_steradians(L_patches): for i in range(patch_altitude.shape[0]): # If there are more than one patch in a band if skyalt_c[skyalt == patch_altitude[i]] > 1: - steradian[i] = ((360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad) * (np.sin((patch_altitude[i] + patch_altitude[0]) * deg2rad) \ - - np.sin((patch_altitude[i] - patch_altitude[0]) * deg2rad)) + steradian[i] = ( + (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad + ) * ( + np.sin((patch_altitude[i] + patch_altitude[0]) * deg2rad) + - np.sin((patch_altitude[i] - patch_altitude[0]) * deg2rad) + ) # If there is only one patch in band, i.e. 90 degrees else: - steradian[i] = ((360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad) * (np.sin((patch_altitude[i]) * deg2rad) \ - - np.sin((patch_altitude[i-1] + patch_altitude[0]) * deg2rad)) - - return steradian, skyalt, patch_altitude \ No newline at end of file + steradian[i] = ( + (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad + ) * ( + np.sin((patch_altitude[i]) * deg2rad) + - np.sin((patch_altitude[i - 1] + patch_altitude[0]) * deg2rad) + ) + + return steradian, skyalt, patch_altitude diff --git a/functions/SOLWEIGpython/sunlit_shaded_patches.py b/functions/SOLWEIGpython/sunlit_shaded_patches.py index c935c08..4c6e75a 100644 --- a/functions/SOLWEIGpython/sunlit_shaded_patches.py +++ b/functions/SOLWEIGpython/sunlit_shaded_patches.py @@ -1,23 +1,26 @@ import numpy as np -''' This function calculates whether a point is sunlit or shaded - based on a sky view factor (in a cylinder), solar altitude, solar azimuth ''' +""" This function calculates whether a point is sunlit or shaded + based on a sky view factor (in a cylinder), solar altitude, solar azimuth """ -def shaded_or_sunlit(solar_altitude, solar_azimuth, patch_altitude, patch_azimuth, asvf): + +def shaded_or_sunlit( + solar_altitude, solar_azimuth, patch_altitude, patch_azimuth, asvf +): # Patch azimuth in relation to sun azimuth patch_to_sun_azi = np.abs(solar_azimuth - patch_azimuth) # Degrees to radians - deg2rad = np.pi/180 - + deg2rad = np.pi / 180 + # Radians to degrees - rad2deg = 180/np.pi + rad2deg = 180 / np.pi - # + # xi = np.cos(patch_to_sun_azi * deg2rad) - # + # yi = 2 * xi * np.tan(solar_altitude * deg2rad) hsvf = np.tan(asvf) @@ -27,7 +30,7 @@ def shaded_or_sunlit(solar_altitude, solar_azimuth, patch_altitude, patch_azimut else: yi_ = yi - # + # tan_delta = hsvf + yi_ # Degrees where below is in shade and above is sunlit @@ -38,4 +41,4 @@ def shaded_or_sunlit(solar_altitude, solar_azimuth, patch_altitude, patch_azimut # Boolean for pixels where patch is shaded shaded_patches = sunlit_degrees > patch_altitude - return sunlit_patches, shaded_patches \ No newline at end of file + return sunlit_patches, shaded_patches diff --git a/functions/SOLWEIGpython/sunonsurface_2018a.py b/functions/SOLWEIGpython/sunonsurface_2018a.py index fa713cf..0acbc6c 100644 --- a/functions/SOLWEIGpython/sunonsurface_2018a.py +++ b/functions/SOLWEIGpython/sunonsurface_2018a.py @@ -1,7 +1,28 @@ import numpy as np -def sunonsurface_2018a(azimuthA, scale, buildings, shadow, sunwall, first, second, aspect, walls, Tg, Tgwall, Ta, - emis_grid, ewall, alb_grid, SBC, albedo_b, Twater, lc_grid, landcover): + +def sunonsurface_2018a( + azimuthA, + scale, + buildings, + shadow, + sunwall, + first, + second, + aspect, + walls, + Tg, + Tgwall, + Ta, + emis_grid, + ewall, + alb_grid, + SBC, + albedo_b, + Twater, + lc_grid, + landcover, +): # This version of sunonsurfce goes with SOLWEIG 2015a. It also simulates # Lup and albedo based on landcover information and shadow patterns. # Fredrik Lindberg, fredrikl@gvc.gu.se @@ -19,11 +40,17 @@ def sunonsurface_2018a(azimuthA, scale, buildings, shadow, sunwall, first, secon # loop parameters index = 0 f = buildings - Lup = SBC * emis_grid * (Tg * shadow + Ta + 273.15) ** 4 - SBC * emis_grid * (Ta + 273.15) ** 4 # +Ta + Lup = ( + SBC * emis_grid * (Tg * shadow + Ta + 273.15) ** 4 + - SBC * emis_grid * (Ta + 273.15) ** 4 + ) # +Ta if landcover == 1: Tg[lc_grid == 3] = Twater - Ta # Setting water temperature - Lwall = SBC * ewall * (Tgwall + Ta + 273.15) ** 4 - SBC * ewall * (Ta + 273.15) ** 4 # +Ta + Lwall = ( + SBC * ewall * (Tgwall + Ta + 273.15) ** 4 + - SBC * ewall * (Ta + 273.15) ** 4 + ) # +Ta albshadow = alb_grid * shadow alb = alb_grid # sh(sh<=0.1)=0; @@ -69,7 +96,8 @@ def sunonsurface_2018a(azimuthA, scale, buildings, shadow, sunwall, first, secon ## The Shadow casting algoritm for n in np.arange(0, second): if (pibyfour <= azimuth and azimuth < threetimespibyfour) or ( - fivetimespibyfour <= azimuth and azimuth < seventimespibyfour): + fivetimespibyfour <= azimuth and azimuth < seventimespibyfour + ): dy = signsinazimuth * index dx = -1 * signcosazimuth * np.abs(np.round(index / tanazimuth)) # ds = dssin @@ -81,23 +109,31 @@ def sunonsurface_2018a(azimuthA, scale, buildings, shadow, sunwall, first, secon absdx = np.abs(dx) absdy = np.abs(dy) - xc1 = ((dx + absdx) / 2) - xc2 = (sizex + (dx - absdx) / 2) - yc1 = ((dy + absdy) / 2) - yc2 = (sizey + (dy - absdy) / 2) + xc1 = (dx + absdx) / 2 + xc2 = sizex + (dx - absdx) / 2 + yc1 = (dy + absdy) / 2 + yc2 = sizey + (dy - absdy) / 2 xp1 = -((dx - absdx) / 2) - xp2 = (sizex - (dx + absdx) / 2) + xp2 = sizex - (dx + absdx) / 2 yp1 = -((dy - absdy) / 2) - yp2 = (sizey - (dy + absdy) / 2) - - tempbu[int(xp1):int(xp2), int(yp1):int(yp2)] = buildings[int(xc1):int(xc2), - int(yc1):int(yc2)] # moving building - tempsh[int(xp1):int(xp2), int(yp1):int(yp2)] = shadow[int(xc1):int(xc2), int(yc1):int(yc2)] # moving shadow - tempLupsh[int(xp1):int(xp2), int(yp1):int(yp2)] = Lup[int(xc1):int(xc2), int(yc1):int(yc2)] # moving Lup/shadow - tempalbsh[int(xp1):int(xp2), int(yp1):int(yp2)] = albshadow[int(xc1):int(xc2), - int(yc1):int(yc2)] # moving Albedo/shadow - tempalbnosh[int(xp1):int(xp2), int(yp1):int(yp2)] = alb[int(xc1):int(xc2), int(yc1):int(yc2)] # moving Albedo + yp2 = sizey - (dy + absdy) / 2 + + tempbu[int(xp1) : int(xp2), int(yp1) : int(yp2)] = buildings[ + int(xc1) : int(xc2), int(yc1) : int(yc2) + ] # moving building + tempsh[int(xp1) : int(xp2), int(yp1) : int(yp2)] = shadow[ + int(xc1) : int(xc2), int(yc1) : int(yc2) + ] # moving shadow + tempLupsh[int(xp1) : int(xp2), int(yp1) : int(yp2)] = Lup[ + int(xc1) : int(xc2), int(yc1) : int(yc2) + ] # moving Lup/shadow + tempalbsh[int(xp1) : int(xp2), int(yp1) : int(yp2)] = albshadow[ + int(xc1) : int(xc2), int(yc1) : int(yc2) + ] # moving Albedo/shadow + tempalbnosh[int(xp1) : int(xp2), int(yp1) : int(yp2)] = alb[ + int(xc1) : int(xc2), int(yc1) : int(yc2) + ] # moving Albedo f = np.min([f, tempbu], axis=0) # utsmetning av buildings shadow2 = tempsh * f @@ -112,8 +148,9 @@ def sunonsurface_2018a(azimuthA, scale, buildings, shadow, sunwall, first, secon albnosh = tempalbnosh * f weightsumalbnosh = weightsumalbnosh + albnosh - tempwallsun[int(xp1):int(xp2), int(yp1):int(yp2)] = sunwall[int(xc1):int(xc2), - int(yc1):int(yc2)] # moving buildingwall insun image + tempwallsun[int(xp1) : int(xp2), int(yp1) : int(yp2)] = sunwall[ + int(xc1) : int(xc2), int(yc1) : int(yc2) + ] # moving buildingwall insun image tempb = tempwallsun * f tempbwall = f * -1 + 1 tempbub = ((tempb + tempbub) > 0) * 1 @@ -133,7 +170,9 @@ def sunonsurface_2018a(azimuthA, scale, buildings, shadow, sunwall, first, secon weightsumalbwall_first = weightsumalbwall / ind # *albedo_b weightsumalbsh_first = weightsumalbsh / ind - weightsumalbwallnosh_first = weightsumalbwallnosh / ind # *albedo_b + weightsumalbwallnosh_first = ( + weightsumalbwallnosh / ind + ) # *albedo_b weightsumalbnosh_first = weightsumalbnosh / ind wallinfluence_first = weightsumalbwallnosh_first > 0 # gvf1=(weightsumwall+weightsumsh)/first; @@ -149,55 +188,91 @@ def sunonsurface_2018a(azimuthA, scale, buildings, shadow, sunwall, first, secon azilow = azimuth - np.pi / 2 azihigh = azimuth + np.pi / 2 if azilow >= 0 and azihigh < 2 * np.pi: # 90 to 270 (SHADOW) - facesh = (np.logical_or(aspect < azilow, aspect >= azihigh).astype(float) - wallbol + 1) + facesh = ( + np.logical_or(aspect < azilow, aspect >= azihigh).astype(float) + - wallbol + + 1 + ) elif azilow < 0 and azihigh <= 2 * np.pi: # 0 to 90 azilow = azilow + 2 * np.pi - facesh = np.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 # (SHADOW) # check for the -1 + facesh = ( + np.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 + ) # (SHADOW) # check for the -1 elif azilow > 0 and azihigh >= 2 * np.pi: # 270 to 360 azihigh = azihigh - 2 * np.pi - facesh = np.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 # (SHADOW) + facesh = ( + np.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 + ) # (SHADOW) # removing walls in self shadoing keep = (weightsumwall == second) - facesh keep[keep == -1] = 0 # gvf from shadow only - gvf1 = ((weightsumwall_first + weightsumsh_first) / (first + 1)) * wallsuninfluence_first + \ - (weightsumsh_first) / (first) * (wallsuninfluence_first * -1 + 1) + gvf1 = ( + (weightsumwall_first + weightsumsh_first) / (first + 1) + ) * wallsuninfluence_first + (weightsumsh_first) / (first) * ( + wallsuninfluence_first * -1 + 1 + ) weightsumwall[keep == 1] = 0 - gvf2 = ((weightsumwall + weightsumsh) / (second + 1)) * wallsuninfluence_second + \ - (weightsumsh) / (second) * (wallsuninfluence_second * -1 + 1) + gvf2 = ( + (weightsumwall + weightsumsh) / (second + 1) + ) * wallsuninfluence_second + (weightsumsh) / (second) * ( + wallsuninfluence_second * -1 + 1 + ) - gvf2[gvf2 > 1.] = 1. + gvf2[gvf2 > 1.0] = 1.0 # gvf from shadow and Lup - gvfLup1 = ((weightsumLwall_first + weightsumLupsh_first) / (first + 1)) * wallsuninfluence_first + \ - (weightsumLupsh_first) / (first) * (wallsuninfluence_first * -1 + 1) + gvfLup1 = ( + (weightsumLwall_first + weightsumLupsh_first) / (first + 1) + ) * wallsuninfluence_first + (weightsumLupsh_first) / (first) * ( + wallsuninfluence_first * -1 + 1 + ) weightsumLwall[keep == 1] = 0 - gvfLup2 = ((weightsumLwall + weightsumLupsh) / (second + 1)) * wallsuninfluence_second + \ - (weightsumLupsh) / (second) * (wallsuninfluence_second * -1 + 1) + gvfLup2 = ( + (weightsumLwall + weightsumLupsh) / (second + 1) + ) * wallsuninfluence_second + (weightsumLupsh) / (second) * ( + wallsuninfluence_second * -1 + 1 + ) # gvf from shadow and albedo - gvfalb1 = ((weightsumalbwall_first + weightsumalbsh_first) / (first + 1)) * wallsuninfluence_first + \ - (weightsumalbsh_first) / (first) * (wallsuninfluence_first * -1 + 1) + gvfalb1 = ( + (weightsumalbwall_first + weightsumalbsh_first) / (first + 1) + ) * wallsuninfluence_first + (weightsumalbsh_first) / (first) * ( + wallsuninfluence_first * -1 + 1 + ) weightsumalbwall[keep == 1] = 0 - gvfalb2 = ((weightsumalbwall + weightsumalbsh) / (second + 1)) * wallsuninfluence_second + \ - (weightsumalbsh) / (second) * (wallsuninfluence_second * -1 + 1) + gvfalb2 = ( + (weightsumalbwall + weightsumalbsh) / (second + 1) + ) * wallsuninfluence_second + (weightsumalbsh) / (second) * ( + wallsuninfluence_second * -1 + 1 + ) # gvf from albedo only - gvfalbnosh1 = ((weightsumalbwallnosh_first + weightsumalbnosh_first) / (first + 1)) * wallinfluence_first + \ - (weightsumalbnosh_first) / (first) * (wallinfluence_first * -1 + 1) # - gvfalbnosh2 = ((weightsumalbwallnosh + weightsumalbnosh) / (second)) * wallinfluence_second + \ - (weightsumalbnosh) / (second) * (wallinfluence_second * -1 + 1) + gvfalbnosh1 = ( + (weightsumalbwallnosh_first + weightsumalbnosh_first) / (first + 1) + ) * wallinfluence_first + (weightsumalbnosh_first) / (first) * ( + wallinfluence_first * -1 + 1 + ) # + gvfalbnosh2 = ( + (weightsumalbwallnosh + weightsumalbnosh) / (second) + ) * wallinfluence_second + (weightsumalbnosh) / (second) * ( + wallinfluence_second * -1 + 1 + ) # Weighting gvf = (gvf1 * 0.5 + gvf2 * 0.4) / 0.9 gvfLup = (gvfLup1 * 0.5 + gvfLup2 * 0.4) / 0.9 - gvfLup = gvfLup + ((SBC * emis_grid * (Tg * shadow + Ta + 273.15) ** 4) - SBC * emis_grid * (Ta + 273.15) ** 4) * ( - buildings * -1 + 1) # +Ta + gvfLup = gvfLup + ( + (SBC * emis_grid * (Tg * shadow + Ta + 273.15) ** 4) + - SBC * emis_grid * (Ta + 273.15) ** 4 + ) * ( + buildings * -1 + 1 + ) # +Ta gvfalb = (gvfalb1 * 0.5 + gvfalb2 * 0.4) / 0.9 gvfalb = gvfalb + alb_grid * (buildings * -1 + 1) * shadow gvfalbnosh = (gvfalbnosh1 * 0.5 + gvfalbnosh2 * 0.4) / 0.9 gvfalbnosh = gvfalbnosh * buildings + alb_grid * (buildings * -1 + 1) - return gvf, gvfLup, gvfalb, gvfalbnosh, gvf2 \ No newline at end of file + return gvf, gvfLup, gvfalb, gvfalbnosh, gvf2 diff --git a/functions/SOLWEIGpython/wallOfInterest.py b/functions/SOLWEIGpython/wallOfInterest.py index 1c4399e..60c1771 100644 --- a/functions/SOLWEIGpython/wallOfInterest.py +++ b/functions/SOLWEIGpython/wallOfInterest.py @@ -3,12 +3,20 @@ from osgeo.gdalconst import * from osgeo import gdal, osr + def pointOfInterest(poilyr, poi_field, scale, gdal_dsm): - (dsm_minx, dsm_x_size, dsm_x_rotation, dsm_miny, dsm_y_rotation, dsm_y_size) = gdal_dsm.GetGeoTransform() #TODO: fix for standalone + ( + dsm_minx, + dsm_x_size, + dsm_x_rotation, + dsm_miny, + dsm_y_rotation, + dsm_y_size, + ) = gdal_dsm.GetGeoTransform() # TODO: fix for standalone - # header = 'yyyy id it imin dectime altitude azimuth Ta' - poilyr = QgsVectorLayer(poilyr, 'point', 'ogr') + # header = 'yyyy id it imin dectime altitude azimuth Ta' + poilyr = QgsVectorLayer(poilyr, "point", "ogr") idx = poilyr.fields().indexFromName(poi_field) numfeat = poilyr.featureCount() poiname = [] @@ -29,7 +37,7 @@ def pointOfInterest(poilyr, poi_field, scale, gdal_dsm): # poisxy[ind, 2] = np.round((miny + rows * (1. / scale) - y) * scale) # else: # poisxy[ind, 2] = np.round((miny + rows * (1. / scale) - y) * scale) - # print('y = ' + str(np.round((miny + rows * (1. / scale) - y) * scale))) + # print('y = ' + str(np.round((miny + rows * (1. / scale) - y) * scale))) poisxy[ind, 1] = xcoordinate poisxy[ind, 2] = ycoordinate @@ -41,18 +49,56 @@ def pointOfInterest(poilyr, poi_field, scale, gdal_dsm): # data_out = outputDir + '/POI_' + str(poiname[k]) + '.txt' # np.savetxt(data_out, poi_save, delimiter=' ', header=header, comments='') - return poisxy, poiname + return poisxy, poiname + -def fillWallOfInterest(voxelTable, voxelHeight, woisxy, woiname, outputDir, i, YYYY, jday, hours, minu, dectime, Ta, svf): +def fillWallOfInterest( + voxelTable, + voxelHeight, + woisxy, + woiname, + outputDir, + i, + YYYY, + jday, + hours, + minu, + dectime, + Ta, + svf, +): if not woisxy is None: for k in range(0, woisxy.shape[0]): - tempTable = voxelTable.loc[((voxelTable['ypos'] == woisxy[k, 2]) & (voxelTable['xpos'] == woisxy[k, 1]) & voxelTable['voxelHeight'] == voxelHeight)].copy() - output_vars = np.array(['wallTemperature', 'K_in', 'L_in', 'wallTemperatureSolweigOld', - 'Lwallsun', 'Lwallsh', 'Lrefl', 'Lveg', 'Lground', 'Lsky', - 'esky', 'F_sh', 'wallSun', - 'svfbu', 'svfveg', 'svfaveg']) - + tempTable = voxelTable.loc[ + ( + (voxelTable["ypos"] == woisxy[k, 2]) + & (voxelTable["xpos"] == woisxy[k, 1]) + & voxelTable["voxelHeight"] + == voxelHeight + ) + ].copy() + output_vars = np.array( + [ + "wallTemperature", + "K_in", + "L_in", + "wallTemperatureSolweigOld", + "Lwallsun", + "Lwallsh", + "Lrefl", + "Lveg", + "Lground", + "Lsky", + "esky", + "F_sh", + "wallSun", + "svfbu", + "svfveg", + "svfaveg", + ] + ) + counter = 0 for temp_var in output_vars: temp_out = tempTable[temp_var].to_numpy() @@ -60,28 +106,52 @@ def fillWallOfInterest(voxelTable, voxelHeight, woisxy, woiname, outputDir, i, Y temp_all = temp_out.copy() else: temp_all = np.concatenate([temp_all, temp_out]) - + wall_data = np.zeros((1, 7 + temp_all.shape[0])) # Part of file name (wallid), i.e. WOI_wallid.txt - woiwallId = voxelTable.loc[((voxelTable['ypos'] == woisxy[k, 2]) & (voxelTable['xpos'] == woisxy[k, 1])), 'wallId'].to_numpy()[0] + woiwallId = voxelTable.loc[ + ( + (voxelTable["ypos"] == woisxy[k, 2]) + & (voxelTable["xpos"] == woisxy[k, 1]) + ), + "wallId", + ].to_numpy()[0] # print('wall id = ' + str(woiwallId) + ' and direction = ' + str(woiname[k])) - data_out = outputDir + '/WOI_' + str(woiname[k]) + '_height' + str(voxelHeight) + '.txt' + data_out = ( + outputDir + + "/WOI_" + + str(woiname[k]) + + "_height" + + str(voxelHeight) + + ".txt" + ) if i == 0: - print('wall id = ' + str(woiwallId) + ' and direction = ' + str(woiname[k])) + print( + "wall id = " + + str(woiwallId) + + " and direction = " + + str(woiname[k]) + ) # Output file header - header = 'yyyy id it imin dectime Ta SVF ' - voxelHeader = '' + header = "yyyy id it imin dectime Ta SVF " + voxelHeader = "" for temp_header in output_vars: - header += ' ' + temp_header - #header = header + voxelHeader + header += " " + temp_header + # header = header + voxelHeader # Part of file name (wallid), i.e. WOI_wallid.txt # woiname = voxelTable.loc[((voxelTable['ypos'] == woisxy[k, 2]) & (voxelTable['xpos'] == woisxy[k, 1])), 'wallId'].to_numpy()[0] - woi_save = [] # + woi_save = [] # # data_out = outputDir + '/WOI_' + str(woiname) + '.txt' - np.savetxt(data_out, woi_save, delimiter=' ', header=header, comments='') + np.savetxt( + data_out, + woi_save, + delimiter=" ", + header=header, + comments="", + ) # Fill wall_data with variables - wall_data[0, 0] = YYYY[0][i] + wall_data[0, 0] = YYYY[0][i] wall_data[0, 1] = jday[0][i] wall_data[0, 2] = hours[i] wall_data[0, 3] = minu[i] @@ -93,8 +163,10 @@ def fillWallOfInterest(voxelTable, voxelHeight, woisxy, woiname, outputDir, i, Y # Num format for output file data # woi_numformat = '%d %d %d %d %.5f %.2f %.2f' + ' %.2f' * temp_wall.shape[0] - woi_numformat = '%d %d %d %d %.5f %.2f %.2f' + ' %.2f' * temp_all.shape[0] + woi_numformat = ( + "%d %d %d %d %.5f %.2f %.2f" + " %.2f" * temp_all.shape[0] + ) # Open file, add data, save - f_handle = open(data_out, 'ab') + f_handle = open(data_out, "ab") np.savetxt(f_handle, wall_data, fmt=woi_numformat) - f_handle.close() \ No newline at end of file + f_handle.close() diff --git a/functions/SOLWEIGpython/wall_cover.py b/functions/SOLWEIGpython/wall_cover.py index b302e00..da81e5c 100644 --- a/functions/SOLWEIGpython/wall_cover.py +++ b/functions/SOLWEIGpython/wall_cover.py @@ -1,12 +1,13 @@ import numpy as np + def get_wall_cover(voxelTable, lcgrid, dsm, lc_params): - '''Function to set thermal properties of wall used in surface temperature scheme of walls''' + """Function to set thermal properties of wall used in surface temperature scheme of walls""" # Y-position of wall pixel in raster - ypos = voxelTable['ypos'].to_numpy().astype(int) + ypos = voxelTable["ypos"].to_numpy().astype(int) # X-position of wall pixel in raster - xpos = voxelTable['xpos'].to_numpy().astype(int) + xpos = voxelTable["xpos"].to_numpy().astype(int) # Empty array to store wall code wallCode = np.zeros((voxelTable.shape[0])) # Empty array to store thermal effusivity @@ -18,15 +19,20 @@ def get_wall_cover(voxelTable, lcgrid, dsm, lc_params): # Empty array to store emissivity wallEmissivity = np.zeros((voxelTable.shape[0])) # Empty array to store wall thickness - wallThickness = np.zeros((voxelTable.shape[0])) + wallThickness = np.zeros((voxelTable.shape[0])) # Search kernel used to find wall code domain = np.array([[1, 1, 1], [1, 0, 1], [1, 1, 1]]) # Loop through all wall pixels in voxel table for i in range(voxelTable.shape[0]): # Temporary lc_grid based on kernel and wall y and x position - temp_lc = lcgrid[ypos[i]-1:ypos[i]+2, xpos[i]-1:xpos[i]+2] * domain + temp_lc = ( + lcgrid[ypos[i] - 1 : ypos[i] + 2, xpos[i] - 1 : xpos[i] + 2] + * domain + ) # Temporary dsm based on kernel and wall y and x position - temp_dsm = dsm[ypos[i]-1:ypos[i]+2, xpos[i]-1:xpos[i]+2] * domain + temp_dsm = ( + dsm[ypos[i] - 1 : ypos[i] + 2, xpos[i] - 1 : xpos[i] + 2] * domain + ) # Temporary code based on highest pixel in temp_dsm where temp_lc is a building temp_code = temp_lc[((temp_lc > 99) & (temp_dsm == temp_dsm.max()))] @@ -42,36 +48,48 @@ def get_wall_cover(voxelTable, lcgrid, dsm, lc_params): # Save wall code in array wallCode[i] = temp_code # Volumetric heat capacity - temp_Tc = lc_params['Specific_heat']['Value'][lc_params['Names']['Value'][str(temp_code)]] + temp_Tc = lc_params["Specific_heat"]["Value"][ + lc_params["Names"]["Value"][str(temp_code)] + ] # Thermal conductivity - temp_Tk = lc_params['Thermal_conductivity']['Value'][lc_params['Names']['Value'][str(temp_code)]] + temp_Tk = lc_params["Thermal_conductivity"]["Value"][ + lc_params["Names"]["Value"][str(temp_code)] + ] # Material density - temp_D = lc_params['Density']['Value'][lc_params['Names']['Value'][str(temp_code)]] + temp_D = lc_params["Density"]["Value"][ + lc_params["Names"]["Value"][str(temp_code)] + ] # Calculate thermal effusivity temp_Tu = np.sqrt(temp_Tc * temp_D * temp_Tk) # Calculate thermal diffusivity - temp_Td = temp_Tk/(temp_Tc*temp_D) + temp_Td = temp_Tk / (temp_Tc * temp_D) # Save thermal effusivity of wall pixel in array wallTu[i] = temp_Tu # Save thermal diffusivity of wall pixel in array wallTd[i] = temp_Td # Get wall albedo - temp_a = lc_params['Albedo']['Material']['Value'][lc_params['Names']['Value'][str(temp_code)]] + temp_a = lc_params["Albedo"]["Material"]["Value"][ + lc_params["Names"]["Value"][str(temp_code)] + ] # Save wall albedo wallAlbedo[i] = temp_a # Get wall emissivity - temp_e = lc_params['Emissivity']['Value'][lc_params['Names']['Value'][str(temp_code)]] + temp_e = lc_params["Emissivity"]["Value"][ + lc_params["Names"]["Value"][str(temp_code)] + ] # Save wall emissivity wallEmissivity[i] = temp_e # Get wall thickness - temp_wt = lc_params['Wall_thickness']['Value'][lc_params['Names']['Value'][str(temp_code)]] + temp_wt = lc_params["Wall_thickness"]["Value"][ + lc_params["Names"]["Value"][str(temp_code)] + ] # Save wall thickness wallThickness[i] = temp_wt - voxelTable['thermalEffusivity'] = wallTu - voxelTable['thermalDiffusivity'] = wallTd - voxelTable['wallAlbedo'] = wallAlbedo - voxelTable['wallEmissivity'] = wallEmissivity - voxelTable['wallThickness'] = wallThickness + voxelTable["thermalEffusivity"] = wallTu + voxelTable["thermalDiffusivity"] = wallTd + voxelTable["wallAlbedo"] = wallAlbedo + voxelTable["wallEmissivity"] = wallEmissivity + voxelTable["wallThickness"] = wallThickness return voxelTable diff --git a/functions/SOLWEIGpython/wall_surface_temperature.py b/functions/SOLWEIGpython/wall_surface_temperature.py index 77d1f82..b49bec8 100644 --- a/functions/SOLWEIGpython/wall_surface_temperature.py +++ b/functions/SOLWEIGpython/wall_surface_temperature.py @@ -8,66 +8,179 @@ # Stefan Boltzmans Constant SBC = 5.67051e-8 -def load_walls(voxelTable, solweig_parameters, wall_type, wallaspect, Ta, timeStep, albedo_b, emissivity_b, albedo_grid, landcover, lcgrid, dsm): - '''This function loads the voxel data created in sky view factor calculator and turns it into a Pandas DataFrame.''' - + +def load_walls( + voxelTable, + solweig_parameters, + wall_type, + wallaspect, + Ta, + timeStep, + albedo_b, + emissivity_b, + albedo_grid, + landcover, + lcgrid, + dsm, +): + """This function loads the voxel data created in sky view factor calculator and turns it into a Pandas DataFrame.""" + # Load data as Pandas DataFrame and add column names - voxelTable = pd.DataFrame(voxelTable, columns = ['voxelId', 'voxelHeight', 'wallHeight', 'wallHeight_exact', 'wallId', 'ypos', 'xpos', - 'SVF_height', 'SVF', 'SVF_fix', 'svfbu', 'svfveg', 'svfaveg']) - + voxelTable = pd.DataFrame( + voxelTable, + columns=[ + "voxelId", + "voxelHeight", + "wallHeight", + "wallHeight_exact", + "wallId", + "ypos", + "xpos", + "SVF_height", + "SVF", + "SVF_fix", + "svfbu", + "svfveg", + "svfaveg", + ], + ) + # Initiate/declare new columns used by SOLWEIG and parameterization scheme for wall surface temperatures - voxelTable['wallTemperature'] = Ta # Initial wall temperature is set to air temperature - voxelTable['timeStep'] = timeStep - + voxelTable["wallTemperature"] = ( + Ta # Initial wall temperature is set to air temperature + ) + voxelTable["timeStep"] = timeStep + # Add columns filled later - columns_to_add = ['SVF_ground', 'svfbu_at_ground', 'svfaveg_at_ground', 'wallAspect', 'wallEmissivity', 'wallThickness', - 'wallAlbedo', 'thermalEffusivity', 'thermalDiffusivity', 'groundAlbedo', 'wallShade', 'wallShadeHeight', - 'LongwaveRadiation', 'K_in', 'L_in', 'Lwallsun', 'Lwallsh', 'Lrefl', 'Lveg', 'Lground', 'Lsky', 'esky', 'voxelHeightMasl'] + columns_to_add = [ + "SVF_ground", + "svfbu_at_ground", + "svfaveg_at_ground", + "wallAspect", + "wallEmissivity", + "wallThickness", + "wallAlbedo", + "thermalEffusivity", + "thermalDiffusivity", + "groundAlbedo", + "wallShade", + "wallShadeHeight", + "LongwaveRadiation", + "K_in", + "L_in", + "Lwallsun", + "Lwallsh", + "Lrefl", + "Lveg", + "Lground", + "Lsky", + "esky", + "voxelHeightMasl", + ] for col in columns_to_add: voxelTable[col] = 0 - + # tmp = svf + svfveg - 1. # tmp[tmp < 0.] = 0. # %matlab crazyness around 0 # svfalfa = np.arcsin(np.exp((np.log((1. - tmp)) / 2.))) - tmp = voxelTable['SVF_fix'].to_numpy() + voxelTable['svfveg'].to_numpy() - 1. - tmp[tmp < 0.] = 0. - voxelTable['svfalfa'] = np.arcsin(np.exp((np.log((1. - tmp)) / 2.))) + tmp = ( + voxelTable["SVF_fix"].to_numpy() + + voxelTable["svfveg"].to_numpy() + - 1.0 + ) + tmp[tmp < 0.0] = 0.0 + voxelTable["svfalfa"] = np.arcsin(np.exp((np.log((1.0 - tmp)) / 2.0))) # Add wall aspect of each voxel # temp_table = np.column_stack([voxelTable['voxelId'].to_numpy(), voxelTable['ypos'].to_numpy(), voxelTable['xpos'].to_numpy()]) - temp_table = np.column_stack([voxelTable['wallId'].to_numpy(), voxelTable['ypos'].to_numpy(), voxelTable['xpos'].to_numpy()]) - temp_table = np.unique(temp_table, axis = 0) + temp_table = np.column_stack( + [ + voxelTable["wallId"].to_numpy(), + voxelTable["ypos"].to_numpy(), + voxelTable["xpos"].to_numpy(), + ] + ) + temp_table = np.unique(temp_table, axis=0) # Add wall aspect for i in np.arange(temp_table.shape[0]): - temp_aspect = wallaspect[temp_table[i,1].astype(int), temp_table[i,2].astype(int)] - voxelTable.loc[voxelTable['wallId'] == temp_table[i,0], 'wallAspect'] = temp_aspect - temp_building = voxelTable.loc[((voxelTable['wallId'] == temp_table[i,0]) & (voxelTable['voxelHeight'] == voxelTable['voxelHeight'].min())), 'svfbu'].copy().to_numpy()[0] - temp_veg = voxelTable.loc[((voxelTable['wallId'] == temp_table[i,0]) & (voxelTable['voxelHeight'] == voxelTable['voxelHeight'].min())), 'svfaveg'].copy().to_numpy()[0] - temp_albedo = albedo_grid[temp_table[i,1].astype(int), temp_table[i,2].astype(int)] - temp_dsm = dsm[temp_table[i,1].astype(int), temp_table[i,2].astype(int)] - - voxelTable.loc[voxelTable['wallId'] == temp_table[i,0], 'svfbu_at_ground'] = temp_building - voxelTable.loc[voxelTable['wallId'] == temp_table[i,0], 'svfaveg_at_ground'] = temp_veg - voxelTable.loc[voxelTable['wallId'] == temp_table[i,0], 'groundAlbedo'] = temp_albedo - voxelTable.loc[voxelTable['wallId'] == temp_table[i,0], 'voxelHeightMasl'] = voxelTable.loc[voxelTable['wallId'] == temp_table[i,0], 'voxelHeight'].to_numpy() + temp_dsm - + temp_aspect = wallaspect[ + temp_table[i, 1].astype(int), temp_table[i, 2].astype(int) + ] + voxelTable.loc[ + voxelTable["wallId"] == temp_table[i, 0], "wallAspect" + ] = temp_aspect + temp_building = ( + voxelTable.loc[ + ( + (voxelTable["wallId"] == temp_table[i, 0]) + & ( + voxelTable["voxelHeight"] + == voxelTable["voxelHeight"].min() + ) + ), + "svfbu", + ] + .copy() + .to_numpy()[0] + ) + temp_veg = ( + voxelTable.loc[ + ( + (voxelTable["wallId"] == temp_table[i, 0]) + & ( + voxelTable["voxelHeight"] + == voxelTable["voxelHeight"].min() + ) + ), + "svfaveg", + ] + .copy() + .to_numpy()[0] + ) + temp_albedo = albedo_grid[ + temp_table[i, 1].astype(int), temp_table[i, 2].astype(int) + ] + temp_dsm = dsm[ + temp_table[i, 1].astype(int), temp_table[i, 2].astype(int) + ] + + voxelTable.loc[ + voxelTable["wallId"] == temp_table[i, 0], "svfbu_at_ground" + ] = temp_building + voxelTable.loc[ + voxelTable["wallId"] == temp_table[i, 0], "svfaveg_at_ground" + ] = temp_veg + voxelTable.loc[ + voxelTable["wallId"] == temp_table[i, 0], "groundAlbedo" + ] = temp_albedo + voxelTable.loc[ + voxelTable["wallId"] == temp_table[i, 0], "voxelHeightMasl" + ] = ( + voxelTable.loc[ + voxelTable["wallId"] == temp_table[i, 0], "voxelHeight" + ].to_numpy() + + temp_dsm + ) + # Set voxelId to index - voxelTable = voxelTable.set_index('voxelId') + voxelTable = voxelTable.set_index("voxelId") # Calculate fractions # Non-sky fraction = (1 - svf at ground level) - 0.5 (ground surface seen from wall) - building_fraction = 1 - voxelTable['svfbu_at_ground'].to_numpy() - 0.5 - building_fraction[building_fraction < 0] = 0. - veg_fraction = 1 - voxelTable['svfaveg_at_ground'].to_numpy() - 0.5 - veg_fraction[veg_fraction < 0] = 0. - voxelTable['building_fraction'] = building_fraction - voxelTable['veg_fraction'] = veg_fraction - sky_fraction = voxelTable['SVF_fix'].to_numpy() + building_fraction = 1 - voxelTable["svfbu_at_ground"].to_numpy() - 0.5 + building_fraction[building_fraction < 0] = 0.0 + veg_fraction = 1 - voxelTable["svfaveg_at_ground"].to_numpy() - 0.5 + veg_fraction[veg_fraction < 0] = 0.0 + voxelTable["building_fraction"] = building_fraction + voxelTable["veg_fraction"] = veg_fraction + sky_fraction = voxelTable["SVF_fix"].to_numpy() ground_fraction = 1 - sky_fraction - building_fraction - veg_fraction - voxelTable['ground_fraction'] = ground_fraction + voxelTable["ground_fraction"] = ground_fraction - voxelTable['total_fraction'] = building_fraction + sky_fraction + ground_fraction + veg_fraction + voxelTable["total_fraction"] = ( + building_fraction + sky_fraction + ground_fraction + veg_fraction + ) # Set thermal effusivity according to wall type (either from GUI or from landcover) if landcover == 1: @@ -76,131 +189,188 @@ def load_walls(voxelTable, solweig_parameters, wall_type, wallaspect, Ta, timeSt unique_walls = unique_landcover[unique_landcover > 99].astype(int) # Get wall properties for wall surface temperature scheme if unique_walls.size > 1: - voxelTable = get_wall_cover(voxelTable, lcgrid, dsm, solweig_parameters) + voxelTable = get_wall_cover( + voxelTable, lcgrid, dsm, solweig_parameters + ) elif unique_walls.size == 1: # Specific heat capacity - wallTc = solweig_parameters['Specific_heat']['Value'][solweig_parameters['Names']['Value'][str(unique_walls[0])]] + wallTc = solweig_parameters["Specific_heat"]["Value"][ + solweig_parameters["Names"]["Value"][str(unique_walls[0])] + ] # Thermal conductivity - wallTk = solweig_parameters['Thermal_conductivity']['Value'][solweig_parameters['Names']['Value'][str(unique_walls[0])]] + wallTk = solweig_parameters["Thermal_conductivity"]["Value"][ + solweig_parameters["Names"]["Value"][str(unique_walls[0])] + ] # Material density - wallD = solweig_parameters['Density']['Value'][solweig_parameters['Names']['Value'][str(unique_walls[0])]] + wallD = solweig_parameters["Density"]["Value"][ + solweig_parameters["Names"]["Value"][str(unique_walls[0])] + ] # Thermal effusivity wallTu = np.sqrt(wallTc * wallD * wallTk) # Thermal diffusivity - wallTd = wallTk/(wallTc*wallD) + wallTd = wallTk / (wallTc * wallD) # Set thermal effusivity - voxelTable['thermalEffusivity'] = wallTu + voxelTable["thermalEffusivity"] = wallTu # Set thermal diffusivity - voxelTable['thermalDiffusivity'] = wallTd + voxelTable["thermalDiffusivity"] = wallTd # Set wall albedo - # voxelTable['wallAlbedo'] = solweig_parameters['Albedo']['Material']['Value'][solweig_parameters['Names']['Value'][str(unique_walls[0])]] - voxelTable['wallAlbedo'] = albedo_b + # voxelTable['wallAlbedo'] = solweig_parameters['Albedo']['Material']['Value'][solweig_parameters['Names']['Value'][str(unique_walls[0])]] + voxelTable["wallAlbedo"] = albedo_b # Set wall emissivity # voxelTable['wallEmissivity'] = solweig_parameters['Emissivity']['Value'][solweig_parameters['Names']['Value'][str(unique_walls[0])]] - voxelTable['wallEmissivity'] = emissivity_b + voxelTable["wallEmissivity"] = emissivity_b # Set thickness - voxelTable['wallThickness'] = solweig_parameters['Wall_thickness']['Value'][solweig_parameters['Names']['Value'][str(unique_walls[0])]] + voxelTable["wallThickness"] = solweig_parameters["Wall_thickness"][ + "Value" + ][solweig_parameters["Names"]["Value"][str(unique_walls[0])]] else: landcover = 0 - + if landcover == 0: # Specific heat capacity - wallTc = solweig_parameters['Specific_heat']['Value'][solweig_parameters['Names']['Value'][wall_type]] + wallTc = solweig_parameters["Specific_heat"]["Value"][ + solweig_parameters["Names"]["Value"][wall_type] + ] # Thermal conductivity - wallTk = solweig_parameters['Thermal_conductivity']['Value'][solweig_parameters['Names']['Value'][wall_type]] + wallTk = solweig_parameters["Thermal_conductivity"]["Value"][ + solweig_parameters["Names"]["Value"][wall_type] + ] # Material density - wallD = solweig_parameters['Density']['Value'][solweig_parameters['Names']['Value'][wall_type]] + wallD = solweig_parameters["Density"]["Value"][ + solweig_parameters["Names"]["Value"][wall_type] + ] # Thermal effusivity - wallTu = np.sqrt(wallTc * wallD * wallTk) + wallTu = np.sqrt(wallTc * wallD * wallTk) # Set thermal effusivity - voxelTable['thermalEffusivity'] = wallTu + voxelTable["thermalEffusivity"] = wallTu # Calculate thermal diffusivity - wallTd = wallTk/(wallTc*wallD) + wallTd = wallTk / (wallTc * wallD) # Set thermal diffusivity - voxelTable['thermalDiffusivity'] = wallTd + voxelTable["thermalDiffusivity"] = wallTd # Get wall albedo # voxelTable['wallAlbedo'] = solweig_parameters['Albedo']['Material']['Value'][solweig_parameters['Names']['Value'][wall_type]] - voxelTable['wallAlbedo'] = albedo_b + voxelTable["wallAlbedo"] = albedo_b # Get wall emissivity # voxelTable['wallEmissivity'] = solweig_parameters['Emissivity']['Value'][solweig_parameters['Names']['Value'][wall_type]] - voxelTable['wallEmissivity'] = emissivity_b + voxelTable["wallEmissivity"] = emissivity_b # Get wall thickness - voxelTable['wallThickness'] = solweig_parameters['Wall_thickness']['Value'][solweig_parameters['Names']['Value'][wall_type]] + voxelTable["wallThickness"] = solweig_parameters["Wall_thickness"][ + "Value" + ][solweig_parameters["Names"]["Value"][wall_type]] eqTime = True ### REMEMEBER TO TURN OFF FOR KOLUMBUS ### if eqTime: - voxelTable['timeStep'] = voxelTable['wallThickness'].to_numpy()**2/(np.pi**2 * voxelTable['thermalDiffusivity'].to_numpy()) + voxelTable["timeStep"] = voxelTable[ + "wallThickness" + ].to_numpy() ** 2 / ( + np.pi**2 * voxelTable["thermalDiffusivity"].to_numpy() + ) return voxelTable, wallaspect + def step_heating(q, e, t): - '''Function to calculate delta surface temperature based on heat flux (q), thermal effusivity (e) and time (t)''' - return (2*q)/e*np.sqrt(t/np.pi) + """Function to calculate delta surface temperature based on heat flux (q), thermal effusivity (e) and time (t)""" + return (2 * q) / e * np.sqrt(t / np.pi) + + +def surface_temperature_calc( + effusivity, t, Kin, Lin, Ta, wall_emissivity, Ts_previous +): + """Function to get surface temperature""" -def surface_temperature_calc(effusivity, t, Kin, Lin, Ta, wall_emissivity, Ts_previous): - '''Function to get surface temperature''' - dT = np.zeros((effusivity.shape[0])) Ts = np.zeros((effusivity.shape[0])) # Calculate heat flux based on Ts in previous time step and Kin, Lin and Ta from current time step - Lout_temp = wall_emissivity * SBC * (Ts_previous + 273.15)**4 + Lout_temp = wall_emissivity * SBC * (Ts_previous + 273.15) ** 4 # sensible_temp = 20 * (Ts_previous - Ta) - energy_in_temp = Kin + Lin - Lout_temp #- sensible_temp + energy_in_temp = Kin + Lin - Lout_temp # - sensible_temp # Calculate dT (Ts - Ta) with step wise heating dT = step_heating(energy_in_temp, effusivity, t) # Surface temperature Ts_current = Ta + dT # Estimate surface temperature with Ts of current timestep, i.e. not Ts_previous - Lout_temp = wall_emissivity * SBC * (Ts_current + 273.15)**4 - energy_in_temp = Kin + Lin - Lout_temp #- sensible_temp + Lout_temp = wall_emissivity * SBC * (Ts_current + 273.15) ** 4 + energy_in_temp = Kin + Lin - Lout_temp # - sensible_temp dT = step_heating(energy_in_temp, effusivity, t) # Surface temperature after second iteration Ts = Ta + dT - + return Ts, dT -def wall_surface_temperature(voxelTable, wallsh, altitude, azimuth, timeStep, K_direct, K_diff, K_down, Ldown, Lup, Ta, esky): - '''Wall surface temperature parameterization - - This parameterization scheme estimates a wall temperature based on the - incoming energy (short- and longwave radiation), outgoing longwave radiation and sensible heat.''' + +def wall_surface_temperature( + voxelTable, + wallsh, + altitude, + azimuth, + timeStep, + K_direct, + K_diff, + K_down, + Ldown, + Lup, + Ta, + esky, +): + """Wall surface temperature parameterization + + This parameterization scheme estimates a wall temperature based on the + incoming energy (short- and longwave radiation), outgoing longwave radiation and sensible heat. + """ # Stefan Boltzmans Constant SBC = 5.67051e-8 # Degrees fo radians conversion factor - deg2rad = np.pi/180 + deg2rad = np.pi / 180 # Estimate shadow on wall, i.e. how far up the wall that the shade stretches (or how far down the wall that the sun reaches) - voxelTable['wallShade'] = 0 # Starting value is zero, i.e. the entire wall is shaded + voxelTable["wallShade"] = ( + 0 # Starting value is zero, i.e. the entire wall is shaded + ) # If sun is above horizon, get wall shadow height on wall from wallsh calculated in shadowingfunction_wallheight_23/13 in Solweig_2022a_calc_forprocessing.py if altitude > 0: # Starting value of wallShadeHeight - voxelTable['wallShadeHeight'] = 0 + voxelTable["wallShadeHeight"] = 0 # Find number of unique walls in model domain - unique_walls = np.unique(voxelTable['wallId']) + unique_walls = np.unique(voxelTable["wallId"]) # Find wall shade height of each unique wall for unique_wall in unique_walls: # temp_sh is the shadow height of a wall - temp_sh = wallsh[voxelTable.loc[voxelTable.wallId == unique_wall, 'ypos'].to_numpy().astype(int)[0], voxelTable.loc[voxelTable.wallId == unique_wall, 'xpos'].to_numpy().astype(int)[0]] - + temp_sh = wallsh[ + voxelTable.loc[voxelTable.wallId == unique_wall, "ypos"] + .to_numpy() + .astype(int)[0], + voxelTable.loc[voxelTable.wallId == unique_wall, "xpos"] + .to_numpy() + .astype(int)[0], + ] + # Change to sunlit (1) for voxels that are above wall shade height - voxelTable.loc[(voxelTable['wallId'] == unique_wall) & (voxelTable['voxelHeight'] >= temp_sh) & (voxelTable['wallHeight_exact'] > temp_sh), 'wallShade'] = 1 + voxelTable.loc[ + (voxelTable["wallId"] == unique_wall) + & (voxelTable["voxelHeight"] >= temp_sh) + & (voxelTable["wallHeight_exact"] > temp_sh), + "wallShade", + ] = 1 # Add wall shade height to pandas dataframe - voxelTable.loc[(voxelTable['wallId'] == unique_wall), 'wallShadeHeight'] = temp_sh + voxelTable.loc[ + (voxelTable["wallId"] == unique_wall), "wallShadeHeight" + ] = temp_sh # If sun is below horizon, everything is in "shade" else: - voxelTable['wallShadeHeight'] = voxelTable['wallHeight_exact'] + voxelTable["wallShadeHeight"] = voxelTable["wallHeight_exact"] # Ldown and Lup for wall pixels used when estimating the amount of longwave received from surrounding surfaces (ground and reflected) Ldown_array = np.zeros((voxelTable.shape[0])) Lup_array = np.zeros((voxelTable.shape[0])) for idx in np.arange(voxelTable.shape[0]): - temp_y = voxelTable.iloc[idx]['ypos'].astype(int) - temp_x = voxelTable.iloc[idx]['xpos'].astype(int) + temp_y = voxelTable.iloc[idx]["ypos"].astype(int) + temp_x = voxelTable.iloc[idx]["xpos"].astype(int) Ldown_array[idx] = Ldown[temp_y, temp_x] Lup_array[idx] = Lup[temp_y, temp_x] @@ -208,93 +378,172 @@ def wall_surface_temperature(voxelTable, wallsh, altitude, azimuth, timeStep, K_ # If sun above horizon if altitude > 0: # Cylindric wedge based on building height angle from svf and solar zenith angle - F_sh = cylindric_wedge_voxel((90 - altitude) * deg2rad, voxelTable['svfalfa'].to_numpy()) # Fraction shadow on building walls based on sun alt and svf + F_sh = cylindric_wedge_voxel( + (90 - altitude) * deg2rad, voxelTable["svfalfa"].to_numpy() + ) # Fraction shadow on building walls based on sun alt and svf F_sh[np.isnan(F_sh)] = 0.5 # Only half of hemisphere visible from a wall, therefore the entire seen area can be sunlit, compared to an open space where only half can be sunlit - F_sh = 2. * F_sh - 1. #(cylindric_wedge scaled 0-1) + F_sh = 2.0 * F_sh - 1.0 # (cylindric_wedge scaled 0-1) # Fraction of sunlit and shaded building surfaces seen by a building wall based on its aspect and the azimuth of the sun - wallSun = np.abs(voxelTable['wallAspect'].to_numpy() - azimuth) - wallSun = wallSun/180. - wallSun[wallSun > 1.] = 2 - (wallSun[wallSun > 1.]) - wallSun = 0.2 + wallSun * 0.6 # Scaling wall shadows to avoid totally shaded and totally sunlit walls + wallSun = np.abs(voxelTable["wallAspect"].to_numpy() - azimuth) + wallSun = wallSun / 180.0 + wallSun[wallSun > 1.0] = 2 - (wallSun[wallSun > 1.0]) + wallSun = ( + 0.2 + wallSun * 0.6 + ) # Scaling wall shadows to avoid totally shaded and totally sunlit walls # Temperature in shade and sun based on mean values for all voxels, i.e. shade = mean of all shaded and sun = mean of all sunlit - ts_shade = voxelTable.loc[voxelTable['wallShade'] == 0, 'wallTemperature'].to_numpy().mean() - ts_sun = voxelTable.loc[voxelTable['wallShade'] == 1, 'wallTemperature'].to_numpy().mean() + ts_shade = ( + voxelTable.loc[voxelTable["wallShade"] == 0, "wallTemperature"] + .to_numpy() + .mean() + ) + ts_sun = ( + voxelTable.loc[voxelTable["wallShade"] == 1, "wallTemperature"] + .to_numpy() + .mean() + ) # Calculation of longwave radiation received by the voxels based on sunlit and shaded surroundings - Lwallsun = (SBC * voxelTable['wallEmissivity'].to_numpy() * ((ts_sun + 273.15)**4) * voxelTable['building_fraction'].to_numpy() * (1. - F_sh)) * wallSun # (1 - svf - 0.5) istället för viktwall? - Lwallsh = (SBC * voxelTable['wallEmissivity'].to_numpy() * ((ts_shade + 273.15)**4) * voxelTable['building_fraction'].to_numpy() * (1. - F_sh)) * (1 - wallSun) # (1 - svf - 0.5) istället för viktwall? - - Lwallsh += SBC * voxelTable['wallEmissivity'].to_numpy() * ((ts_shade + 273.15)**4) * voxelTable['building_fraction'].to_numpy() * F_sh # * 0.5 # (1 - svf - 0.5) istället för viktwall? + Lwallsun = ( + SBC + * voxelTable["wallEmissivity"].to_numpy() + * ((ts_sun + 273.15) ** 4) + * voxelTable["building_fraction"].to_numpy() + * (1.0 - F_sh) + ) * wallSun # (1 - svf - 0.5) istället för viktwall? + Lwallsh = ( + SBC + * voxelTable["wallEmissivity"].to_numpy() + * ((ts_shade + 273.15) ** 4) + * voxelTable["building_fraction"].to_numpy() + * (1.0 - F_sh) + ) * ( + 1 - wallSun + ) # (1 - svf - 0.5) istället för viktwall? + + Lwallsh += ( + SBC + * voxelTable["wallEmissivity"].to_numpy() + * ((ts_shade + 273.15) ** 4) + * voxelTable["building_fraction"].to_numpy() + * F_sh + ) # * 0.5 # (1 - svf - 0.5) istället för viktwall? # If sun below horizon else: - Lwallsun = 0. - ts_shade = voxelTable.loc[voxelTable['wallShade'] == 0, 'wallTemperature'].to_numpy().mean() - Lwallsh = SBC * voxelTable['wallEmissivity'].to_numpy() * (ts_shade + 273.15)**4 * voxelTable['building_fraction'].to_numpy() # * 0.5 # (1 - svf - 0.5) istället för viktwall? + Lwallsun = 0.0 + ts_shade = ( + voxelTable.loc[voxelTable["wallShade"] == 0, "wallTemperature"] + .to_numpy() + .mean() + ) + Lwallsh = ( + SBC + * voxelTable["wallEmissivity"].to_numpy() + * (ts_shade + 273.15) ** 4 + * voxelTable["building_fraction"].to_numpy() + ) # * 0.5 # (1 - svf - 0.5) istället för viktwall? # Received longwave radiation from vegetation - Lveg = SBC * voxelTable['wallEmissivity'].to_numpy() * (Ta + 273.15)**4 * voxelTable['veg_fraction'].to_numpy() # * 0.5 # (1 - svf - 0.5) istället för viktwall? + Lveg = ( + SBC + * voxelTable["wallEmissivity"].to_numpy() + * (Ta + 273.15) ** 4 + * voxelTable["veg_fraction"].to_numpy() + ) # * 0.5 # (1 - svf - 0.5) istället för viktwall? # Received longwave radiation from the sky - Lsky = SBC * esky * ((Ta + 273.15)**4) * voxelTable['SVF_fix'].to_numpy() # * 0.5 # svf istället för viktwall? + Lsky = ( + SBC * esky * ((Ta + 273.15) ** 4) * voxelTable["SVF_fix"].to_numpy() + ) # * 0.5 # svf istället för viktwall? # Received reflected longwave radiation - Lrefl = (1. - voxelTable['wallEmissivity'].to_numpy()) * (Ldown_array + Lup_array) * voxelTable['building_fraction'].to_numpy() # * 0.5 # (1 - svf - 0.5) istället för viktwall? + Lrefl = ( + (1.0 - voxelTable["wallEmissivity"].to_numpy()) + * (Ldown_array + Lup_array) + * voxelTable["building_fraction"].to_numpy() + ) # * 0.5 # (1 - svf - 0.5) istället för viktwall? # Received longwave radiation from ground - Lground = Lup_array * voxelTable['ground_fraction'].to_numpy() - + Lground = Lup_array * voxelTable["ground_fraction"].to_numpy() + # Total amount of longwave radiation received by a wall surface - L_in = Lwallsun + Lwallsh + Lrefl + Lveg + Lground + Lsky #TODO ground?? - - voxelTable['Lwallsun'] = Lwallsun - voxelTable['Lwallsh'] = Lwallsh - voxelTable['Lrefl'] = Lrefl - voxelTable['Lveg'] = Lveg - voxelTable['Lground'] = Lground - voxelTable['Lsky'] = Lsky - voxelTable['esky'] = esky + L_in = Lwallsun + Lwallsh + Lrefl + Lveg + Lground + Lsky # TODO ground?? + + voxelTable["Lwallsun"] = Lwallsun + voxelTable["Lwallsh"] = Lwallsh + voxelTable["Lrefl"] = Lrefl + voxelTable["Lveg"] = Lveg + voxelTable["Lground"] = Lground + voxelTable["Lsky"] = Lsky + voxelTable["esky"] = esky if altitude > 0: - voxelTable['F_sh'] = F_sh - voxelTable['wallSun'] = wallSun + voxelTable["F_sh"] = F_sh + voxelTable["wallSun"] = wallSun else: - voxelTable['F_sh'] = 0. - voxelTable['wallSun'] = 0. + voxelTable["F_sh"] = 0.0 + voxelTable["wallSun"] = 0.0 # Angle of incidence (wall aspect vs solar position (altitude and azimuth)) - sun_x = 1 * np.cos(math.radians(altitude)) * np.cos(math.radians(azimuth)) * np.cos(deg2rad * voxelTable['wallAspect'].to_numpy()) - sun_y = 1 * np.cos(math.radians(altitude)) * np.sin(math.radians(azimuth)) * np.sin(deg2rad * voxelTable['wallAspect'].to_numpy()) + sun_x = ( + 1 + * np.cos(math.radians(altitude)) + * np.cos(math.radians(azimuth)) + * np.cos(deg2rad * voxelTable["wallAspect"].to_numpy()) + ) + sun_y = ( + 1 + * np.cos(math.radians(altitude)) + * np.sin(math.radians(azimuth)) + * np.sin(deg2rad * voxelTable["wallAspect"].to_numpy()) + ) cf = sun_x + sun_y - cf[cf <= 0] = 0. + cf[cf <= 0] = 0.0 # If sun above horizon if altitude > 0: - K_in = (1 - voxelTable['wallAlbedo'].to_numpy()) * (K_direct * cf * voxelTable['wallShade'].to_numpy() + - K_diff * voxelTable['SVF_fix'].to_numpy() + - K_down * voxelTable['wallAlbedo'].to_numpy() * voxelTable['building_fraction'] + - (K_down * voxelTable['groundAlbedo']) * voxelTable['ground_fraction']) + K_in = (1 - voxelTable["wallAlbedo"].to_numpy()) * ( + K_direct * cf * voxelTable["wallShade"].to_numpy() + + K_diff * voxelTable["SVF_fix"].to_numpy() + + K_down + * voxelTable["wallAlbedo"].to_numpy() + * voxelTable["building_fraction"] + + (K_down * voxelTable["groundAlbedo"]) + * voxelTable["ground_fraction"] + ) # If sun below horizon else: - K_in = 0. + K_in = 0.0 - voxelTable['K_in'] = K_in - voxelTable['L_in'] = L_in + voxelTable["K_in"] = K_in + voxelTable["L_in"] = L_in # Calculate wall surface temperature with step-wise heating - Ts_previous = voxelTable['wallTemperature'].to_numpy() - if voxelTable['timeStep'].unique().size == 1: - timeStep = voxelTable.iloc[0]['timeStep'] + Ts_previous = voxelTable["wallTemperature"].to_numpy() + if voxelTable["timeStep"].unique().size == 1: + timeStep = voxelTable.iloc[0]["timeStep"] else: - timeStep = voxelTable['timeStep'].to_numpy() - Ts, dT = surface_temperature_calc(voxelTable['thermalEffusivity'].to_numpy(), timeStep, K_in, L_in, Ta, voxelTable['wallEmissivity'].to_numpy(), Ts_previous) - voxelTable['wallTemperature'] = Ts - + timeStep = voxelTable["timeStep"].to_numpy() + Ts, dT = surface_temperature_calc( + voxelTable["thermalEffusivity"].to_numpy(), + timeStep, + K_in, + L_in, + Ta, + voxelTable["wallEmissivity"].to_numpy(), + Ts_previous, + ) + voxelTable["wallTemperature"] = Ts + # Convert wall temperature to longwave radiation - voxelTable['LongwaveRadiation'] = ((voxelTable['wallEmissivity'] * SBC * ((voxelTable['wallTemperature'] + 273.15) ** 4)) / np.pi) + voxelTable["LongwaveRadiation"] = ( + voxelTable["wallEmissivity"] + * SBC + * ((voxelTable["wallTemperature"] + 273.15) ** 4) + ) / np.pi # Position of the sun for current timestep (can be removed?) - voxelTable['sunAltitude'] = altitude - voxelTable['sunAzimuth'] = azimuth + voxelTable["sunAltitude"] = altitude + voxelTable["sunAzimuth"] = azimuth - return voxelTable \ No newline at end of file + return voxelTable diff --git a/functions/SOLWEIGpython/wallsAsNetCDF.py b/functions/SOLWEIGpython/wallsAsNetCDF.py index 80cb2f7..2d2e91e 100644 --- a/functions/SOLWEIGpython/wallsAsNetCDF.py +++ b/functions/SOLWEIGpython/wallsAsNetCDF.py @@ -1,13 +1,16 @@ try: import xarray as xr import rioxarray -except: +except: pass import numpy as np -def walls_as_netcdf(voxelTable, rows, cols, timeSlots, iteration, dsm, raster_path, output_path): - '''This function creates a 4D NetCDF with wall temperatures and corresponding emitted longwave radiation''' + +def walls_as_netcdf( + voxelTable, rows, cols, timeSlots, iteration, dsm, raster_path, output_path +): + """This function creates a 4D NetCDF with wall temperatures and corresponding emitted longwave radiation""" # rows = number of rows (latitudinal position) # cols = number of columns (longitudinal position) # level = number of voxel levels (elevation position) @@ -16,26 +19,37 @@ def walls_as_netcdf(voxelTable, rows, cols, timeSlots, iteration, dsm, raster_pa # Highest number of voxels used to determine z/height level of NetCDF # levels = voxelTable.loc[voxelTable['voxelHeight'] == voxelTable['voxelHeight'].max(), 'voxelHeight'].to_numpy()[0].astype(int) - levels = voxelTable.loc[voxelTable['voxelHeightMasl'] == voxelTable['voxelHeightMasl'].max(), 'voxelHeightMasl'].to_numpy()[0].astype(int) + levels = ( + voxelTable.loc[ + voxelTable["voxelHeightMasl"] + == voxelTable["voxelHeightMasl"].max(), + "voxelHeightMasl", + ] + .to_numpy()[0] + .astype(int) + ) # Range of height levels - height_levels = np.arange(1, levels+1) + height_levels = np.arange(1, levels + 1) # Create empty numpy array to fill with wall temperatures from current time step wallTemperature = np.full((cols, rows, levels), np.nan, dtype=np.float32) # Add current time step wall temperature and longwave radiation to numpy array, which will be used to update the NetCDF. - #for y, x, z, wallTemp in zip(voxelTable['ypos'].astype(int), voxelTable['xpos'].astype(int), voxelTable['voxelHeight'].astype(int), voxelTable['wallTemperature'].astype(np.float32)): - for y, x, z, wallTemp in zip(voxelTable['ypos'].astype(int), voxelTable['xpos'].astype(int), voxelTable['voxelHeightMasl'].astype(int), voxelTable['wallTemperature'].astype(np.float32)): - wallTemperature[x, y, z-1] = wallTemp + # for y, x, z, wallTemp in zip(voxelTable['ypos'].astype(int), voxelTable['xpos'].astype(int), voxelTable['voxelHeight'].astype(int), voxelTable['wallTemperature'].astype(np.float32)): + for y, x, z, wallTemp in zip( + voxelTable["ypos"].astype(int), + voxelTable["xpos"].astype(int), + voxelTable["voxelHeightMasl"].astype(int), + voxelTable["wallTemperature"].astype(np.float32), + ): + wallTemperature[x, y, z - 1] = wallTemp chunkx = cols if cols < 100 else 100 chunky = rows if rows < 100 else 100 chunkz = levels if levels < 10 else 10 # NetCDF compression - comp = dict(zlib=True, - complevel=5, - chunksizes=(chunkx, chunky, chunkz, 1)) + comp = dict(zlib=True, complevel=5, chunksizes=(chunkx, chunky, chunkz, 1)) # If first time step, create an empty NetCDF to fill with values if iteration == 0: @@ -45,23 +59,22 @@ def walls_as_netcdf(voxelTable, rows, cols, timeSlots, iteration, dsm, raster_pa lat = raster_file.y.to_numpy() lon = raster_file.x.to_numpy() # temp_data = np.zeros((cols, rows, levels, timeSlots.shape[0])) - temp_data = np.full((cols, rows, levels, timeSlots.shape[0]), np.nan, dtype=np.float32) + temp_data = np.full( + (cols, rows, levels, timeSlots.shape[0]), np.nan, dtype=np.float32 + ) data_xr = xr.Dataset( data_vars=dict( - wall_temperature=(["lon", "lat", "height", "time"], temp_data), - ), + wall_temperature=(["lon", "lat", "height", "time"], temp_data), + ), coords=dict( - lon=lon, - lat=lat, - height=height_levels, - time=timeSlots + lon=lon, lat=lat, height=height_levels, time=timeSlots ), - attrs={"crs": raster_file.rio.crs.to_string()} + attrs={"crs": raster_file.rio.crs.to_string()}, ) - + # Update wall temperature and longwave radiation for current timestep (iteration) data_xr.wall_temperature[:, :, :, iteration] = wallTemperature - + encodings = {var: comp for var in data_xr.data_vars} # Save as NetCDF @@ -70,8 +83,8 @@ def walls_as_netcdf(voxelTable, rows, cols, timeSlots, iteration, dsm, raster_pa data_xr.close() # If not first time step, load existing NetCDF as an xarray dataset else: - with xr.open_dataset(output_path, mode='r+') as data_xr: + with xr.open_dataset(output_path, mode="r+") as data_xr: # Update wall temperature for current timestep (iteration) data_xr.wall_temperature[:, :, :, iteration] = wallTemperature # Save changes - data_xr.to_netcdf(output_path, engine="netcdf4", mode="a") \ No newline at end of file + data_xr.to_netcdf(output_path, engine="netcdf4", mode="a") diff --git a/functions/TreeGenerator/makevegdems.py b/functions/TreeGenerator/makevegdems.py index 89ace4b..7bd7fd0 100644 --- a/functions/TreeGenerator/makevegdems.py +++ b/functions/TreeGenerator/makevegdems.py @@ -1,8 +1,23 @@ from builtins import range import numpy as np + # import matplotlib.pylab as plt -def vegunitsgeneration(buildings, vegdem, vegdem2, ttype, height, trunk, dia, rowa, cola, sizex, sizey, scale): + +def vegunitsgeneration( + buildings, + vegdem, + vegdem2, + ttype, + height, + trunk, + dia, + rowa, + cola, + sizex, + sizey, + scale, +): # This function creates the shape of each vegetation unit and locates it a grid. vegdemtemp = np.zeros([sizey, sizex]) @@ -15,7 +30,7 @@ def vegunitsgeneration(buildings, vegdem, vegdem2, ttype, height, trunk, dia, ro trees = circle * (trees + trunk) treetrunkunder = trunk * circle - else: #ttype == 2 # desiduous tree + else: # ttype == 2 # desiduous tree canopy = 1 - ((1 - trees) ** 2) trees = canopy * (height - trunk) circle = imcircle(dia) @@ -55,27 +70,75 @@ def vegunitsgeneration(buildings, vegdem, vegdem2, ttype, height, trunk, dia, ro if colcutmin == 0: colcutmin = 0 - if row1 < 1 or col1 < 1 or row1 + rowmax - 1 > vegdem.shape[0] or col1 + rowmax - 1 > vegdem.shape[1]: + if ( + row1 < 1 + or col1 < 1 + or row1 + rowmax - 1 > vegdem.shape[0] + or col1 + rowmax - 1 > vegdem.shape[1] + ): # cutting tree at dem edge - if ((treetrunkunder.ndim > 1) and (trees.ndim > 1)): - trees = trees[int(rowcutmin):int(rowcutmax), int(colcutmin):int(colcutmax)] - treetrunkunder = treetrunkunder[int(rowcutmin): int(rowcutmax), int(colcutmin): int(colcutmax)] - vegdemtemp[int(rowmin):int(rowmin + trees.shape[0]), int(colmin):int(colmin + trees.shape[1])] = trees - vegdem2temp[int(rowmin):int(rowmin + trees.shape[0]), int(colmin):int(colmin + trees.shape[1])] = treetrunkunder + if (treetrunkunder.ndim > 1) and (trees.ndim > 1): + trees = trees[ + int(rowcutmin) : int(rowcutmax), + int(colcutmin) : int(colcutmax), + ] + treetrunkunder = treetrunkunder[ + int(rowcutmin) : int(rowcutmax), + int(colcutmin) : int(colcutmax), + ] + vegdemtemp[ + int(rowmin) : int(rowmin + trees.shape[0]), + int(colmin) : int(colmin + trees.shape[1]), + ] = trees + vegdem2temp[ + int(rowmin) : int(rowmin + trees.shape[0]), + int(colmin) : int(colmin + trees.shape[1]), + ] = treetrunkunder else: # no cutting of tree at dem edge - vegdemtemp[int(rowmin):int(rowmin + rowmax), int(colmin):int(colmin + colmax)] = trees - vegdem2temp[int(rowmin):int(rowmin + rowmax), int(colmin):int(colmin + colmax)] = treetrunkunder + vegdemtemp[ + int(rowmin) : int(rowmin + rowmax), + int(colmin) : int(colmin + colmax), + ] = trees + vegdem2temp[ + int(rowmin) : int(rowmin + rowmax), + int(colmin) : int(colmin + colmax), + ] = treetrunkunder if ttype == 0: # remove trees - if row1 < 1 or col1 < 1 or row1 + rowmax - 1 > vegdem.shape[0] or col1 + rowmax - 1 > vegdem.shape[1]: - vegdemtemp[int(rowmin):int(rowmin + trees.shape[0]), int(colmin):int(colmin + trees.shape[1])] = trees * 0 - vegdem2temp[int(rowmin):int(rowmin + trees.shape[0]), int(colmin):int(colmin + trees.shape[1])] = treetrunkunder * 0 + if ( + row1 < 1 + or col1 < 1 + or row1 + rowmax - 1 > vegdem.shape[0] + or col1 + rowmax - 1 > vegdem.shape[1] + ): + vegdemtemp[ + int(rowmin) : int(rowmin + trees.shape[0]), + int(colmin) : int(colmin + trees.shape[1]), + ] = ( + trees * 0 + ) + vegdem2temp[ + int(rowmin) : int(rowmin + trees.shape[0]), + int(colmin) : int(colmin + trees.shape[1]), + ] = ( + treetrunkunder * 0 + ) else: - vegdemtemp[int(rowmin):int(rowmin + rowmax), int(colmin):int(colmin + colmax)] = trees * 0 - vegdem2temp[int(rowmin):int(rowmin + rowmax), int(colmin):int(colmin + colmax)] = treetrunkunder * 0 + vegdemtemp[ + int(rowmin) : int(rowmin + rowmax), + int(colmin) : int(colmin + colmax), + ] = ( + trees * 0 + ) + vegdem2temp[ + int(rowmin) : int(rowmin + rowmax), + int(colmin) : int(colmin + colmax), + ] = ( + treetrunkunder * 0 + ) else: # add trees - + vegdem = np.fmax(vegdem, vegdemtemp) vegdem2temp[vegdemtemp == 0] = -1000 vegdem2[vegdem2 == 0] = -1000 @@ -95,10 +158,13 @@ def conifertree(dia): index = 1 while dia - index * 2 >= 1: if dia - index * 2 >= 2: - circle2 = imcircle(dia-index*2) + circle2 = imcircle(dia - index * 2) circle3 = np.zeros([circle.shape[0], circle.shape[0]]) - circle3[index:circle2.shape[0]+index, index:circle2.shape[0]+index] = circle2 - circle = circle+circle3 + circle3[ + index : circle2.shape[0] + index, + index : circle2.shape[0] + index, + ] = circle2 + circle = circle + circle3 index = index + 1 if dia - index * 2 == 1: @@ -107,7 +173,7 @@ def conifertree(dia): circle = circle + circle3 index = index + 1 - tree = circle/np.max(circle) + tree = circle / np.max(circle) return tree @@ -131,10 +197,10 @@ def imcircle(n): for i in range(0, int(height_45)): upward = i + 1 - 0.5 sine = upward / radius - cosine = np.sqrt(1 - sine ** 2) + cosine = np.sqrt(1 - sine**2) width[0, i] = np.ceil(cosine * radius) - array = width[0, 0:int(height_45)] - height_45 + array = width[0, 0 : int(height_45)] - height_45 for j in range(int(np.max(array)), int(np.min(array)) - 1, -1): width[0, int(height_45 + j - 1)] = np.max(np.where(array == j)) + 1 @@ -142,14 +208,18 @@ def imcircle(n): if np.min(width) == 0: ind = np.where(width == 0) index = ind[1] - width[0, index] = np.round(np.mean([width[0, index - 1], width[0, index + 1]])) + width[0, index] = np.round( + np.mean([width[0, index - 1], width[0, index + 1]]) + ) width = np.append(np.fliplr(width), width, axis=1) for k in range(0, int(DIAMETER)): - semicircle[k, 0:int(width[0, k])] = np.ones([1, int(width[0, k])]) + semicircle[k, 0 : int(width[0, k])] = np.ones( + [1, int(width[0, k])] + ) - y = np.append(np.fliplr(semicircle), semicircle,axis=1) + y = np.append(np.fliplr(semicircle), semicircle, axis=1) else: # odd n DIAMETER = n @@ -163,10 +233,10 @@ def imcircle(n): for i in range(0, int(height_45)): upward = i + 1 sine = upward / radius - cosine = np.sqrt(1 - sine ** 2) + cosine = np.sqrt(1 - sine**2) width[0, i] = np.ceil(cosine * radius - 0.5) - array = width[0, 0:int(height_45)] - height_45 + array = width[0, 0 : int(height_45)] - height_45 for j in range(int(np.max(array)), int(np.min(array)) - 1, -1): width[0, int(height_45 + j - 1)] = np.max(np.where(array == j)) + 1 @@ -174,17 +244,23 @@ def imcircle(n): if np.min(width) == 0: ind = np.where(width == 0) index = ind[1] - width[0, index] = np.round(np.mean([width[0, index - 1], width[0, index + 1]])) + width[0, index] = np.round( + np.mean([width[0, index - 1], width[0, index + 1]]) + ) - width1 = np.append(np.fliplr(width), np.ones([1, 1]) * np.max(width), axis=1) - width = np.append(width1, width,axis=1) + width1 = np.append( + np.fliplr(width), np.ones([1, 1]) * np.max(width), axis=1 + ) + width = np.append(width1, width, axis=1) for k in range(0, int(DIAMETER)): - semicircle[k, 0:int(width[0, k])] = np.ones([1, int(width[0, k])]) + semicircle[k, 0 : int(width[0, k])] = np.ones( + [1, int(width[0, k])] + ) - y = np.append(np.fliplr(semicircle), np.ones([int(DIAMETER), 1]), axis=1) + y = np.append( + np.fliplr(semicircle), np.ones([int(DIAMETER), 1]), axis=1 + ) y = np.append(y, semicircle, axis=1) return y - - diff --git a/functions/TreePlanter/SOLWEIG1D/Kside1D_veg_v2019a.py b/functions/TreePlanter/SOLWEIG1D/Kside1D_veg_v2019a.py index 7eff81c..c52dbe4 100644 --- a/functions/TreePlanter/SOLWEIG1D/Kside1D_veg_v2019a.py +++ b/functions/TreePlanter/SOLWEIG1D/Kside1D_veg_v2019a.py @@ -3,8 +3,36 @@ from ...SOLWEIGpython.Kvikt_veg import Kvikt_veg -def Kside_veg_v2019a(radI, radD, radG, shadow, svfS, svfW, svfN, svfE, svfEveg, svfSveg, svfWveg, svfNveg, azimuth, - altitude, psi, t, albedo, F_sh, KupE, KupS, KupW, KupN, cyl, lv, ani, diffsh, rows, cols): +def Kside_veg_v2019a( + radI, + radD, + radG, + shadow, + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + azimuth, + altitude, + psi, + t, + albedo, + F_sh, + KupE, + KupS, + KupW, + KupN, + cyl, + lv, + ani, + diffsh, + rows, + cols, +): # New reflection equation 2012-05-25 vikttot = 4.4897 aziE = azimuth + t @@ -23,19 +51,39 @@ def Kside_veg_v2019a(radI, radD, radG, shadow, svfS, svfW, svfN, svfE, svfEveg, KnorthI = 0 else: # Kside with weights ### if azimuth > (360 - t) or azimuth <= (180 - t): - KeastI = radI * shadow * np.cos(altitude * deg2rad) * np.sin(aziE * deg2rad) + KeastI = ( + radI + * shadow + * np.cos(altitude * deg2rad) + * np.sin(aziE * deg2rad) + ) else: KeastI = 0 if azimuth > (90 - t) and azimuth <= (270 - t): - KsouthI = radI * shadow * np.cos(altitude * deg2rad) * np.sin(aziS * deg2rad) + KsouthI = ( + radI + * shadow + * np.cos(altitude * deg2rad) + * np.sin(aziS * deg2rad) + ) else: KsouthI = 0 if azimuth > (180 - t) and azimuth <= (360 - t): - KwestI = radI * shadow * np.cos(altitude * deg2rad) * np.sin(aziW * deg2rad) + KwestI = ( + radI + * shadow + * np.cos(altitude * deg2rad) + * np.sin(aziW * deg2rad) + ) else: KwestI = 0 if azimuth <= (90 - t) or azimuth > (270 - t): - KnorthI = radI * shadow * np.cos(altitude * deg2rad) * np.sin(aziN * deg2rad) + KnorthI = ( + radI + * shadow + * np.cos(altitude * deg2rad) + * np.sin(aziN * deg2rad) + ) else: KnorthI = 0 @@ -43,16 +91,16 @@ def Kside_veg_v2019a(radI, radD, radG, shadow, svfS, svfW, svfN, svfE, svfEveg, ### Diffuse and reflected radiation ### [viktveg, viktwall] = Kvikt_veg(svfE, svfEveg, vikttot) - svfviktbuvegE = (viktwall + (viktveg) * (1 - psi)) + svfviktbuvegE = viktwall + (viktveg) * (1 - psi) [viktveg, viktwall] = Kvikt_veg(svfS, svfSveg, vikttot) - svfviktbuvegS = (viktwall + (viktveg) * (1 - psi)) + svfviktbuvegS = viktwall + (viktveg) * (1 - psi) [viktveg, viktwall] = Kvikt_veg(svfW, svfWveg, vikttot) - svfviktbuvegW = (viktwall + (viktveg) * (1 - psi)) + svfviktbuvegW = viktwall + (viktveg) * (1 - psi) [viktveg, viktwall] = Kvikt_veg(svfN, svfNveg, vikttot) - svfviktbuvegN = (viktwall + (viktveg) * (1 - psi)) + svfviktbuvegN = viktwall + (viktveg) * (1 - psi) ### Anisotropic Diffuse Radiation after Perez et al. 1993 ### if ani == 1: @@ -79,82 +127,173 @@ def Kside_veg_v2019a(radI, radD, radG, shadow, svfS, svfW, svfN, svfE, svfEveg, elif ix == 144: aziDel = 360 - phiVar[ix] = (aziDel * deg2rad) * (np.sin((aniAlt[ix] + 6) * deg2rad) - np.sin( - (aniAlt[ix] - 6) * deg2rad)) # Solid angle / Steradian + phiVar[ix] = (aziDel * deg2rad) * ( + np.sin((aniAlt[ix] + 6) * deg2rad) + - np.sin((aniAlt[ix] - 6) * deg2rad) + ) # Solid angle / Steradian radTot = radTot + ( - aniLum[ix] * phiVar[ix] * np.sin(aniAlt[ix] * deg2rad)) # Radiance fraction normalization + aniLum[ix] * phiVar[ix] * np.sin(aniAlt[ix] * deg2rad) + ) # Radiance fraction normalization lumChi = (aniLum * radD) / radTot # Radiance fraction normalization if cyl == 1: for idx in range(0, 145): - anglIncC = np.cos(aniAlt[idx] * deg2rad) * np.cos(0) * np.sin(np.pi / 2) + np.sin( - aniAlt[idx] * deg2rad) * np.cos( - np.pi / 2) # Angle of incidence, np.cos(0) because cylinder - always perpendicular - KsideD = KsideD + diffsh[idx] * lumChi[idx] * anglIncC * phiVar[idx] # Diffuse vertical radiation - Keast = (albedo * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) + KupE) * 0.5 - Ksouth = (albedo * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) + KupS) * 0.5 - Kwest = (albedo * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + KupW) * 0.5 - Knorth = (albedo * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + KupN) * 0.5 + anglIncC = np.cos(aniAlt[idx] * deg2rad) * np.cos(0) * np.sin( + np.pi / 2 + ) + np.sin(aniAlt[idx] * deg2rad) * np.cos( + np.pi / 2 + ) # Angle of incidence, np.cos(0) because cylinder - always perpendicular + KsideD = ( + KsideD + diffsh[idx] * lumChi[idx] * anglIncC * phiVar[idx] + ) # Diffuse vertical radiation + Keast = ( + albedo * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) + + KupE + ) * 0.5 + Ksouth = ( + albedo * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) + + KupS + ) * 0.5 + Kwest = ( + albedo * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + + KupW + ) * 0.5 + Knorth = ( + albedo * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + + KupN + ) * 0.5 else: # Box - diffRadE = np.zeros((rows, cols)); - diffRadS = np.zeros((rows, cols)); - diffRadW = np.zeros((rows, cols)); + diffRadE = np.zeros((rows, cols)) + diffRadS = np.zeros((rows, cols)) + diffRadW = np.zeros((rows, cols)) diffRadN = np.zeros((rows, cols)) for idx in range(0, 145): if aniAzi[idx] <= (180): - anglIncE = np.cos(aniAlt[idx] * deg2rad) * np.cos((90 - aniAzi[idx]) * deg2rad) * np.sin( - np.pi / 2) + np.sin( - aniAlt[idx] * deg2rad) * np.cos(np.pi / 2) - diffRadE = diffRadE + diffsh[idx] * lumChi[idx] * anglIncE * phiVar[idx] # * 0.5 + anglIncE = np.cos(aniAlt[idx] * deg2rad) * np.cos( + (90 - aniAzi[idx]) * deg2rad + ) * np.sin(np.pi / 2) + np.sin( + aniAlt[idx] * deg2rad + ) * np.cos( + np.pi / 2 + ) + diffRadE = ( + diffRadE + + diffsh[idx] * lumChi[idx] * anglIncE * phiVar[idx] + ) # * 0.5 if aniAzi[idx] > (90) and aniAzi[idx] <= (270): - anglIncS = np.cos(aniAlt[idx] * deg2rad) * np.cos((180 - aniAzi[idx]) * deg2rad) * np.sin( - np.pi / 2) + np.sin( - aniAlt[idx] * deg2rad) * np.cos(np.pi / 2) - diffRadS = diffRadS + diffsh[idx] * lumChi[idx] * anglIncS * phiVar[idx] # * 0.5 + anglIncS = np.cos(aniAlt[idx] * deg2rad) * np.cos( + (180 - aniAzi[idx]) * deg2rad + ) * np.sin(np.pi / 2) + np.sin( + aniAlt[idx] * deg2rad + ) * np.cos( + np.pi / 2 + ) + diffRadS = ( + diffRadS + + diffsh[idx] * lumChi[idx] * anglIncS * phiVar[idx] + ) # * 0.5 if aniAzi[idx] > (180) and aniAzi[idx] <= (360): - anglIncW = np.cos(aniAlt[idx] * deg2rad) * np.cos((270 - aniAzi[idx]) * deg2rad) * np.sin( - np.pi / 2) + np.sin( - aniAlt[idx] * deg2rad) * np.cos(np.pi / 2) - diffRadW = diffRadW + diffsh[idx] * lumChi[idx] * anglIncW * phiVar[idx] # * 0.5 + anglIncW = np.cos(aniAlt[idx] * deg2rad) * np.cos( + (270 - aniAzi[idx]) * deg2rad + ) * np.sin(np.pi / 2) + np.sin( + aniAlt[idx] * deg2rad + ) * np.cos( + np.pi / 2 + ) + diffRadW = ( + diffRadW + + diffsh[idx] * lumChi[idx] * anglIncW * phiVar[idx] + ) # * 0.5 if aniAzi[idx] > (270) or aniAzi[idx] <= (90): - anglIncN = np.cos(aniAlt[idx] * deg2rad) * np.cos((0 - aniAzi[idx]) * deg2rad) * np.sin( - np.pi / 2) + np.sin( - aniAlt[idx] * deg2rad) * np.cos(np.pi / 2) - diffRadN = diffRadN + diffsh[idx] * lumChi[idx] * anglIncN * phiVar[idx] # * 0.5 + anglIncN = np.cos(aniAlt[idx] * deg2rad) * np.cos( + (0 - aniAzi[idx]) * deg2rad + ) * np.sin(np.pi / 2) + np.sin( + aniAlt[idx] * deg2rad + ) * np.cos( + np.pi / 2 + ) + diffRadN = ( + diffRadN + + diffsh[idx] * lumChi[idx] * anglIncN * phiVar[idx] + ) # * 0.5 - KeastDG = diffRadE + (albedo * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) + KupE) * 0.5 + KeastDG = ( + diffRadE + + ( + albedo + * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) + + KupE + ) + * 0.5 + ) Keast = KeastI + KeastDG - KsouthDG = diffRadS + (albedo * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) + KupS) * 0.5 + KsouthDG = ( + diffRadS + + ( + albedo + * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) + + KupS + ) + * 0.5 + ) Ksouth = KsouthI + KsouthDG - KwestDG = diffRadW + (albedo * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + KupW) * 0.5 + KwestDG = ( + diffRadW + + ( + albedo + * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + + KupW + ) + * 0.5 + ) Kwest = KwestI + KwestDG - KnorthDG = diffRadN + (albedo * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + KupN) * 0.5 + KnorthDG = ( + diffRadN + + ( + albedo + * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + + KupN + ) + * 0.5 + ) Knorth = KnorthI + KnorthDG else: - KeastDG = (radD * (1 - svfviktbuvegE) + albedo * ( - svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) + KupE) * 0.5 + KeastDG = ( + radD * (1 - svfviktbuvegE) + + albedo * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) + + KupE + ) * 0.5 Keast = KeastI + KeastDG - KsouthDG = (radD * (1 - svfviktbuvegS) + albedo * ( - svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) + KupS) * 0.5 + KsouthDG = ( + radD * (1 - svfviktbuvegS) + + albedo * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) + + KupS + ) * 0.5 Ksouth = KsouthI + KsouthDG - KwestDG = (radD * (1 - svfviktbuvegW) + albedo * ( - svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + KupW) * 0.5 + KwestDG = ( + radD * (1 - svfviktbuvegW) + + albedo * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + + KupW + ) * 0.5 Kwest = KwestI + KwestDG - KnorthDG = (radD * (1 - svfviktbuvegN) + albedo * ( - svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + KupN) * 0.5 + KnorthDG = ( + radD * (1 - svfviktbuvegN) + + albedo * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + + KupN + ) * 0.5 Knorth = KnorthI + KnorthDG - return Keast, Ksouth, Kwest, Knorth, KsideI, KsideD \ No newline at end of file + return Keast, Ksouth, Kwest, Knorth, KsideI, KsideD diff --git a/functions/TreePlanter/SOLWEIG1D/SOLWEIG1D_2023a.py b/functions/TreePlanter/SOLWEIG1D/SOLWEIG1D_2023a.py index c4cf426..a91a45b 100644 --- a/functions/TreePlanter/SOLWEIG1D/SOLWEIG1D_2023a.py +++ b/functions/TreePlanter/SOLWEIG1D/SOLWEIG1D_2023a.py @@ -1,18 +1,26 @@ import numpy as np import os -from ....util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import clearnessindex_2013b -from ....util.SEBESOLWEIGCommonFiles import Solweig_v2015_metdata_noload as metload +from ....util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import ( + clearnessindex_2013b, +) +from ....util.SEBESOLWEIGCommonFiles import ( + Solweig_v2015_metdata_noload as metload, +) + # from ..SOLWEIG1D import Solweig1D_2019a_calc as so from ..SOLWEIG1D import Solweig1D_2023a_calc as so from ....util.SEBESOLWEIGCommonFiles.create_patches import create_patches from ...SOLWEIGpython.CirclePlotBar import PolarBarPlot -def tmrt_1d_fun(metfilepath,infolder,tau,lon,lat,dsm,r_range,outputDir): - + +def tmrt_1d_fun(metfilepath, infolder, tau, lon, lat, dsm, r_range, outputDir): + # Load settings from SOLWEIG # settingsHeader = 'UTC, posture, onlyglobal, landcover, anisotropic, cylinder, albedo_walls, albedo_ground, emissivity_walls, emissivity_ground, absK, absL, elevation' - settingsSolweig = np.loadtxt(infolder + '/treeplantersettings.txt', skiprows=1, delimiter=' ') + settingsSolweig = np.loadtxt( + infolder + "/treeplantersettings.txt", skiprows=1, delimiter=" " + ) UTC = int(settingsSolweig[0]) pos = int(settingsSolweig[1]) onlyglobal = int(settingsSolweig[2]) @@ -37,20 +45,20 @@ def tmrt_1d_fun(metfilepath,infolder,tau,lon,lat,dsm,r_range,outputDir): cyl = 1 # Degrees to radians - deg2rad = np.pi/180 + deg2rad = np.pi / 180 useveg = 1 # 1 if vegetation should be considered - sh = 1. # 0 if shadowed by building - vegsh = 0. # 0 if shadowed by tree + sh = 1.0 # 0 if shadowed by building + vegsh = 0.0 # 0 if shadowed by tree svf = 0.7 if useveg == 1: svfveg = 0.4 svfaveg = 0.5 trans = tau else: - svfveg = 1. - svfaveg = 1. - trans = 1. + svfveg = 1.0 + svfaveg = 1.0 + trans = 1.0 # program start if pos == 0: @@ -65,9 +73,9 @@ def tmrt_1d_fun(metfilepath,infolder,tau,lon,lat,dsm,r_range,outputDir): Fcyl = 0.20 if metfile == 1: - met = np.loadtxt(metfilepath, skiprows=1, delimiter=' ') + met = np.loadtxt(metfilepath, skiprows=1, delimiter=" ") else: - met = np.zeros((1, 24)) - 999. + met = np.zeros((1, 24)) - 999.0 year = 2011 month = 6 day = 6 @@ -90,13 +98,13 @@ def tmrt_1d_fun(metfilepath,infolder,tau,lon,lat,dsm,r_range,outputDir): else: dayspermonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] - doy = np.sum(dayspermonth[0:month - 1]) + day + doy = np.sum(dayspermonth[0 : month - 1]) + day - Ta = 25. - RH = 50. - radG = 880. - radD = 150. - radI = 950. + Ta = 25.0 + RH = 50.0 + radG = 880.0 + radD = 150.0 + radI = 950.0 met[0, 0] = year met[0, 1] = doy @@ -108,11 +116,12 @@ def tmrt_1d_fun(metfilepath,infolder,tau,lon,lat,dsm,r_range,outputDir): met[0, 21] = radD met[0, 22] = radI - location = {'longitude': lon, 'latitude': lat, 'altitude': alt} - YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax = metload.Solweig_2015a_metdata_noload(met, location, - UTC) + location = {"longitude": lon, "latitude": lat, "altitude": alt} + YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax = ( + metload.Solweig_2015a_metdata_noload(met, location, UTC) + ) - svfalfa = np.arcsin(np.exp((np.log((1. - svf)) / 2.))) + svfalfa = np.arcsin(np.exp((np.log((1.0 - svf)) / 2.0))) dectime_new = np.zeros((dectime.shape[0], 1)) dec_doy = int(dectime[0]) @@ -134,7 +143,10 @@ def tmrt_1d_fun(metfilepath,infolder,tau,lon,lat,dsm,r_range,outputDir): amaxvalue = dsm.max() - dsm.min() # load landcover file - sitein = os.path.dirname(os.path.abspath(__file__)) + "/landcoverclasses_2018a_orig.txt" + sitein = ( + os.path.dirname(os.path.abspath(__file__)) + + "/landcoverclasses_2018a_orig.txt" + ) f = open(sitein) lin = f.readlines() lc_class = np.zeros((lin.__len__() - 1, 6)) @@ -161,7 +173,7 @@ def tmrt_1d_fun(metfilepath,infolder,tau,lon,lat,dsm,r_range,outputDir): TmaxLST_wall = lc_class[wall_pos, 5] # If metfile starts at night - CI = 1. + CI = 1.0 if ani == 1: # Always use 153 patches @@ -178,12 +190,20 @@ def tmrt_1d_fun(metfilepath,infolder,tau,lon,lat,dsm,r_range,outputDir): for i in range(skyvaultalt.shape[0]): # If there are more than one patch in a band if skyalt_c[skyalt == skyvaultalt[i]] > 1: - steradian[i] = ((360 / skyalt_c[skyalt == skyvaultalt[i]]) * deg2rad) * (np.sin((skyvaultalt[i] + skyvaultalt[0]) * deg2rad) \ - - np.sin((skyvaultalt[i] - skyvaultalt[0]) * deg2rad)) + steradian[i] = ( + (360 / skyalt_c[skyalt == skyvaultalt[i]]) * deg2rad + ) * ( + np.sin((skyvaultalt[i] + skyvaultalt[0]) * deg2rad) + - np.sin((skyvaultalt[i] - skyvaultalt[0]) * deg2rad) + ) # If there is only one patch in band, i.e. 90 degrees else: - steradian[i] = ((360 / skyalt_c[skyalt == skyvaultalt[i]]) * deg2rad) * (np.sin((skyvaultalt[i]) * deg2rad) \ - - np.sin((skyvaultalt[i-1] + skyvaultalt[0]) * deg2rad)) + steradian[i] = ( + (360 / skyalt_c[skyalt == skyvaultalt[i]]) * deg2rad + ) * ( + np.sin((skyvaultalt[i]) * deg2rad) + - np.sin((skyvaultalt[i - 1] + skyvaultalt[0]) * deg2rad) + ) diffsh = np.zeros((skyvaultalt.shape[0])) @@ -198,22 +218,22 @@ def tmrt_1d_fun(metfilepath,infolder,tau,lon,lat,dsm,r_range,outputDir): vegp = np.bool_(vegp) buip = np.bool_(buip) - skyp = ((vegp == False) & (buip == False)) + skyp = (vegp == False) & (buip == False) skyp = np.bool_(skyp) - patch_svf = (np.sum(steradian[skyp]) / np.sum(steradian)) + patch_svf = np.sum(steradian[skyp]) / np.sum(steradian) svf = patch_svf.copy() # asvf to calculate sunlit and shaded patches asvf = np.arccos(np.sqrt(patch_svf)) - svfalfa = np.arcsin(np.exp((np.log((1. - svf)) / 2.))) + svfalfa = np.arcsin(np.exp((np.log((1.0 - svf)) / 2.0))) # Create a "sky view image" from patches Lsky_patch_characteristics = np.zeros((skyvaultalt.shape[0], 3)) - Lsky_patch_characteristics[:,0] = skyvaultalt - Lsky_patch_characteristics[:,1] = skyvaultazi + Lsky_patch_characteristics[:, 0] = skyvaultalt + Lsky_patch_characteristics[:, 1] = skyvaultazi Lsky_patch_characteristics[vegp, 2] = 2.5 Lsky_patch_characteristics[buip, 2] = 4.5 Lsky_patch_characteristics[skyp, 2] = 1.8 @@ -243,26 +263,92 @@ def tmrt_1d_fun(metfilepath,infolder,tau,lon,lat,dsm,r_range,outputDir): alt = altitude[0][daylines] alt2 = np.where(alt > 1) rise = alt2[0][0] - [_, CI, _, _, _] = clearnessindex_2013b(zen[0, i + rise + 1], jday[0, i + rise + 1], - Ta[i + rise + 1], - RH[i + rise + 1] / 100., radG[i + rise + 1], location, - P[i + rise + 1]) + [_, CI, _, _, _] = clearnessindex_2013b( + zen[0, i + rise + 1], + jday[0, i + rise + 1], + Ta[i + rise + 1], + RH[i + rise + 1] / 100.0, + radG[i + rise + 1], + location, + P[i + rise + 1], + ) if (CI > 1) or (CI == np.inf): CI = 1 - Tmrt, Kdown, Kup, Ldown, Lup, Tg, ea, esky, I0, CI, Keast, Ksouth, Kwest, Knorth, Least, Lsouth, Lwest, \ - Lnorth, KsideI, radIo, radDo, shadow1d = so.Solweig1D_2019a_calc(svf, svfveg, svfaveg, sh, vegsh, albedo_b, - absK, absL, ewall, - Fside, Fup, Fcyl, - altitude[0][i], azimuth[0][i], zen[0][i], - jday[0][i], - onlyglobal, location, dectime[i], altmax[0][i], - cyl, elvis, - Ta[i], RH[i], radG[i], radD[i], radI[i], P[i], - Twater, TgK, Tstart, albedo_g, eground, - TgK_wall, Tstart_wall, - TmaxLST, TmaxLST_wall, svfalfa, CI, ani, - diffsh, trans, patch_option, skyp, buip, vegp, asvf, np.copy(Lsky_patch_characteristics), steradian) + ( + Tmrt, + Kdown, + Kup, + Ldown, + Lup, + Tg, + ea, + esky, + I0, + CI, + Keast, + Ksouth, + Kwest, + Knorth, + Least, + Lsouth, + Lwest, + Lnorth, + KsideI, + radIo, + radDo, + shadow1d, + ) = so.Solweig1D_2019a_calc( + svf, + svfveg, + svfaveg, + sh, + vegsh, + albedo_b, + absK, + absL, + ewall, + Fside, + Fup, + Fcyl, + altitude[0][i], + azimuth[0][i], + zen[0][i], + jday[0][i], + onlyglobal, + location, + dectime[i], + altmax[0][i], + cyl, + elvis, + Ta[i], + RH[i], + radG[i], + radD[i], + radI[i], + P[i], + Twater, + TgK, + Tstart, + albedo_g, + eground, + TgK_wall, + Tstart_wall, + TmaxLST, + TmaxLST_wall, + svfalfa, + CI, + ani, + diffsh, + trans, + patch_option, + skyp, + buip, + vegp, + asvf, + np.copy(Lsky_patch_characteristics), + steradian, + ) tmrt_1d[i_c, 0] = Tmrt tmrt_1d[i_c, 1] = hours[i] diff --git a/functions/TreePlanter/SOLWEIG1D/SOLWEIG_1D.py b/functions/TreePlanter/SOLWEIG1D/SOLWEIG_1D.py index 9a87fa3..99ae37e 100644 --- a/functions/TreePlanter/SOLWEIG1D/SOLWEIG_1D.py +++ b/functions/TreePlanter/SOLWEIG1D/SOLWEIG_1D.py @@ -1,15 +1,22 @@ import numpy as np import os -from ....util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import clearnessindex_2013b -from ....util.SEBESOLWEIGCommonFiles import Solweig_v2015_metdata_noload as metload +from ....util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import ( + clearnessindex_2013b, +) +from ....util.SEBESOLWEIGCommonFiles import ( + Solweig_v2015_metdata_noload as metload, +) from ..SOLWEIG1D import Solweig1D_2019a_calc as so from ....util.SEBESOLWEIGCommonFiles.create_patches import create_patches -def tmrt_1d_fun(metfilepath,infolder,tau,lon,lat,dsm,r_range): - + +def tmrt_1d_fun(metfilepath, infolder, tau, lon, lat, dsm, r_range): + # Load settings from SOLWEIG # settingsHeader = 'UTC, posture, onlyglobal, landcover, anisotropic, cylinder, albedo_walls, albedo_ground, emissivity_walls, emissivity_ground, absK, absL, elevation' - settingsSolweig = np.loadtxt(infolder + '/treeplantersettings.txt', skiprows=1, delimiter=' ') + settingsSolweig = np.loadtxt( + infolder + "/treeplantersettings.txt", skiprows=1, delimiter=" " + ) UTC = int(settingsSolweig[0]) pos = int(settingsSolweig[1]) onlyglobal = int(settingsSolweig[2]) @@ -41,17 +48,17 @@ def tmrt_1d_fun(metfilepath,infolder,tau,lon,lat,dsm,r_range): # absL = 0.98 useveg = 1 # 1 if vegetation should be considered - sh = 1. # 0 if shadowed by building - vegsh = 0. # 0 if shadowed by tree + sh = 1.0 # 0 if shadowed by building + vegsh = 0.0 # 0 if shadowed by tree svf = 0.6 if useveg == 1: svfveg = 0.8 svfaveg = 0.9 trans = tau else: - svfveg = 1. - svfaveg = 1. - trans = 1. + svfveg = 1.0 + svfaveg = 1.0 + trans = 1.0 # program start if pos == 0: @@ -66,9 +73,9 @@ def tmrt_1d_fun(metfilepath,infolder,tau,lon,lat,dsm,r_range): Fcyl = 0.20 if metfile == 1: - met = np.loadtxt(metfilepath, skiprows=1, delimiter=' ') + met = np.loadtxt(metfilepath, skiprows=1, delimiter=" ") else: - met = np.zeros((1, 24)) - 999. + met = np.zeros((1, 24)) - 999.0 year = 2011 month = 6 day = 6 @@ -91,13 +98,13 @@ def tmrt_1d_fun(metfilepath,infolder,tau,lon,lat,dsm,r_range): else: dayspermonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] - doy = np.sum(dayspermonth[0:month - 1]) + day + doy = np.sum(dayspermonth[0 : month - 1]) + day - Ta = 25. - RH = 50. - radG = 880. - radD = 150. - radI = 950. + Ta = 25.0 + RH = 50.0 + radG = 880.0 + radD = 150.0 + radI = 950.0 met[0, 0] = year met[0, 1] = doy @@ -109,11 +116,12 @@ def tmrt_1d_fun(metfilepath,infolder,tau,lon,lat,dsm,r_range): met[0, 21] = radD met[0, 22] = radI - location = {'longitude': lon, 'latitude': lat, 'altitude': alt} - YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax = metload.Solweig_2015a_metdata_noload(met, location, - UTC) + location = {"longitude": lon, "latitude": lat, "altitude": alt} + YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax = ( + metload.Solweig_2015a_metdata_noload(met, location, UTC) + ) - svfalfa = np.arcsin(np.exp((np.log((1. - svf)) / 2.))) + svfalfa = np.arcsin(np.exp((np.log((1.0 - svf)) / 2.0))) dectime_new = np.zeros((dectime.shape[0], 1)) dec_doy = int(dectime[0]) @@ -135,7 +143,10 @@ def tmrt_1d_fun(metfilepath,infolder,tau,lon,lat,dsm,r_range): amaxvalue = dsm.max() - dsm.min() # load landcover file - sitein = os.path.dirname(os.path.abspath(__file__)) + "/landcoverclasses_2018a_orig.txt" + sitein = ( + os.path.dirname(os.path.abspath(__file__)) + + "/landcoverclasses_2018a_orig.txt" + ) f = open(sitein) lin = f.readlines() lc_class = np.zeros((lin.__len__() - 1, 6)) @@ -162,7 +173,7 @@ def tmrt_1d_fun(metfilepath,infolder,tau,lon,lat,dsm,r_range): TmaxLST_wall = lc_class[wall_pos, 5] # If metfile starts at night - CI = 1. + CI = 1.0 if ani == 1: # skyvaultalt = np.atleast_2d([]) @@ -179,10 +190,10 @@ def tmrt_1d_fun(metfilepath,infolder,tau,lon,lat,dsm,r_range): # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) skyvaultalt, skyvaultazi, _, _, _, _, _ = create_patches(patch_option) - + diffsh = np.zeros((skyvaultalt.shape[0])) - svfalfadeg = svfalfa / (np.pi / 180.) + svfalfadeg = svfalfa / (np.pi / 180.0) for k in range(0, 145): if skyvaultalt[k] > svfalfadeg: diffsh[k] = 1 @@ -202,26 +213,86 @@ def tmrt_1d_fun(metfilepath,infolder,tau,lon,lat,dsm,r_range): alt = altitude[0][daylines] alt2 = np.where(alt > 1) rise = alt2[0][0] - [_, CI, _, _, _] = clearnessindex_2013b(zen[0, i + rise + 1], jday[0, i + rise + 1], - Ta[i + rise + 1], - RH[i + rise + 1] / 100., radG[i + rise + 1], location, - P[i + rise + 1]) + [_, CI, _, _, _] = clearnessindex_2013b( + zen[0, i + rise + 1], + jday[0, i + rise + 1], + Ta[i + rise + 1], + RH[i + rise + 1] / 100.0, + radG[i + rise + 1], + location, + P[i + rise + 1], + ) if (CI > 1) or (CI == np.inf): CI = 1 - Tmrt, Kdown, Kup, Ldown, Lup, Tg, ea, esky, I0, CI, Keast, Ksouth, Kwest, Knorth, Least, Lsouth, Lwest, \ - Lnorth, KsideI, radIo, radDo, shadow1d = so.Solweig1D_2019a_calc(svf, svfveg, svfaveg, sh, vegsh, albedo_b, - absK, absL, ewall, - Fside, Fup, Fcyl, - altitude[0][i], azimuth[0][i], zen[0][i], - jday[0][i], - onlyglobal, location, dectime[i], altmax[0][i], - cyl, elvis, - Ta[i], RH[i], radG[i], radD[i], radI[i], P[i], - Twater, TgK, Tstart, albedo_g, eground, - TgK_wall, Tstart_wall, - TmaxLST, TmaxLST_wall, svfalfa, CI, ani, - diffsh, trans, patch_option) + ( + Tmrt, + Kdown, + Kup, + Ldown, + Lup, + Tg, + ea, + esky, + I0, + CI, + Keast, + Ksouth, + Kwest, + Knorth, + Least, + Lsouth, + Lwest, + Lnorth, + KsideI, + radIo, + radDo, + shadow1d, + ) = so.Solweig1D_2019a_calc( + svf, + svfveg, + svfaveg, + sh, + vegsh, + albedo_b, + absK, + absL, + ewall, + Fside, + Fup, + Fcyl, + altitude[0][i], + azimuth[0][i], + zen[0][i], + jday[0][i], + onlyglobal, + location, + dectime[i], + altmax[0][i], + cyl, + elvis, + Ta[i], + RH[i], + radG[i], + radD[i], + radI[i], + P[i], + Twater, + TgK, + Tstart, + albedo_g, + eground, + TgK_wall, + Tstart_wall, + TmaxLST, + TmaxLST_wall, + svfalfa, + CI, + ani, + diffsh, + trans, + patch_option, + ) tmrt_1d[i_c, 0] = Tmrt tmrt_1d[i_c, 1] = hours[i] diff --git a/functions/TreePlanter/SOLWEIG1D/Solweig1D_2019a_calc.py b/functions/TreePlanter/SOLWEIG1D/Solweig1D_2019a_calc.py index 1dc3453..f58ca0b 100644 --- a/functions/TreePlanter/SOLWEIG1D/Solweig1D_2019a_calc.py +++ b/functions/TreePlanter/SOLWEIG1D/Solweig1D_2019a_calc.py @@ -1,20 +1,66 @@ from __future__ import absolute_import import numpy as np from ...SOLWEIGpython.daylen import daylen -from ....util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import clearnessindex_2013b +from ....util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import ( + clearnessindex_2013b, +) from ....util.SEBESOLWEIGCommonFiles.diffusefraction import diffusefraction from ...SOLWEIGpython.cylindric_wedge import cylindric_wedge + # from .TsWaveDelay_2015a import TsWaveDelay_2015a # from .Kup_veg_2015a import Kup_veg_2015a from ...SOLWEIGpython.Lside_veg_v2015a import Lside_veg_v2015a from ..SOLWEIG1D.Kside1D_veg_v2019a import Kside_veg_v2019a -#from ...SOLWEIGpython.Perez_v3_moved import Perez_v3 + +# from ...SOLWEIGpython.Perez_v3_moved import Perez_v3 from ....util.SEBESOLWEIGCommonFiles.Perez_v3 import Perez_v3 -def Solweig1D_2019a_calc(svf, svfveg, svfaveg, sh, vegsh, albedo_b, absK, absL, ewall, Fside, Fup, Fcyl, altitude, azimuth, zen, jday, - onlyglobal, location, dectime, altmax, cyl, elvis, Ta, RH, radG, radD, radI, P, - Twater, TgK, Tstart, albedo_g, eground, TgK_wall, Tstart_wall, TmaxLST, TmaxLST_wall, - svfalfa, CI, ani, diffsh, trans, patch_option): + +def Solweig1D_2019a_calc( + svf, + svfveg, + svfaveg, + sh, + vegsh, + albedo_b, + absK, + absL, + ewall, + Fside, + Fup, + Fcyl, + altitude, + azimuth, + zen, + jday, + onlyglobal, + location, + dectime, + altmax, + cyl, + elvis, + Ta, + RH, + radG, + radD, + radI, + P, + Twater, + TgK, + Tstart, + albedo_g, + eground, + TgK_wall, + Tstart_wall, + TmaxLST, + TmaxLST_wall, + svfalfa, + CI, + ani, + diffsh, + trans, + patch_option, +): # This is the core function of the SOLWEIG1D model, 2019-Jun-21 # Fredrik Lindberg, fredrikl@gvc.gu.se, Goteborg Urban Climate Group, Gothenburg University, Sweden @@ -34,33 +80,39 @@ def Solweig1D_2019a_calc(svf, svfveg, svfaveg, sh, vegsh, albedo_b, absK, absL, psi = trans # Instrument offset in degrees - t = 0. + t = 0.0 # Stefan Bolzmans Constant SBC = 5.67051e-8 # Find sunrise decimal hour - new from 2014a - _, _, _, SNUP = daylen(jday, location['latitude']) + _, _, _, SNUP = daylen(jday, location["latitude"]) shadow = sh - (1 - vegsh) * (1 - psi) # Vapor pressure - ea = 6.107 * 10 ** ((7.5 * Ta) / (237.3 + Ta)) * (RH / 100.) + ea = 6.107 * 10 ** ((7.5 * Ta) / (237.3 + Ta)) * (RH / 100.0) # Determination of clear - sky emissivity from Prata (1996) msteg = 46.5 * (ea / (Ta + 273.15)) - esky = (1 - (1 + msteg) * np.exp(-((1.2 + 3.0 * msteg) ** 0.5))) + elvis # -0.04 old error from Jonsson et al.2006 + esky = ( + 1 - (1 + msteg) * np.exp(-((1.2 + 3.0 * msteg) ** 0.5)) + ) + elvis # -0.04 old error from Jonsson et al.2006 - if altitude > 0: # # # # # # DAYTIME # # # # # # + if altitude > 0: # # # # # # DAYTIME # # # # # # # Clearness Index on Earth's surface after Crawford and Dunchon (1999) with a correction # factor for low sun elevations after Lindberg et al.(2008) - I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b(zen, jday, Ta, RH / 100., radG, location, P) + I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( + zen, jday, Ta, RH / 100.0, radG, location, P + ) if (CI > 1) or (CI == np.inf): CI = 1 # Estimation of radD and radI if not measured after Reindl et al.(1990) if onlyglobal == 1: - I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b(zen, jday, Ta, RH / 100., radG, location, P) + I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( + zen, jday, Ta, RH / 100.0, radG, location, P + ) if (CI > 1) or (CI == np.inf): CI = 1 @@ -70,14 +122,20 @@ def Solweig1D_2019a_calc(svf, svfveg, svfaveg, sh, vegsh, albedo_b, absK, absL, # Anisotropic Diffuse Radiation after Perez et al. 1993 if ani == 1: patchchoice = 1 - zenDeg = zen*(180/np.pi) - lv = Perez_v3(zenDeg, azimuth, radD, radI, jday, patchchoice, patch_option) # Relative luminance + zenDeg = zen * (180 / np.pi) + lv = Perez_v3( + zenDeg, azimuth, radD, radI, jday, patchchoice, patch_option + ) # Relative luminance - aniLum = 0. + aniLum = 0.0 for idx in range(0, 145): - aniLum = aniLum + diffsh[idx] * lv[0][idx][2] # Total relative luminance from sky into each cell + aniLum = ( + aniLum + diffsh[idx] * lv[0][idx][2] + ) # Total relative luminance from sky into each cell - dRad = aniLum * radD # Total diffuse radiation from sky into each cell + dRad = ( + aniLum * radD + ) # Total diffuse radiation from sky into each cell else: dRad = radD * svf @@ -87,16 +145,38 @@ def Solweig1D_2019a_calc(svf, svfveg, svfaveg, sh, vegsh, albedo_b, absK, absL, # new using max sun alt.instead of dfm Tgamp = (TgK * altmax - Tstart) + Tstart Tgampwall = (TgK_wall * altmax - (Tstart_wall)) + (Tstart_wall) - Tg = Tgamp * np.sin((((dectime - np.floor(dectime)) - SNUP / 24) / (TmaxLST / 24 - SNUP / 24)) * np.pi / 2) + Tstart # 2015 a, based on max sun altitude - Tgwall = Tgampwall * np.sin((((dectime - np.floor(dectime)) - SNUP / 24) / (TmaxLST_wall / 24 - SNUP / 24)) * np.pi / 2) + (Tstart_wall) # 2015a, based on max sun altitude + Tg = ( + Tgamp + * np.sin( + ( + ((dectime - np.floor(dectime)) - SNUP / 24) + / (TmaxLST / 24 - SNUP / 24) + ) + * np.pi + / 2 + ) + + Tstart + ) # 2015 a, based on max sun altitude + Tgwall = Tgampwall * np.sin( + ( + ((dectime - np.floor(dectime)) - SNUP / 24) + / (TmaxLST_wall / 24 - SNUP / 24) + ) + * np.pi + / 2 + ) + ( + Tstart_wall + ) # 2015a, based on max sun altitude if Tgwall < 0: # temporary for removing low Tg during morning 20130205 # Tg = 0 Tgwall = 0 # New estimation of Tg reduction for non - clear situation based on Reindl et al.1990 - radI0, _ = diffusefraction(I0, altitude, 1., Ta, RH) - corr = 0.1473 * np.log(90 - (zen / np.pi * 180)) + 0.3454 # 20070329 correction of lat, Lindberg et al. 2008 + radI0, _ = diffusefraction(I0, altitude, 1.0, Ta, RH) + corr = ( + 0.1473 * np.log(90 - (zen / np.pi * 180)) + 0.3454 + ) # 20070329 correction of lat, Lindberg et al. 2008 CI_Tg = (radI / radI0) + (1 - corr) if (CI_Tg > 1) or (CI_Tg == np.inf): CI_Tg = 1 @@ -114,25 +194,60 @@ def Solweig1D_2019a_calc(svf, svfveg, svfaveg, sh, vegsh, albedo_b, absK, absL, LupN = Lup # Building height angle from svf - F_sh = cylindric_wedge(zen, svfalfa, 1, 1) # Fraction shadow on building walls based on sun alt and svf + F_sh = cylindric_wedge( + zen, svfalfa, 1, 1 + ) # Fraction shadow on building walls based on sun alt and svf F_sh[np.isnan(F_sh)] = 0.5 # # # # # # # Calculation of shortwave daytime radiative fluxes # # # # # # # - Kdown = radI * shadow * np.sin(altitude * (np.pi / 180)) + dRad + albedo_b * (1 - svf) * \ - (radG * (1 - F_sh) + radD * F_sh) # *sin(altitude(i) * (pi / 180)) + Kdown = ( + radI * shadow * np.sin(altitude * (np.pi / 180)) + + dRad + + albedo_b * (1 - svf) * (radG * (1 - F_sh) + radD * F_sh) + ) # *sin(altitude(i) * (pi / 180)) - #Kdown = radI * shadow * np.sin(altitude * (np.pi / 180)) + radD * svfbuveg + albedo_b * (1 - svfbuveg) * \ - #(radG * (1 - F_sh) + radD * F_sh) # *sin(altitude(i) * (pi / 180)) + # Kdown = radI * shadow * np.sin(altitude * (np.pi / 180)) + radD * svfbuveg + albedo_b * (1 - svfbuveg) * \ + # (radG * (1 - F_sh) + radD * F_sh) # *sin(altitude(i) * (pi / 180)) - Kup = albedo_g * (shadow * radI * np.sin(altitude * (np.pi / 180.))) + radD * svf + \ - albedo_b * (1 - svf) * (radG * (1 - F_sh) + radD * F_sh) + Kup = ( + albedo_g * (shadow * radI * np.sin(altitude * (np.pi / 180.0))) + + radD * svf + + albedo_b * (1 - svf) * (radG * (1 - F_sh) + radD * F_sh) + ) # Kup, KupE, KupS, KupW, KupN = Kup_veg_2015a(radI, radD, radG, altitude, svf, albedo_b, F_sh, gvfalb, # gvfalbE, gvfalbS, gvfalbW, gvfalbN, gvfalbnosh, gvfalbnoshE, gvfalbnoshS, gvfalbnoshW, gvfalbnoshN) - Keast, Ksouth, Kwest, Knorth, KsideI, KsideD = Kside_veg_v2019a(radI, radD, radG, shadow, svfS, svfW, svfN, svfE, - svfEveg, svfSveg, svfWveg, svfNveg, azimuth, altitude, psi, t, albedo_b, F_sh, Kup, Kup, Kup, - Kup, cyl, lv, ani, diffsh, 1, 1) + Keast, Ksouth, Kwest, Knorth, KsideI, KsideD = Kside_veg_v2019a( + radI, + radD, + radG, + shadow, + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + azimuth, + altitude, + psi, + t, + albedo_b, + F_sh, + Kup, + Kup, + Kup, + Kup, + cyl, + lv, + ani, + diffsh, + 1, + 1, + ) firstdaytime = 0 @@ -142,18 +257,18 @@ def Solweig1D_2019a_calc(svf, svfveg, svfaveg, sh, vegsh, albedo_b, absK, absL, # CI_Tg = -999 # F_sh = [] # Nocturnal K fluxes set to 0 - Knight = 0. - Kdown = 0. - Kwest = 0. - Kup = 0. - Keast = 0. - Ksouth = 0. - Knorth = 0. - KsideI = 0. - KsideD = 0. - F_sh = 0. - Tg = 0. - shadow = 0. + Knight = 0.0 + Kdown = 0.0 + Kwest = 0.0 + Kup = 0.0 + Keast = 0.0 + Ksouth = 0.0 + Knorth = 0.0 + KsideI = 0.0 + KsideD = 0.0 + F_sh = 0.0 + Tg = 0.0 + shadow = 0.0 # # # # Lup # # # # Lup = SBC * eground * ((Knight + Ta + Tg + 273.15) ** 4) @@ -172,32 +287,117 @@ def Solweig1D_2019a_calc(svf, svfveg, svfaveg, sh, vegsh, albedo_b, absK, absL, # firstdaytime = 1 # # # # Ldown # # # # - Ldown = svf * esky * SBC * ((Ta + 273.15) ** 4) + (1 - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + \ - (1 - svf) * (1 - ewall) * esky * SBC * ((Ta + 273.15) ** 4) # Jonsson et al.(2006) + Ldown = ( + svf * esky * SBC * ((Ta + 273.15) ** 4) + + (1 - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + + (1 - svf) * (1 - ewall) * esky * SBC * ((Ta + 273.15) ** 4) + ) # Jonsson et al.(2006) if CI < 0.95: # non - clear conditions c = 1 - CI - Ldown = Ldown * (1 - c) + \ - c * (svf * SBC * ((Ta + 273.15) ** 4) + (1 - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + - (1 - svf) * (1 - ewall) * esky * SBC * ((Ta + 273.15) ** 4)) + Ldown = Ldown * (1 - c) + c * ( + svf * SBC * ((Ta + 273.15) ** 4) + + (1 - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + + (1 - svf) * (1 - ewall) * esky * SBC * ((Ta + 273.15) ** 4) + ) # # # # Lside # # # # - Least, Lsouth, Lwest, Lnorth = Lside_veg_v2015a(svfS, svfW, svfN, svfE, svfEveg, svfSveg, svfWveg, svfNveg, - svfEaveg, svfSaveg, svfWaveg, svfNaveg, azimuth, altitude, Ta, Tgwall, SBC, ewall, Ldown, - esky, t, F_sh, CI, LupE, LupS, LupW, LupN) + Least, Lsouth, Lwest, Lnorth = Lside_veg_v2015a( + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + azimuth, + altitude, + Ta, + Tgwall, + SBC, + ewall, + Ldown, + esky, + t, + F_sh, + CI, + LupE, + LupS, + LupW, + LupN, + ) # # # # Calculation of radiant flux density and Tmrt # # # # - if cyl == 1 and ani == 1: # Human body considered as a cylinder with Perez et al. (1993) - Sstr = absK * ((KsideI + KsideD) * Fcyl + (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside) + absL * \ - (Ldown * Fup + Lup * Fup + Lnorth * Fside + Least * Fside + Lsouth * Fside + Lwest * Fside) - elif cyl == 1 and ani == 0: # Human body considered as a cylinder with isotropic all-sky diffuse - Sstr = absK * (KsideI * Fcyl + (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside) + absL * \ - (Ldown * Fup + Lup * Fup + Lnorth * Fside + Least * Fside + Lsouth * Fside + Lwest * Fside) - else: # Human body considered as a standing cube - Sstr = absK * ((Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside) +absL * \ - (Ldown * Fup + Lup * Fup + Lnorth * Fside + Least * Fside + Lsouth * Fside + Lwest * Fside) + if ( + cyl == 1 and ani == 1 + ): # Human body considered as a cylinder with Perez et al. (1993) + Sstr = absK * ( + (KsideI + KsideD) * Fcyl + + (Kdown + Kup) * Fup + + (Knorth + Keast + Ksouth + Kwest) * Fside + ) + absL * ( + Ldown * Fup + + Lup * Fup + + Lnorth * Fside + + Least * Fside + + Lsouth * Fside + + Lwest * Fside + ) + elif ( + cyl == 1 and ani == 0 + ): # Human body considered as a cylinder with isotropic all-sky diffuse + Sstr = absK * ( + KsideI * Fcyl + + (Kdown + Kup) * Fup + + (Knorth + Keast + Ksouth + Kwest) * Fside + ) + absL * ( + Ldown * Fup + + Lup * Fup + + Lnorth * Fside + + Least * Fside + + Lsouth * Fside + + Lwest * Fside + ) + else: # Human body considered as a standing cube + Sstr = absK * ( + (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside + ) + absL * ( + Ldown * Fup + + Lup * Fup + + Lnorth * Fside + + Least * Fside + + Lsouth * Fside + + Lwest * Fside + ) Tmrt = np.sqrt(np.sqrt((Sstr / (absL * SBC)))) - 273.2 - return Tmrt, Kdown, Kup, Ldown, Lup, Tg, ea, esky, I0, CI, Keast, Ksouth, Kwest, Knorth, Least, Lsouth, Lwest, \ - Lnorth, KsideI, radI, radD, shadow + return ( + Tmrt, + Kdown, + Kup, + Ldown, + Lup, + Tg, + ea, + esky, + I0, + CI, + Keast, + Ksouth, + Kwest, + Knorth, + Least, + Lsouth, + Lwest, + Lnorth, + KsideI, + radI, + radD, + shadow, + ) diff --git a/functions/TreePlanter/SOLWEIG1D/Solweig1D_2023a_calc.py b/functions/TreePlanter/SOLWEIG1D/Solweig1D_2023a_calc.py index 55253b4..bf99a7c 100644 --- a/functions/TreePlanter/SOLWEIG1D/Solweig1D_2023a_calc.py +++ b/functions/TreePlanter/SOLWEIG1D/Solweig1D_2023a_calc.py @@ -1,23 +1,75 @@ from __future__ import absolute_import import numpy as np from ...SOLWEIGpython.daylen import daylen -from ....util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import clearnessindex_2013b +from ....util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import ( + clearnessindex_2013b, +) from ....util.SEBESOLWEIGCommonFiles.diffusefraction import diffusefraction from ...SOLWEIGpython.cylindric_wedge import cylindric_wedge + # from .TsWaveDelay_2015a import TsWaveDelay_2015a # from .Kup_veg_2015a import Kup_veg_2015a from ...SOLWEIGpython.Lside_veg_v2015a import Lside_veg_v2015a from ..SOLWEIG1D.Kside1D_veg_v2019a import Kside_veg_v2019a -#from ...SOLWEIGpython.Perez_v3_moved import Perez_v3 + +# from ...SOLWEIGpython.Perez_v3_moved import Perez_v3 from ....util.SEBESOLWEIGCommonFiles.Perez_v3 import Perez_v3 from .anisotropic_sky import anisotropic_sky as ani_sky from copy import deepcopy -def Solweig1D_2019a_calc(svf, svfveg, svfaveg, sh, vegsh, albedo_b, absK, absL, ewall, Fside, Fup, Fcyl, altitude, azimuth, zen, jday, - onlyglobal, location, dectime, altmax, cyl, elvis, Ta, RH, radG, radD, radI, P, - Twater, TgK, Tstart, albedo_g, eground, TgK_wall, Tstart_wall, TmaxLST, TmaxLST_wall, - svfalfa, CI, ani, diffsh, trans, patch_option, skyp, buip, vegp, asvf, L_patches, steradians): + +def Solweig1D_2019a_calc( + svf, + svfveg, + svfaveg, + sh, + vegsh, + albedo_b, + absK, + absL, + ewall, + Fside, + Fup, + Fcyl, + altitude, + azimuth, + zen, + jday, + onlyglobal, + location, + dectime, + altmax, + cyl, + elvis, + Ta, + RH, + radG, + radD, + radI, + P, + Twater, + TgK, + Tstart, + albedo_g, + eground, + TgK_wall, + Tstart_wall, + TmaxLST, + TmaxLST_wall, + svfalfa, + CI, + ani, + diffsh, + trans, + patch_option, + skyp, + buip, + vegp, + asvf, + L_patches, + steradians, +): # This is the core function of the SOLWEIG1D model, 2019-Jun-21 # Fredrik Lindberg, fredrikl@gvc.gu.se, Goteborg Urban Climate Group, Gothenburg University, Sweden @@ -37,36 +89,42 @@ def Solweig1D_2019a_calc(svf, svfveg, svfaveg, sh, vegsh, albedo_b, absK, absL, psi = trans # Instrument offset in degrees - t = 0. + t = 0.0 # Stefan Bolzmans Constant SBC = 5.67051e-8 # Degrees to radians - deg2rad = np.pi/180 + deg2rad = np.pi / 180 # Find sunrise decimal hour - new from 2014a - _, _, _, SNUP = daylen(jday, location['latitude']) + _, _, _, SNUP = daylen(jday, location["latitude"]) shadow = sh - (1 - vegsh) * (1 - psi) # Vapor pressure - ea = 6.107 * 10 ** ((7.5 * Ta) / (237.3 + Ta)) * (RH / 100.) + ea = 6.107 * 10 ** ((7.5 * Ta) / (237.3 + Ta)) * (RH / 100.0) # Determination of clear - sky emissivity from Prata (1996) msteg = 46.5 * (ea / (Ta + 273.15)) - esky = (1 - (1 + msteg) * np.exp(-((1.2 + 3.0 * msteg) ** 0.5))) + elvis # -0.04 old error from Jonsson et al.2006 + esky = ( + 1 - (1 + msteg) * np.exp(-((1.2 + 3.0 * msteg) ** 0.5)) + ) + elvis # -0.04 old error from Jonsson et al.2006 - if altitude > 0: # # # # # # DAYTIME # # # # # # + if altitude > 0: # # # # # # DAYTIME # # # # # # # Clearness Index on Earth's surface after Crawford and Dunchon (1999) with a correction # factor for low sun elevations after Lindberg et al.(2008) - I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b(zen, jday, Ta, RH / 100., radG, location, P) + I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( + zen, jday, Ta, RH / 100.0, radG, location, P + ) if (CI > 1) or (CI == np.inf): CI = 1 # Estimation of radD and radI if not measured after Reindl et al.(1990) if onlyglobal == 1: - I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b(zen, jday, Ta, RH / 100., radG, location, P) + I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( + zen, jday, Ta, RH / 100.0, radG, location, P + ) if (CI > 1) or (CI == np.inf): CI = 1 @@ -76,14 +134,20 @@ def Solweig1D_2019a_calc(svf, svfveg, svfaveg, sh, vegsh, albedo_b, absK, absL, # Anisotropic Diffuse Radiation after Perez et al. 1993 if ani == 1: patchchoice = 1 - zenDeg = zen*(180/np.pi) - lv, _, _ = Perez_v3(zenDeg, azimuth, radD, radI, jday, patchchoice, patch_option) # Relative luminance + zenDeg = zen * (180 / np.pi) + lv, _, _ = Perez_v3( + zenDeg, azimuth, radD, radI, jday, patchchoice, patch_option + ) # Relative luminance - aniLum = 0. + aniLum = 0.0 for idx in range(skyp.shape[0]): - aniLum = aniLum + skyp[idx] * lv[idx, 2] # Total relative luminance from sky into each cell + aniLum = ( + aniLum + skyp[idx] * lv[idx, 2] + ) # Total relative luminance from sky into each cell - dRad = aniLum * radD # Total diffuse radiation from sky into each cell + dRad = ( + aniLum * radD + ) # Total diffuse radiation from sky into each cell else: dRad = radD * svf @@ -92,19 +156,35 @@ def Solweig1D_2019a_calc(svf, svfveg, svfaveg, sh, vegsh, albedo_b, absK, absL, # # # Surface temperature parameterisation during daytime # # # # # new using max sun alt.instead of dfm # Tgamp = (TgK * altmax - Tstart) + Tstart # Old - Tgamp = TgK * altmax + Tstart # Fixed 2021 + Tgamp = TgK * altmax + Tstart # Fixed 2021 # Tgampwall = (TgK_wall * altmax - (Tstart_wall)) + (Tstart_wall) # Old Tgampwall = TgK_wall * altmax + Tstart_wall - Tg = Tgamp * np.sin((((dectime - np.floor(dectime)) - SNUP / 24) / (TmaxLST / 24 - SNUP / 24)) * np.pi / 2) # 2015 a, based on max sun altitude - Tgwall = Tgampwall * np.sin((((dectime - np.floor(dectime)) - SNUP / 24) / (TmaxLST_wall / 24 - SNUP / 24)) * np.pi / 2) # 2015a, based on max sun altitude + Tg = Tgamp * np.sin( + ( + ((dectime - np.floor(dectime)) - SNUP / 24) + / (TmaxLST / 24 - SNUP / 24) + ) + * np.pi + / 2 + ) # 2015 a, based on max sun altitude + Tgwall = Tgampwall * np.sin( + ( + ((dectime - np.floor(dectime)) - SNUP / 24) + / (TmaxLST_wall / 24 - SNUP / 24) + ) + * np.pi + / 2 + ) # 2015a, based on max sun altitude if Tgwall < 0: # temporary for removing low Tg during morning 20130205 # Tg = 0 Tgwall = 0 # New estimation of Tg reduction for non - clear situation based on Reindl et al.1990 - radI0, _ = diffusefraction(I0, altitude, 1., Ta, RH) - corr = 0.1473 * np.log(90 - (zen / np.pi * 180)) + 0.3454 # 20070329 correction of lat, Lindberg et al. 2008 + radI0, _ = diffusefraction(I0, altitude, 1.0, Ta, RH) + corr = ( + 0.1473 * np.log(90 - (zen / np.pi * 180)) + 0.3454 + ) # 20070329 correction of lat, Lindberg et al. 2008 CI_Tg = (radG / radI0) + (1 - corr) if (CI_Tg > 1) or (CI_Tg == np.inf): CI_Tg = 1 @@ -113,12 +193,12 @@ def Solweig1D_2019a_calc(svf, svfveg, svfaveg, sh, vegsh, albedo_b, absK, absL, CI_TgG = (radG / radG0) + (1 - corr) if (CI_TgG > 1) or (CI_TgG == np.inf): CI_TgG = 1 - + # Tg = Tg * CI_Tg # new estimation # Tgwall = Tgwall * CI_Tg Tg = Tg * CI_TgG # new estimation Tgwall = Tgwall * CI_TgG - #if landcover == 1: + # if landcover == 1: # Tg[Tg < 0] = 0 # temporary for removing low Tg during morning 20130205 # gvf = shadow @@ -132,18 +212,26 @@ def Solweig1D_2019a_calc(svf, svfveg, svfaveg, sh, vegsh, albedo_b, absK, absL, LupN = Lup * 0.5 # Building height angle from svf - F_sh = cylindric_wedge(zen, svfalfa, 1, 1) # Fraction shadow on building walls based on sun alt and svf + F_sh = cylindric_wedge( + zen, svfalfa, 1, 1 + ) # Fraction shadow on building walls based on sun alt and svf F_sh[np.isnan(F_sh)] = 0.5 # # # # # # # Calculation of shortwave daytime radiative fluxes # # # # # # # - Kdown = radI * shadow * np.sin(altitude * (np.pi / 180)) + dRad + albedo_b * (1 - svf) * \ - (radG * (1 - F_sh) + radD * F_sh) # *sin(altitude(i) * (pi / 180)) + Kdown = ( + radI * shadow * np.sin(altitude * (np.pi / 180)) + + dRad + + albedo_b * (1 - svf) * (radG * (1 - F_sh) + radD * F_sh) + ) # *sin(altitude(i) * (pi / 180)) - #Kdown = radI * shadow * np.sin(altitude * (np.pi / 180)) + radD * svfbuveg + albedo_b * (1 - svfbuveg) * \ - #(radG * (1 - F_sh) + radD * F_sh) # *sin(altitude(i) * (pi / 180)) + # Kdown = radI * shadow * np.sin(altitude * (np.pi / 180)) + radD * svfbuveg + albedo_b * (1 - svfbuveg) * \ + # (radG * (1 - F_sh) + radD * F_sh) # *sin(altitude(i) * (pi / 180)) - Kup = albedo_g * (shadow * radI * np.sin(altitude * (np.pi / 180.))) + radD * svf + \ - albedo_b * (1 - svf) * (radG * (1 - F_sh) + radD * F_sh) + Kup = ( + albedo_g * (shadow * radI * np.sin(altitude * (np.pi / 180.0))) + + radD * svf + + albedo_b * (1 - svf) * (radG * (1 - F_sh) + radD * F_sh) + ) # Kup, KupE, KupS, KupW, KupN = Kup_veg_2015a(radI, radD, radG, altitude, svf, albedo_b, F_sh, gvfalb, # gvfalbE, gvfalbS, gvfalbW, gvfalbN, gvfalbnosh, gvfalbnoshE, gvfalbnoshS, gvfalbnoshW, gvfalbnoshN) @@ -160,18 +248,18 @@ def Solweig1D_2019a_calc(svf, svfveg, svfaveg, sh, vegsh, albedo_b, absK, absL, # CI_Tg = -999 # F_sh = [] # Nocturnal K fluxes set to 0 - Knight = 0. - Kdown = 0. - Kwest = 0. - Kup = 0. - Keast = 0. - Ksouth = 0. - Knorth = 0. - KsideI = 0. - KsideD = 0. - F_sh = 0. - Tg = 0. - shadow = 0. + Knight = 0.0 + Kdown = 0.0 + Kwest = 0.0 + Kup = 0.0 + Keast = 0.0 + Ksouth = 0.0 + Knorth = 0.0 + KsideI = 0.0 + KsideD = 0.0 + F_sh = 0.0 + Tg = 0.0 + shadow = 0.0 # # # # Lup # # # # Lup = SBC * eground * ((Knight + Ta + Tg + 273.15) ** 4) @@ -190,34 +278,110 @@ def Solweig1D_2019a_calc(svf, svfveg, svfaveg, sh, vegsh, albedo_b, absK, absL, # firstdaytime = 1 # # # # Ldown # # # # - Ldown = svf * esky * SBC * ((Ta + 273.15) ** 4) + (1 - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + \ - (1 - svf) * (1 - ewall) * esky * SBC * ((Ta + 273.15) ** 4) # Jonsson et al.(2006) + Ldown = ( + svf * esky * SBC * ((Ta + 273.15) ** 4) + + (1 - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + + (1 - svf) * (1 - ewall) * esky * SBC * ((Ta + 273.15) ** 4) + ) # Jonsson et al.(2006) if CI < 0.95: # non - clear conditions c = 1 - CI - Ldown = Ldown * (1 - c) + \ - c * (svf * SBC * ((Ta + 273.15) ** 4) + (1 - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + - (1 - svf) * (1 - ewall) * esky * SBC * ((Ta + 273.15) ** 4)) + Ldown = Ldown * (1 - c) + c * ( + svf * SBC * ((Ta + 273.15) ** 4) + + (1 - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + + (1 - svf) * (1 - ewall) * esky * SBC * ((Ta + 273.15) ** 4) + ) if ani == 0: # # # # Lside # # # # - Least, Lsouth, Lwest, Lnorth = Lside_veg_v2015a(svfS, svfW, svfN, svfE, svfEveg, svfSveg, svfWveg, svfNveg, - svfEaveg, svfSaveg, svfWaveg, svfNaveg, azimuth, altitude, Ta, Tgwall, SBC, ewall, Ldown, - esky, t, F_sh, CI, LupE, LupS, LupW, LupN) + Least, Lsouth, Lwest, Lnorth = Lside_veg_v2015a( + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + azimuth, + altitude, + Ta, + Tgwall, + SBC, + ewall, + Ldown, + esky, + t, + F_sh, + CI, + LupE, + LupS, + LupW, + LupN, + ) else: if CI < 0.95: - esky_c = CI * esky + (1 - CI) * 1. + esky_c = CI * esky + (1 - CI) * 1.0 esky = esky_c - L_patches[:, 2] = 0. + L_patches[:, 2] = 0.0 + + ( + Ldown, + Lside, + Lside_sky, + Lside_veg, + Lside_sh, + Lside_sun, + Lside_ref, + Least_, + Lwest_, + Lnorth_, + Lsouth_, + Keast, + Ksouth, + Kwest, + Knorth, + KsideI, + KsideD, + Kside, + steradians, + skyalt, + ) = ani_sky( + skyp, + buip, + vegp, + altitude, + azimuth, + asvf, + cyl, + esky, + L_patches, + False, + False, + False, + steradians, + Ta, + Tgwall, + ewall, + Lup, + radI, + radD, + radG, + lv, + albedo_b, + 0, + diffsh, + shadow, + Kup, + ) - Ldown, Lside, Lside_sky, Lside_veg, Lside_sh, Lside_sun, Lside_ref, Least_, Lwest_, Lnorth_, Lsouth_, \ - Keast, Ksouth, Kwest, Knorth, KsideI, KsideD, Kside, steradians, skyalt = ani_sky(skyp, buip, vegp, altitude, azimuth, asvf, cyl, esky, - L_patches, False, False, False, steradians, Ta, Tgwall, ewall, Lup, radI, radD, radG, lv, - albedo_b, 0, diffsh, shadow, Kup) - Least = 0 Lwest = 0 Lsouth = 0 @@ -240,18 +404,67 @@ def Solweig1D_2019a_calc(svf, svfveg, svfaveg, sh, vegsh, albedo_b, absK, absL, # Sstr = absK * ((KsideI + KsideD) * Fcyl + (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside) + absL * \ # (Ldown * Fup + Lup * Fup + Lnorth * Fside + Least * Fside + Lsouth * Fside + Lwest * Fside) if cyl == 1 and ani == 1: - Sstr = absK * (Kside * Fcyl + (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside) + absL * \ - ((Ldown + Lup) * Fup + Lside * Fcyl + (Lnorth + Least + Lsouth + Lwest) * Fside) - - elif cyl == 1 and ani == 0: # Human body considered as a cylinder with isotropic all-sky diffuse - Sstr = absK * (KsideI * Fcyl + (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside) + absL * \ - (Ldown * Fup + Lup * Fup + Lnorth * Fside + Least * Fside + Lsouth * Fside + Lwest * Fside) - - else: # Human body considered as a standing cube - Sstr = absK * ((Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside) +absL * \ - (Ldown * Fup + Lup * Fup + Lnorth * Fside + Least * Fside + Lsouth * Fside + Lwest * Fside) + Sstr = absK * ( + Kside * Fcyl + + (Kdown + Kup) * Fup + + (Knorth + Keast + Ksouth + Kwest) * Fside + ) + absL * ( + (Ldown + Lup) * Fup + + Lside * Fcyl + + (Lnorth + Least + Lsouth + Lwest) * Fside + ) + + elif ( + cyl == 1 and ani == 0 + ): # Human body considered as a cylinder with isotropic all-sky diffuse + Sstr = absK * ( + KsideI * Fcyl + + (Kdown + Kup) * Fup + + (Knorth + Keast + Ksouth + Kwest) * Fside + ) + absL * ( + Ldown * Fup + + Lup * Fup + + Lnorth * Fside + + Least * Fside + + Lsouth * Fside + + Lwest * Fside + ) + + else: # Human body considered as a standing cube + Sstr = absK * ( + (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside + ) + absL * ( + Ldown * Fup + + Lup * Fup + + Lnorth * Fside + + Least * Fside + + Lsouth * Fside + + Lwest * Fside + ) Tmrt = np.sqrt(np.sqrt((Sstr / (absL * SBC)))) - 273.2 - return Tmrt, Kdown, Kup, Ldown, Lup, Tg, ea, esky, I0, CI, Keast, Ksouth, Kwest, Knorth, Least, Lsouth, Lwest, \ - Lnorth, KsideI, radI, radD, shadow + return ( + Tmrt, + Kdown, + Kup, + Ldown, + Lup, + Tg, + ea, + esky, + I0, + CI, + Keast, + Ksouth, + Kwest, + Knorth, + Least, + Lsouth, + Lwest, + Lnorth, + KsideI, + radI, + radD, + shadow, + ) diff --git a/functions/TreePlanter/SOLWEIG1D/anisotropic_sky.py b/functions/TreePlanter/SOLWEIG1D/anisotropic_sky.py index ccefc56..63aa195 100644 --- a/functions/TreePlanter/SOLWEIG1D/anisotropic_sky.py +++ b/functions/TreePlanter/SOLWEIG1D/anisotropic_sky.py @@ -7,11 +7,36 @@ from . import sunlit_shaded_patches from . import patch_radiation -def anisotropic_sky(skyp, buip, vegp, solar_altitude, solar_azimuth, asvf, cyl, - esky, L_patches, wallScheme, voxelTable, voxelMaps, steradians, Ta, Tgwall, ewall, Lup, - radI, radD, radG, lv, albedo, anisotropic_diffuse, diffsh, shadow, Kup): - - '''This function estimates short- and longwave radiation from an anisotropic sky....''' + +def anisotropic_sky( + skyp, + buip, + vegp, + solar_altitude, + solar_azimuth, + asvf, + cyl, + esky, + L_patches, + wallScheme, + voxelTable, + voxelMaps, + steradians, + Ta, + Tgwall, + ewall, + Lup, + radI, + radD, + radG, + lv, + albedo, + anisotropic_diffuse, + diffsh, + shadow, + Kup, +): + """This function estimates short- and longwave radiation from an anisotropic sky....""" # Stefan-Boltzmann's Constant SBC = 5.67051e-8 @@ -62,13 +87,19 @@ def anisotropic_sky(skyp, buip, vegp, solar_altitude, solar_azimuth, asvf, cyl, # Unsworth & Monteith (1975) if emis_m == 1: - patch_emissivity_normalized, esky_band = emissivity_models.model1(L_patches, esky, Ta) + patch_emissivity_normalized, esky_band = emissivity_models.model1( + L_patches, esky, Ta + ) # Martin & Berdahl (1984) elif emis_m == 2: - patch_emissivity_normalized, esky_band = emissivity_models.model2(L_patches, esky, Ta) + patch_emissivity_normalized, esky_band = emissivity_models.model2( + L_patches, esky, Ta + ) # Bliss (1961) elif emis_m == 3: - patch_emissivity_normalized, esky_band = emissivity_models.model3(L_patches, esky, Ta) + patch_emissivity_normalized, esky_band = emissivity_models.model3( + L_patches, esky, Ta + ) # True = anisotropic sky, False = isotropic sky anisotropic_sky_ = True @@ -85,17 +116,27 @@ def anisotropic_sky(skyp, buip, vegp, solar_altitude, solar_azimuth, asvf, cyl, else: temp_emissivity = esky # Estimate longwave radiation on a horizontal surface (Ldown), vertical surface (Lside) and perpendicular (Lnormal) - Ldown[patch_altitude == temp_altitude] = ((temp_emissivity * SBC * ((Ta + 273.15) ** 4)) / np.pi) * steradians[patch_altitude == temp_altitude] * np.sin(temp_altitude * deg2rad) - Lside[patch_altitude == temp_altitude] = ((temp_emissivity * SBC * ((Ta + 273.15) ** 4)) / np.pi) * steradians[patch_altitude == temp_altitude] * np.cos(temp_altitude * deg2rad) - Lnormal[patch_altitude == temp_altitude] = ((temp_emissivity * SBC * ((Ta + 273.15) ** 4)) / np.pi) * steradians[patch_altitude == temp_altitude] + Ldown[patch_altitude == temp_altitude] = ( + ((temp_emissivity * SBC * ((Ta + 273.15) ** 4)) / np.pi) + * steradians[patch_altitude == temp_altitude] + * np.sin(temp_altitude * deg2rad) + ) + Lside[patch_altitude == temp_altitude] = ( + ((temp_emissivity * SBC * ((Ta + 273.15) ** 4)) / np.pi) + * steradians[patch_altitude == temp_altitude] + * np.cos(temp_altitude * deg2rad) + ) + Lnormal[patch_altitude == temp_altitude] = ( + (temp_emissivity * SBC * ((Ta + 273.15) ** 4)) / np.pi + ) * steradians[patch_altitude == temp_altitude] Lsky_normal = deepcopy(L_patches) Lsky_down = deepcopy(L_patches) Lsky_side = deepcopy(L_patches) - Lsky_normal[:,2] = Lnormal - Lsky_down[:,2] = Ldown - Lsky_side[:,2] = Lside + Lsky_normal[:, 2] = Lnormal + Lsky_down[:, 2] = Ldown + Lsky_side[:, 2] = Lside # Shortwave radiation if solar_altitude > 0: @@ -105,13 +146,17 @@ def anisotropic_sky(skyp, buip, vegp, solar_altitude, solar_azimuth, asvf, cyl, radTot = np.zeros(1) # Radiance fraction normalization for i in np.arange(patch_altitude.shape[0]): - radTot += (patch_luminance[i] * steradians[i] * np.sin(patch_altitude[i] * deg2rad)) # Radiance fraction normalization + radTot += ( + patch_luminance[i] + * steradians[i] + * np.sin(patch_altitude[i] * deg2rad) + ) # Radiance fraction normalization lumChi = (patch_luminance * radD) / radTot for i in np.arange(patch_altitude.shape[0]): # Calculations for patches on sky, shmat = 1 = sky is visible # temp_sky = ((shmat[:, :, i] == 1) & (vegshmat[:, :, i] == 1)) - + # Calculations for patches that are vegetation, vegshmat = 0 = shade from vegetation # temp_vegsh = ((vegshmat[:, :, i] == 0) | (vbshvegshmat[:, :, i] == 0)) @@ -125,18 +170,37 @@ def anisotropic_sky(skyp, buip, vegp, solar_altitude, solar_azimuth, asvf, cyl, # temp_sh_roof = temp_sh * (voxelMaps[:, :, i] == 0) # Estimate sunlit and shaded patches - sunlit_patches, shaded_patches = sunlit_shaded_patches.shaded_or_sunlit(solar_altitude, solar_azimuth, patch_altitude[i], patch_azimuth[i], asvf) + sunlit_patches, shaded_patches = ( + sunlit_shaded_patches.shaded_or_sunlit( + solar_altitude, + solar_azimuth, + patch_altitude[i], + patch_azimuth[i], + asvf, + ) + ) if cyl == 1: # Angle of incidence, np.cos(0) because cylinder - always perpendicular - angle_of_incidence = np.cos(patch_altitude[i] * deg2rad) * np.cos(0) # * np.sin(np.pi / 2) \ + angle_of_incidence = np.cos(patch_altitude[i] * deg2rad) * np.cos( + 0 + ) # * np.sin(np.pi / 2) \ # Angle of incidence to horizontal surface angle_of_incidence_h = np.sin(patch_altitude[i] * deg2rad) ### CALCULATIONS FOR LONGWAVE RADIATION ### # Longwave radiation from sky - Lside_sky_temp, Ldown_sky_temp, Least_temp, Lsouth_temp, Lwest_temp, Lnorth_temp = patch_radiation.longwave_from_sky(skyp[i], Lsky_side[i, 2], Lsky_down[i, 2], patch_azimuth[i]) - + ( + Lside_sky_temp, + Ldown_sky_temp, + Least_temp, + Lsouth_temp, + Lwest_temp, + Lnorth_temp, + ) = patch_radiation.longwave_from_sky( + skyp[i], Lsky_side[i, 2], Lsky_down[i, 2], patch_azimuth[i] + ) + Lside_sky += Lside_sky_temp Ldown_sky += Ldown_sky_temp Least += Least_temp @@ -145,26 +209,58 @@ def anisotropic_sky(skyp, buip, vegp, solar_altitude, solar_azimuth, asvf, cyl, Lnorth += Lnorth_temp # Longwave radiation from vegetation - Lside_veg_temp, Ldown_veg_temp, \ - Least_temp, Lsouth_temp, Lwest_temp, Lnorth_temp = patch_radiation.longwave_from_veg(vegp[i], steradians[i], angle_of_incidence, angle_of_incidence_h, - patch_altitude[i], patch_azimuth[i], ewall, Ta) - + ( + Lside_veg_temp, + Ldown_veg_temp, + Least_temp, + Lsouth_temp, + Lwest_temp, + Lnorth_temp, + ) = patch_radiation.longwave_from_veg( + vegp[i], + steradians[i], + angle_of_incidence, + angle_of_incidence_h, + patch_altitude[i], + patch_azimuth[i], + ewall, + Ta, + ) + Lside_veg += Lside_veg_temp Ldown_veg += Ldown_veg_temp Least += Least_temp Lsouth += Lsouth_temp Lwest += Lwest_temp - Lnorth += Lnorth_temp + Lnorth += Lnorth_temp # Longwave radiation from buildings # if wallScheme == 0: azimuth_difference = np.abs(solar_azimuth - patch_azimuth[i]) - Lside_sun_temp, Lside_sh_temp, \ - Ldown_sun_temp, Ldown_sh_temp, \ - Least_temp, Lsouth_temp, Lwest_temp, Lnorth_temp = patch_radiation.longwave_from_buildings(buip[i], steradians[i], angle_of_incidence, angle_of_incidence_h, - patch_azimuth[i], sunlit_patches, shaded_patches, - azimuth_difference, solar_altitude, ewall, Ta, Tgwall) + ( + Lside_sun_temp, + Lside_sh_temp, + Ldown_sun_temp, + Ldown_sh_temp, + Least_temp, + Lsouth_temp, + Lwest_temp, + Lnorth_temp, + ) = patch_radiation.longwave_from_buildings( + buip[i], + steradians[i], + angle_of_incidence, + angle_of_incidence_h, + patch_azimuth[i], + sunlit_patches, + shaded_patches, + azimuth_difference, + solar_altitude, + ewall, + Ta, + Tgwall, + ) # else: # azimuth_difference = np.abs(solar_azimuth - patch_azimuth[i]) @@ -172,13 +268,13 @@ def anisotropic_sky(skyp, buip, vegp, solar_altitude, solar_azimuth, asvf, cyl, # # print('Building pixels wall scheme = ' + str(np.sum(temp_sh_w > 0))) # Lside_sun_temp, Lside_sh_temp, \ # Ldown_sun_temp, Ldown_sh_temp, \ - # Least_temp, Lsouth_temp, Lwest_temp, Lnorth_temp = patch_radiation.longwave_from_buildings_wallScheme(temp_sh_w, voxelTable, steradians[i], angle_of_incidence, angle_of_incidence_h, - # patch_azimuth[i]) + # Least_temp, Lsouth_temp, Lwest_temp, Lnorth_temp = patch_radiation.longwave_from_buildings_wallScheme(temp_sh_w, voxelTable, steradians[i], angle_of_incidence, angle_of_incidence_h, + # patch_azimuth[i]) # Lside_sun_r_temp, Lside_sh_r_temp, \ # Ldown_sun_r_temp, Ldown_sh_r_temp, \ - # Least_r_temp, Lsouth_r_temp, Lwest_r_temp, Lnorth_r_temp = patch_radiation.longwave_from_buildings(temp_sh_roof, steradians[i], angle_of_incidence, angle_of_incidence_h, - # patch_azimuth[i], sunlit_patches, shaded_patches, + # Least_r_temp, Lsouth_r_temp, Lwest_r_temp, Lnorth_r_temp = patch_radiation.longwave_from_buildings(temp_sh_roof, steradians[i], angle_of_incidence, angle_of_incidence_h, + # patch_azimuth[i], sunlit_patches, shaded_patches, # azimuth_difference, solar_altitude, ewall, Ta, Tgwall) # Lside_sun_temp += Lside_sun_r_temp @@ -197,37 +293,99 @@ def anisotropic_sky(skyp, buip, vegp, solar_altitude, solar_azimuth, asvf, cyl, Least += Least_temp Lsouth += Lsouth_temp Lwest += Lwest_temp - Lnorth += Lnorth_temp + Lnorth += Lnorth_temp ### CALCULATIONS FOR SHORTWAVE RADIATION ### if solar_altitude > 0: # Shortwave radiation from sky - KsideD += skyp[i] * lumChi[i] * angle_of_incidence * steradians[i] - + KsideD += ( + skyp[i] * lumChi[i] * angle_of_incidence * steradians[i] + ) + if skyp[i]: - Keast_temp, Ksouth_temp, Kwest_temp, Knorth_temp = patch_radiation.cardinal_shortwave(lumChi[i], angle_of_incidence, skyp[i], steradians[i], 1, patch_azimuth[i], patch_altitude[i]) + Keast_temp, Ksouth_temp, Kwest_temp, Knorth_temp = ( + patch_radiation.cardinal_shortwave( + lumChi[i], + angle_of_incidence, + skyp[i], + steradians[i], + 1, + patch_azimuth[i], + patch_altitude[i], + ) + ) # Shortwave reflected on sunlit surfaces # sunlit_surface = ((albedo * radG) / np.pi) - sunlit_surface = ((albedo * (radI * np.cos(solar_altitude * deg2rad)) + (radD * 0.5)) / np.pi) + sunlit_surface = ( + albedo * (radI * np.cos(solar_altitude * deg2rad)) + + (radD * 0.5) + ) / np.pi # Shortwave reflected on shaded surfaces and vegetation - shaded_surface = ((albedo * radD * 0.5) / np.pi) - + shaded_surface = (albedo * radD * 0.5) / np.pi + # Shortwave radiation from vegetation - Kref_veg += shaded_surface * vegp[i] * steradians[i] * angle_of_incidence - + Kref_veg += ( + shaded_surface + * vegp[i] + * steradians[i] + * angle_of_incidence + ) + if vegp[i]: - Keast_temp, Ksouth_temp, Kwest_temp, Knorth_temp = patch_radiation.cardinal_shortwave(shaded_surface, angle_of_incidence, vegp[i], steradians[i], 1, patch_azimuth[i], patch_altitude[i]) + Keast_temp, Ksouth_temp, Kwest_temp, Knorth_temp = ( + patch_radiation.cardinal_shortwave( + shaded_surface, + angle_of_incidence, + vegp[i], + steradians[i], + 1, + patch_azimuth[i], + patch_altitude[i], + ) + ) # Shortwave radiation from buildings - Kref_sun += sunlit_surface * sunlit_patches * buip[i] * steradians[i] * angle_of_incidence - Kref_sh += shaded_surface * shaded_patches * buip[i] * steradians[i] * angle_of_incidence + Kref_sun += ( + sunlit_surface + * sunlit_patches + * buip[i] + * steradians[i] + * angle_of_incidence + ) + Kref_sh += ( + shaded_surface + * shaded_patches + * buip[i] + * steradians[i] + * angle_of_incidence + ) if buip[i]: if sunlit_patches == 1: - Keast_temp, Ksouth_temp, Kwest_temp, Knorth_temp = patch_radiation.cardinal_shortwave(sunlit_surface, angle_of_incidence, buip[i], steradians[i], sunlit_patches, patch_azimuth[i], patch_altitude[i]) + Keast_temp, Ksouth_temp, Kwest_temp, Knorth_temp = ( + patch_radiation.cardinal_shortwave( + sunlit_surface, + angle_of_incidence, + buip[i], + steradians[i], + sunlit_patches, + patch_azimuth[i], + patch_altitude[i], + ) + ) elif shaded_patches == 1: - Keast_temp, Ksouth_temp, Kwest_temp, Knorth_temp = patch_radiation.cardinal_shortwave(shaded_surface, angle_of_incidence, buip[i], steradians[i], shaded_patches, patch_azimuth[i], patch_altitude[i]) + Keast_temp, Ksouth_temp, Kwest_temp, Knorth_temp = ( + patch_radiation.cardinal_shortwave( + shaded_surface, + angle_of_incidence, + buip[i], + steradians[i], + shaded_patches, + patch_azimuth[i], + patch_altitude[i], + ) + ) Keast += Keast_temp Kwest += Kwest_temp @@ -237,16 +395,33 @@ def anisotropic_sky(skyp, buip, vegp, solar_altitude, solar_azimuth, asvf, cyl, # Calculate reflected longwave in each patch for idx in np.arange(patch_altitude.shape[0]): # Angle of incidence, np.cos(0) because cylinder - always perpendicular - angle_of_incidence = np.cos(patch_altitude[idx] * deg2rad) * np.cos(0) # * np.sin(np.pi / 2) \ + angle_of_incidence = np.cos(patch_altitude[idx] * deg2rad) * np.cos( + 0 + ) # * np.sin(np.pi / 2) \ # Angle of incidence to horizontal surface angle_of_incidence_h = np.sin(patch_altitude[idx] * deg2rad) # Patches with reflected surfaces - temp_sh = ((buip[idx] == 1) | (vegp[idx] == 1)) - - Lside_ref_temp, Ldown_ref_temp, \ - Least_temp, Lsouth_temp, Lwest_temp, Lnorth_temp = patch_radiation.reflected_longwave(temp_sh, steradians[idx], angle_of_incidence, angle_of_incidence_h, patch_azimuth[idx], Ldown_sky, Lup, ewall) + temp_sh = (buip[idx] == 1) | (vegp[idx] == 1) + + ( + Lside_ref_temp, + Ldown_ref_temp, + Least_temp, + Lsouth_temp, + Lwest_temp, + Lnorth_temp, + ) = patch_radiation.reflected_longwave( + temp_sh, + steradians[idx], + angle_of_incidence, + angle_of_incidence_h, + patch_azimuth[idx], + Ldown_sky, + Lup, + ewall, + ) Lside_ref += Lside_ref_temp Ldown_ref += Ldown_ref_temp @@ -262,17 +437,17 @@ def anisotropic_sky(skyp, buip, vegp, solar_altitude, solar_azimuth, asvf, cyl, Ldown = Ldown_sky + Ldown_veg + Ldown_sh + Ldown_sun + Ldown_ref ### Direct radiation ### - if cyl == 1: ### Kside with cylinder ### + if cyl == 1: ### Kside with cylinder ### KsideI = shadow * radI * np.cos(solar_altitude * deg2rad) if solar_altitude > 0: Kside = KsideI + KsideD + Kref_sun + Kref_sh + Kref_veg - Keast_ = (Kup * 0.5) - Kwest_ = (Kup * 0.5) - Knorth_ = (Kup * 0.5) - Ksouth_ = (Kup * 0.5) + Keast_ = Kup * 0.5 + Kwest_ = Kup * 0.5 + Knorth_ = Kup * 0.5 + Ksouth_ = Kup * 0.5 if cyl == 0: Keast_ += Keast @@ -280,7 +455,25 @@ def anisotropic_sky(skyp, buip, vegp, solar_altitude, solar_azimuth, asvf, cyl, Knorth_ += Knorth Ksouth_ += Ksouth - return Ldown, Lside, Lside_sky, Lside_veg, Lside_sh, Lside_sun, Lside_ref, Least, Lwest, Lnorth, Lsouth, \ - Keast_, Ksouth_, Kwest_, Knorth_, KsideI, KsideD, Kside, steradians, skyalt - - \ No newline at end of file + return ( + Ldown, + Lside, + Lside_sky, + Lside_veg, + Lside_sh, + Lside_sun, + Lside_ref, + Least, + Lwest, + Lnorth, + Lsouth, + Keast_, + Ksouth_, + Kwest_, + Knorth_, + KsideI, + KsideD, + Kside, + steradians, + skyalt, + ) diff --git a/functions/TreePlanter/SOLWEIG1D/emissivity_models.py b/functions/TreePlanter/SOLWEIG1D/emissivity_models.py index aff2b0a..1762e50 100644 --- a/functions/TreePlanter/SOLWEIG1D/emissivity_models.py +++ b/functions/TreePlanter/SOLWEIG1D/emissivity_models.py @@ -1,48 +1,50 @@ import numpy as np -''' Model 1 is based on Unsworth & Monteith, 1975 ''' +""" Model 1 is based on Unsworth & Monteith, 1975 """ + + def model1(sky_patches, esky, Ta): # Stefan-Boltzmann's Constant SBC = 5.67051e-8 # Degrees to radians - deg2rad = np.pi / 180 + deg2rad = np.pi / 180 # Unique altitudes in lv, i.e. unique altitude for the patches skyalt, skyalt_c = np.unique(sky_patches[:, 0], return_counts=True) - + # Unique zeniths for the patches - skyzen = 90-skyalt - + skyzen = 90 - skyalt + # Cosine of the zenith angles cosskyzen = np.cos(skyzen * deg2rad) # Estimate emissivities at different altitudes/zenith angles - + # Constants? a_c = 0.67 b_c = 0.094 # Natural log of the reduced depth of precipitable water - ln_u_prec = esky/b_c-a_c/b_c-0.5 - - # Reduced depth of precipitable water - u_prec = np.exp(ln_u_prec) + ln_u_prec = esky / b_c - a_c / b_c - 0.5 + + # Reduced depth of precipitable water + u_prec = np.exp(ln_u_prec) # Optical water depth - owp = u_prec/cosskyzen + owp = u_prec / cosskyzen # Natural log of optical water depth - log_owp = np.log(owp) + log_owp = np.log(owp) # Emissivity of each zenith angle, i.e. the zenith angle of each band of patches - esky_band = a_c+b_c*log_owp - + esky_band = a_c + b_c * log_owp + # Altitudes of the Robinson & Stone patches - p_alt = sky_patches[:,0] - - # Empty array with length based on number of patches + p_alt = sky_patches[:, 0] + + # Empty array with length based on number of patches patch_emissivity = np.zeros((p_alt.shape[0])) # Fill with emissivities @@ -51,34 +53,39 @@ def model1(sky_patches, esky, Ta): patch_emissivity[p_alt == idx] = temp_emissivity # Normalize - patch_emissivity_normalized = patch_emissivity/np.sum(patch_emissivity) + patch_emissivity_normalized = patch_emissivity / np.sum(patch_emissivity) return patch_emissivity_normalized, esky_band -''' Model 2 is based on Martin & Berdhal, 1984 - Ez = 1 - (1 - Es)*e**(b2*(1.7 - (1/np.cos(z)))) ''' + +""" Model 2 is based on Martin & Berdhal, 1984 + Ez = 1 - (1 - Es)*e**(b2*(1.7 - (1/np.cos(z)))) """ + + def model2(sky_patches, esky, Ta): # Degrees to radians - deg2rad = np.pi / 180 + deg2rad = np.pi / 180 # Unique altitudes in lv, i.e. unique altitude for the patches skyalt, skyalt_c = np.unique(sky_patches[:, 0], return_counts=True) - + # Unique zeniths for the patches - skyzen = 90-skyalt - + skyzen = 90 - skyalt + # Constant (Ångström, 1915), proposed by Nahon et al., 2019 b_c = 0.308 # b_c = 0.1 # Estimate emissivites at different altitudes/zenith angles - esky_band = 1 - (1 - esky) * np.exp(b_c * (1.7 - (1 / np.cos(skyzen * deg2rad)))) - + esky_band = 1 - (1 - esky) * np.exp( + b_c * (1.7 - (1 / np.cos(skyzen * deg2rad))) + ) + # Altitudes of the Robinson & Stone patches - p_alt = sky_patches[:,0] - - # Empty array with length based on number of patches + p_alt = sky_patches[:, 0] + + # Empty array with length based on number of patches patch_emissivity = np.zeros((p_alt.shape[0])) # Fill with emissivities @@ -87,35 +94,38 @@ def model2(sky_patches, esky, Ta): patch_emissivity[p_alt == idx] = temp_emissivity # Normalize - patch_emissivity_normalized = patch_emissivity/np.sum(patch_emissivity) + patch_emissivity_normalized = patch_emissivity / np.sum(patch_emissivity) return patch_emissivity_normalized, esky_band -''' Model 3 is based on Bliss, 1961. - Ez = 1 - (1 - Es)**(1/(b1*np.cos(z))) ''' + +""" Model 3 is based on Bliss, 1961. + Ez = 1 - (1 - Es)**(1/(b1*np.cos(z))) """ + + def model3(sky_patches, esky, Ta): # Degrees to radians - deg2rad = np.pi / 180 + deg2rad = np.pi / 180 # Unique altitudes in lv, i.e. unique altitude for the patches skyalt, skyalt_c = np.unique(sky_patches[:, 0], return_counts=True) - + # Unique zeniths for the patches - skyzen = 90-skyalt - + skyzen = 90 - skyalt + # Constant, can (should?) be changed. Model gave unsatisfactory results in Nahon et al., 2019 b_c = 1.8 # Estimate emissivites at different altitudes/zenith angles - esky_band = 1 - (1 - esky)**(1/(b_c * np.cos(skyzen * deg2rad))) + esky_band = 1 - (1 - esky) ** (1 / (b_c * np.cos(skyzen * deg2rad))) # Estimating longwave radiation for each patch to a horizontal or vertical surface - + # Altitudes of the Robinson & Stone patches - p_alt = sky_patches[:,0] - - # Empty array with length based on number of patches + p_alt = sky_patches[:, 0] + + # Empty array with length based on number of patches patch_emissivity = np.zeros((p_alt.shape[0])) # Fill with emissivities @@ -124,6 +134,6 @@ def model3(sky_patches, esky, Ta): patch_emissivity[p_alt == idx] = temp_emissivity # Normalize - patch_emissivity_normalized = patch_emissivity/np.sum(patch_emissivity) + patch_emissivity_normalized = patch_emissivity / np.sum(patch_emissivity) - return patch_emissivity_normalized, esky_band \ No newline at end of file + return patch_emissivity_normalized, esky_band diff --git a/functions/TreePlanter/SOLWEIG1D/patch_radiation.py b/functions/TreePlanter/SOLWEIG1D/patch_radiation.py index 5d8ed5a..c031d86 100644 --- a/functions/TreePlanter/SOLWEIG1D/patch_radiation.py +++ b/functions/TreePlanter/SOLWEIG1D/patch_radiation.py @@ -1,20 +1,24 @@ import numpy as np -def shortwave_from_sky(sky, angle_of_incidence, lumChi, steradian, patch_azimuth, cyl): - '''Calculates the amount of diffuse shortwave radiation from the sky for a patch with: + +def shortwave_from_sky( + sky, angle_of_incidence, lumChi, steradian, patch_azimuth, cyl +): + """Calculates the amount of diffuse shortwave radiation from the sky for a patch with: angle of incidence = angle_of_incidence luminance = lumChi - steradian = steradian''' + steradian = steradian""" # Diffuse vertical radiation diffuse_shortwave_radiation = sky * lumChi * angle_of_incidence * steradian return diffuse_shortwave_radiation + def longwave_from_sky(sky, Lsky_side, Lsky_down, patch_azimuth): # Degrees to radians - deg2rad = np.pi / 180 + deg2rad = np.pi / 180 # Longwave radiation from sky to vertical surface Ldown_sky = sky * Lsky_down @@ -40,27 +44,41 @@ def longwave_from_sky(sky, Lsky_side, Lsky_down, patch_azimuth): return Lside_sky, Ldown_sky, Least, Lsouth, Lwest, Lnorth -def longwave_from_veg(vegetation, steradian, angle_of_incidence, angle_of_incidence_h, patch_altitude, patch_azimuth, ewall, Ta): - '''Calculates the amount of longwave radiation from vegetation for a patch with: + +def longwave_from_veg( + vegetation, + steradian, + angle_of_incidence, + angle_of_incidence_h, + patch_altitude, + patch_azimuth, + ewall, + Ta, +): + """Calculates the amount of longwave radiation from vegetation for a patch with: angle of incidence = angle_of_incidence steradian = steradian if a patch is vegetation = vegetation - amount of radiation from vegetated patch = vegetation_surface''' + amount of radiation from vegetated patch = vegetation_surface""" # Stefan-Boltzmann's Constant SBC = 5.67051e-8 # Degrees to radians - deg2rad = np.pi / 180 - + deg2rad = np.pi / 180 + # Longwave radiation from vegetation surface (considered vertical) - vegetation_surface = ((ewall * SBC * ((Ta + 273.15) ** 4)) / np.pi) + vegetation_surface = (ewall * SBC * ((Ta + 273.15) ** 4)) / np.pi # Longwave radiation reaching a vertical surface - Lside_veg = vegetation_surface * steradian * angle_of_incidence * vegetation + Lside_veg = ( + vegetation_surface * steradian * angle_of_incidence * vegetation + ) # Longwave radiation reaching a horizontal surface - Ldown_veg = vegetation_surface * steradian * angle_of_incidence_h * vegetation + Ldown_veg = ( + vegetation_surface * steradian * angle_of_incidence_h * vegetation + ) # Least = 0 @@ -70,23 +88,61 @@ def longwave_from_veg(vegetation, steradian, angle_of_incidence, angle_of_incide # Portion into cardinal directions to be used for standing box or POI output if (patch_azimuth > 360) or (patch_azimuth < 180): - Least = vegetation_surface * steradian * np.cos(patch_altitude * deg2rad) * vegetation * np.cos((90 - patch_azimuth) * deg2rad) + Least = ( + vegetation_surface + * steradian + * np.cos(patch_altitude * deg2rad) + * vegetation + * np.cos((90 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 90) and (patch_azimuth < 270): - Lsouth = vegetation_surface * steradian * np.cos(patch_altitude * deg2rad) * vegetation * np.cos((180 - patch_azimuth) * deg2rad) + Lsouth = ( + vegetation_surface + * steradian + * np.cos(patch_altitude * deg2rad) + * vegetation + * np.cos((180 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 180) and (patch_azimuth < 360): - Lwest = vegetation_surface * steradian * np.cos(patch_altitude * deg2rad) * vegetation * np.cos((270 - patch_azimuth) * deg2rad) + Lwest = ( + vegetation_surface + * steradian + * np.cos(patch_altitude * deg2rad) + * vegetation + * np.cos((270 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 270) or (patch_azimuth < 90): - Lnorth = vegetation_surface * steradian * np.cos(patch_altitude * deg2rad) * vegetation * np.cos((0 - patch_azimuth) * deg2rad) + Lnorth = ( + vegetation_surface + * steradian + * np.cos(patch_altitude * deg2rad) + * vegetation + * np.cos((0 - patch_azimuth) * deg2rad) + ) return Lside_veg, Ldown_veg, Least, Lsouth, Lwest, Lnorth -def longwave_from_buildings(building, steradian, angle_of_incidence, angle_of_incidence_h, patch_azimuth, sunlit_patches, shaded_patches, azimuth_difference, solar_altitude, ewall, Ta, Tgwall): + +def longwave_from_buildings( + building, + steradian, + angle_of_incidence, + angle_of_incidence_h, + patch_azimuth, + sunlit_patches, + shaded_patches, + azimuth_difference, + solar_altitude, + ewall, + Ta, + Tgwall, +): # Stefan-Boltzmann's Constant SBC = 5.67051e-8 # Degrees to radians - deg2rad = np.pi / 180 + deg2rad = np.pi / 180 # Least = 0 @@ -95,71 +151,208 @@ def longwave_from_buildings(building, steradian, angle_of_incidence, angle_of_in Lnorth = 0 # Longwave radiation from sunlit surfaces - sunlit_surface = ((ewall * SBC * ((Ta + Tgwall + 273.15) ** 4)) / np.pi) + sunlit_surface = (ewall * SBC * ((Ta + Tgwall + 273.15) ** 4)) / np.pi # Longwave radiation from shaded surfaces - shaded_surface = ((ewall * SBC * ((Ta + 273.15) ** 4)) / np.pi) - if ((azimuth_difference > 90) and (azimuth_difference < 270) and (solar_altitude > 0)): + shaded_surface = (ewall * SBC * ((Ta + 273.15) ** 4)) / np.pi + if ( + (azimuth_difference > 90) + and (azimuth_difference < 270) + and (solar_altitude > 0) + ): # Calculate which patches defined as buildings that are sunlit or shaded # sunlit_patches, shaded_patches = sunlit_shaded_patches.shaded_or_sunlit(solar_altitude, solar_azimuth, patch_altitude, patch_azimuth, asvf) - + # Calculate longwave radiation from sunlit walls to vertical surface - Lside_sun = sunlit_surface * sunlit_patches * steradian * angle_of_incidence * building + Lside_sun = ( + sunlit_surface + * sunlit_patches + * steradian + * angle_of_incidence + * building + ) # Calculate longwave radiation from shaded walls to vertical surface - Lside_sh = shaded_surface * shaded_patches * steradian * angle_of_incidence * building - + Lside_sh = ( + shaded_surface + * shaded_patches + * steradian + * angle_of_incidence + * building + ) + # Calculate longwave radiation from sunlit walls to horizontal surface - Ldown_sun = sunlit_surface * sunlit_patches * steradian * angle_of_incidence_h * building + Ldown_sun = ( + sunlit_surface + * sunlit_patches + * steradian + * angle_of_incidence_h + * building + ) # Calculate longwave radiation from shaded walls to horizontal surface - Ldown_sh = shaded_surface * shaded_patches * steradian * angle_of_incidence_h * building - + Ldown_sh = ( + shaded_surface + * shaded_patches + * steradian + * angle_of_incidence_h + * building + ) + # Portion into cardinal directions to be used for standing box or POI output if (patch_azimuth > 360) or (patch_azimuth < 180): - Least += sunlit_surface * sunlit_patches * steradian * angle_of_incidence * building * np.cos((90 - patch_azimuth) * deg2rad) - Least += shaded_surface * shaded_patches * steradian * angle_of_incidence * building * np.cos((90 - patch_azimuth) * deg2rad) + Least += ( + sunlit_surface + * sunlit_patches + * steradian + * angle_of_incidence + * building + * np.cos((90 - patch_azimuth) * deg2rad) + ) + Least += ( + shaded_surface + * shaded_patches + * steradian + * angle_of_incidence + * building + * np.cos((90 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 90) and (patch_azimuth < 270): - Lsouth += sunlit_surface * sunlit_patches * steradian * angle_of_incidence * building * np.cos((180 - patch_azimuth) * deg2rad) - Lsouth += shaded_surface * shaded_patches * steradian * angle_of_incidence * building * np.cos((180 - patch_azimuth) * deg2rad) + Lsouth += ( + sunlit_surface + * sunlit_patches + * steradian + * angle_of_incidence + * building + * np.cos((180 - patch_azimuth) * deg2rad) + ) + Lsouth += ( + shaded_surface + * shaded_patches + * steradian + * angle_of_incidence + * building + * np.cos((180 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 180) and (patch_azimuth < 360): - Lwest += sunlit_surface * sunlit_patches * steradian * angle_of_incidence * building * np.cos((270 - patch_azimuth) * deg2rad) - Lwest += shaded_surface * shaded_patches * steradian * angle_of_incidence * building * np.cos((270 - patch_azimuth) * deg2rad) + Lwest += ( + sunlit_surface + * sunlit_patches + * steradian + * angle_of_incidence + * building + * np.cos((270 - patch_azimuth) * deg2rad) + ) + Lwest += ( + shaded_surface + * shaded_patches + * steradian + * angle_of_incidence + * building + * np.cos((270 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 270) or (patch_azimuth < 90): - Lnorth += sunlit_surface * sunlit_patches * steradian * angle_of_incidence * building * np.cos((0 - patch_azimuth) * deg2rad) - Lnorth += shaded_surface * shaded_patches * steradian * angle_of_incidence * building * np.cos((0 - patch_azimuth) * deg2rad) + Lnorth += ( + sunlit_surface + * sunlit_patches + * steradian + * angle_of_incidence + * building + * np.cos((0 - patch_azimuth) * deg2rad) + ) + Lnorth += ( + shaded_surface + * shaded_patches + * steradian + * angle_of_incidence + * building + * np.cos((0 - patch_azimuth) * deg2rad) + ) else: # Calculate longwave radiation from shaded walls reaching a vertical surface Lside_sh = shaded_surface * steradian * angle_of_incidence * building Lside_sun = 0 - + # Calculate longwave radiation from shaded walls reaching a horizontal surface Ldown_sh = shaded_surface * steradian * angle_of_incidence_h * building Ldown_sun = 0 # Portion into cardinal directions to be used for standing box or POI output if (patch_azimuth > 360) or (patch_azimuth < 180): - Least = shaded_surface * steradian * angle_of_incidence * building * np.cos((90 - patch_azimuth) * deg2rad) + Least = ( + shaded_surface + * steradian + * angle_of_incidence + * building + * np.cos((90 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 90) and (patch_azimuth < 270): - Lsouth = shaded_surface * steradian * angle_of_incidence * building * np.cos((180 - patch_azimuth) * deg2rad) + Lsouth = ( + shaded_surface + * steradian + * angle_of_incidence + * building + * np.cos((180 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 180) and (patch_azimuth < 360): - Lwest = shaded_surface * steradian * angle_of_incidence * building * np.cos((270 - patch_azimuth) * deg2rad) + Lwest = ( + shaded_surface + * steradian + * angle_of_incidence + * building + * np.cos((270 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 270) or (patch_azimuth < 90): - Lnorth = shaded_surface * steradian * angle_of_incidence * building * np.cos((0 - patch_azimuth) * deg2rad) - - return Lside_sun, Lside_sh, Ldown_sun, Ldown_sh, Least, Lsouth, Lwest, Lnorth - -def reflected_longwave(reflecting_surface, steradian, angle_of_incidence, angle_of_incidence_h, patch_azimuth, Ldown_sky, Lup, ewall): + Lnorth = ( + shaded_surface + * steradian + * angle_of_incidence + * building + * np.cos((0 - patch_azimuth) * deg2rad) + ) + + return ( + Lside_sun, + Lside_sh, + Ldown_sun, + Ldown_sh, + Least, + Lsouth, + Lwest, + Lnorth, + ) + + +def reflected_longwave( + reflecting_surface, + steradian, + angle_of_incidence, + angle_of_incidence_h, + patch_azimuth, + Ldown_sky, + Lup, + ewall, +): # Degrees to radians - deg2rad = np.pi / 180 + deg2rad = np.pi / 180 # Calculate reflected longwave in each patch - reflected_radiation = (((Ldown_sky+Lup) * (1-ewall)*0.5) / np.pi) + reflected_radiation = ((Ldown_sky + Lup) * (1 - ewall) * 0.5) / np.pi # Reflected longwave radiation reaching vertical surfaces - Lside_ref = reflected_radiation * steradian * angle_of_incidence * reflecting_surface - + Lside_ref = ( + reflected_radiation + * steradian + * angle_of_incidence + * reflecting_surface + ) + # Reflected longwave radiation reaching horizontal surfaces - Ldown_ref = reflected_radiation * steradian * angle_of_incidence_h * reflecting_surface + Ldown_ref = ( + reflected_radiation + * steradian + * angle_of_incidence_h + * reflecting_surface + ) # Least = 0 @@ -169,21 +362,46 @@ def reflected_longwave(reflecting_surface, steradian, angle_of_incidence, angle_ # Portion into cardinal directions to be used for standing box or POI output if (patch_azimuth > 360) or (patch_azimuth < 180): - Least = reflected_radiation * steradian * angle_of_incidence * reflecting_surface * np.cos((90 - patch_azimuth) * deg2rad) + Least = ( + reflected_radiation + * steradian + * angle_of_incidence + * reflecting_surface + * np.cos((90 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 90) and (patch_azimuth < 270): - Lsouth = reflected_radiation * steradian * angle_of_incidence * reflecting_surface * np.cos((180 - patch_azimuth) * deg2rad) + Lsouth = ( + reflected_radiation + * steradian + * angle_of_incidence + * reflecting_surface + * np.cos((180 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 180) and (patch_azimuth < 360): - Lwest = reflected_radiation * steradian * angle_of_incidence * reflecting_surface * np.cos((270 - patch_azimuth) * deg2rad) + Lwest = ( + reflected_radiation + * steradian + * angle_of_incidence + * reflecting_surface + * np.cos((270 - patch_azimuth) * deg2rad) + ) if (patch_azimuth > 270) or (patch_azimuth < 90): - Lnorth = reflected_radiation * steradian * angle_of_incidence * reflecting_surface * np.cos((0 - patch_azimuth) * deg2rad) + Lnorth = ( + reflected_radiation + * steradian + * angle_of_incidence + * reflecting_surface + * np.cos((0 - patch_azimuth) * deg2rad) + ) return Lside_ref, Ldown_ref, Least, Lsouth, Lwest, Lnorth + def patch_steradians(L_patches): - ''''This function calculates the steradians of the patches''' + """'This function calculates the steradians of the patches""" # Degrees to radians - deg2rad = np.pi / 180 + deg2rad = np.pi / 180 # Unique altitudes for patches skyalt, skyalt_c = np.unique(L_patches[:, 0], return_counts=True) @@ -196,24 +414,42 @@ def patch_steradians(L_patches): for i in range(patch_altitude.shape[0]): # If there are more than one patch in a band if skyalt_c[skyalt == patch_altitude[i]] > 1: - steradian[i] = ((360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad) * (np.sin((patch_altitude[i] + patch_altitude[0]) * deg2rad) \ - - np.sin((patch_altitude[i] - patch_altitude[0]) * deg2rad)) + steradian[i] = ( + (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad + ) * ( + np.sin((patch_altitude[i] + patch_altitude[0]) * deg2rad) + - np.sin((patch_altitude[i] - patch_altitude[0]) * deg2rad) + ) # If there is only one patch in band, i.e. 90 degrees else: - steradian[i] = ((360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad) * (np.sin((patch_altitude[i]) * deg2rad) \ - - np.sin((patch_altitude[i-1] + patch_altitude[0]) * deg2rad)) - + steradian[i] = ( + (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad + ) * ( + np.sin((patch_altitude[i]) * deg2rad) + - np.sin((patch_altitude[i - 1] + patch_altitude[0]) * deg2rad) + ) + return steradian, skyalt, patch_altitude -def cardinal_shortwave(radiation, angle_of_incidence, patch_type, steradian, sunshade, patch_azimuth, patch_altitude): - '''Function to add shortwave radiation to cardinal directions - + +def cardinal_shortwave( + radiation, + angle_of_incidence, + patch_type, + steradian, + sunshade, + patch_azimuth, + patch_altitude, +): + """Function to add shortwave radiation to cardinal directions + radiation = type of radiation, e.g. reflected from vegetation, reflected from buildings, diffuse angle_of_incidence = angle of incidence of patch patch_type = building, vegetation, sky and if the patch is of any of these types steradian = steradian of patch, i.e. "area of patch" sunshade = if patch is sunlit or in shade (only for buildings), otherwise set to 1 - patch_azimuth = azimuth of patch to determine from which cardinal direction the radiation originates''' + patch_azimuth = azimuth of patch to determine from which cardinal direction the radiation originates + """ # Degrees to radians deg2rad = np.pi / 180 @@ -225,16 +461,40 @@ def cardinal_shortwave(radiation, angle_of_incidence, patch_type, steradian, sun # Portion into cardinal directions to be used for standing box or POI output if (patch_azimuth > 360) or (patch_azimuth < 180): - angle_of_incidence = np.cos(patch_altitude * deg2rad) * np.cos((90 - patch_azimuth) * deg2rad) * np.sin(np.pi / 2) + np.sin(patch_altitude * deg2rad) * np.cos(np.pi / 2) - Keast = radiation * steradian * angle_of_incidence * patch_type * sunshade + angle_of_incidence = np.cos(patch_altitude * deg2rad) * np.cos( + (90 - patch_azimuth) * deg2rad + ) * np.sin(np.pi / 2) + np.sin(patch_altitude * deg2rad) * np.cos( + np.pi / 2 + ) + Keast = ( + radiation * steradian * angle_of_incidence * patch_type * sunshade + ) if (patch_azimuth > 90) and (patch_azimuth < 270): - angle_of_incidence = np.cos(patch_altitude * deg2rad) * np.cos((180 - patch_azimuth) * deg2rad) * np.sin(np.pi / 2) + np.sin(patch_altitude * deg2rad) * np.cos(np.pi / 2) - Ksouth = radiation * steradian * angle_of_incidence * patch_type * sunshade + angle_of_incidence = np.cos(patch_altitude * deg2rad) * np.cos( + (180 - patch_azimuth) * deg2rad + ) * np.sin(np.pi / 2) + np.sin(patch_altitude * deg2rad) * np.cos( + np.pi / 2 + ) + Ksouth = ( + radiation * steradian * angle_of_incidence * patch_type * sunshade + ) if (patch_azimuth > 180) and (patch_azimuth < 360): - angle_of_incidence = np.cos(patch_altitude * deg2rad) * np.cos((270 - patch_azimuth) * deg2rad) * np.sin(np.pi / 2) + np.sin(patch_altitude * deg2rad) * np.cos(np.pi / 2) - Kwest = radiation * steradian * angle_of_incidence * patch_type * sunshade + angle_of_incidence = np.cos(patch_altitude * deg2rad) * np.cos( + (270 - patch_azimuth) * deg2rad + ) * np.sin(np.pi / 2) + np.sin(patch_altitude * deg2rad) * np.cos( + np.pi / 2 + ) + Kwest = ( + radiation * steradian * angle_of_incidence * patch_type * sunshade + ) if (patch_azimuth > 270) or (patch_azimuth < 90): - angle_of_incidence = np.cos(patch_altitude * deg2rad) * np.cos((0 - patch_azimuth) * deg2rad) * np.sin(np.pi / 2) + np.sin(patch_altitude * deg2rad) * np.cos(np.pi / 2) - Knorth = radiation * steradian * angle_of_incidence * patch_type * sunshade - - return Keast, Ksouth, Kwest, Knorth \ No newline at end of file + angle_of_incidence = np.cos(patch_altitude * deg2rad) * np.cos( + (0 - patch_azimuth) * deg2rad + ) * np.sin(np.pi / 2) + np.sin(patch_altitude * deg2rad) * np.cos( + np.pi / 2 + ) + Knorth = ( + radiation * steradian * angle_of_incidence * patch_type * sunshade + ) + + return Keast, Ksouth, Kwest, Knorth diff --git a/functions/TreePlanter/SOLWEIG1D/sunlit_shaded_patches.py b/functions/TreePlanter/SOLWEIG1D/sunlit_shaded_patches.py index c935c08..4c6e75a 100644 --- a/functions/TreePlanter/SOLWEIG1D/sunlit_shaded_patches.py +++ b/functions/TreePlanter/SOLWEIG1D/sunlit_shaded_patches.py @@ -1,23 +1,26 @@ import numpy as np -''' This function calculates whether a point is sunlit or shaded - based on a sky view factor (in a cylinder), solar altitude, solar azimuth ''' +""" This function calculates whether a point is sunlit or shaded + based on a sky view factor (in a cylinder), solar altitude, solar azimuth """ -def shaded_or_sunlit(solar_altitude, solar_azimuth, patch_altitude, patch_azimuth, asvf): + +def shaded_or_sunlit( + solar_altitude, solar_azimuth, patch_altitude, patch_azimuth, asvf +): # Patch azimuth in relation to sun azimuth patch_to_sun_azi = np.abs(solar_azimuth - patch_azimuth) # Degrees to radians - deg2rad = np.pi/180 - + deg2rad = np.pi / 180 + # Radians to degrees - rad2deg = 180/np.pi + rad2deg = 180 / np.pi - # + # xi = np.cos(patch_to_sun_azi * deg2rad) - # + # yi = 2 * xi * np.tan(solar_altitude * deg2rad) hsvf = np.tan(asvf) @@ -27,7 +30,7 @@ def shaded_or_sunlit(solar_altitude, solar_azimuth, patch_altitude, patch_azimut else: yi_ = yi - # + # tan_delta = hsvf + yi_ # Degrees where below is in shade and above is sunlit @@ -38,4 +41,4 @@ def shaded_or_sunlit(solar_altitude, solar_azimuth, patch_altitude, patch_azimut # Boolean for pixels where patch is shaded shaded_patches = sunlit_degrees > patch_altitude - return sunlit_patches, shaded_patches \ No newline at end of file + return sunlit_patches, shaded_patches diff --git a/functions/TreePlanter/TreeGeneratorTempold/makevegdems.py b/functions/TreePlanter/TreeGeneratorTempold/makevegdems.py index e59e921..27c3541 100644 --- a/functions/TreePlanter/TreeGeneratorTempold/makevegdems.py +++ b/functions/TreePlanter/TreeGeneratorTempold/makevegdems.py @@ -1,8 +1,23 @@ from builtins import range import numpy as np + # import matplotlib.pylab as plt -def vegunitsgeneration(buildings, vegdem, vegdem2, ttype, height, trunk, dia, rowa, cola,sizex, sizey, scale): + +def vegunitsgeneration( + buildings, + vegdem, + vegdem2, + ttype, + height, + trunk, + dia, + rowa, + cola, + sizex, + sizey, + scale, +): # This function creates the shape of each vegetation unit and locates it a grid. vegdemtemp = np.zeros([sizey, sizex]) @@ -15,7 +30,7 @@ def vegunitsgeneration(buildings, vegdem, vegdem2, ttype, height, trunk, dia, ro trees = circle * (trees + trunk) treetrunkunder = trunk * circle - else: #ttype == 2 # desiduous tree + else: # ttype == 2 # desiduous tree canopy = 1 - ((1 - trees) ** 2) trees = canopy * (height - trunk) circle = imcircle(dia) @@ -55,24 +70,70 @@ def vegunitsgeneration(buildings, vegdem, vegdem2, ttype, height, trunk, dia, ro if colcutmin == 0: colcutmin = 0 - if row1 < 1 or col1 < 1 or row1 + rowmax - 1 > vegdem.shape[0] or col1 + rowmax - 1 > vegdem.shape[1]: + if ( + row1 < 1 + or col1 < 1 + or row1 + rowmax - 1 > vegdem.shape[0] + or col1 + rowmax - 1 > vegdem.shape[1] + ): # cutting tree at dem edge - trees = trees[int(rowcutmin):int(rowcutmax), int(colcutmin):int(colcutmax)] - treetrunkunder = treetrunkunder[int(rowcutmin): int(rowcutmax), int(colcutmin): int(colcutmax)] - vegdemtemp[int(rowmin):int(rowmin + trees.shape[0]), int(colmin):int(colmin + trees.shape[1])] = trees - vegdem2temp[int(rowmin):int(rowmin + trees.shape[0]), int(colmin):int(colmin + trees.shape[1])] = treetrunkunder + trees = trees[ + int(rowcutmin) : int(rowcutmax), int(colcutmin) : int(colcutmax) + ] + treetrunkunder = treetrunkunder[ + int(rowcutmin) : int(rowcutmax), int(colcutmin) : int(colcutmax) + ] + vegdemtemp[ + int(rowmin) : int(rowmin + trees.shape[0]), + int(colmin) : int(colmin + trees.shape[1]), + ] = trees + vegdem2temp[ + int(rowmin) : int(rowmin + trees.shape[0]), + int(colmin) : int(colmin + trees.shape[1]), + ] = treetrunkunder else: # no cutting of tree at dem edge - vegdemtemp[int(rowmin):int(rowmin + rowmax), int(colmin):int(colmin + colmax)] = trees - vegdem2temp[int(rowmin):int(rowmin + rowmax), int(colmin):int(colmin + colmax)] = treetrunkunder + vegdemtemp[ + int(rowmin) : int(rowmin + rowmax), + int(colmin) : int(colmin + colmax), + ] = trees + vegdem2temp[ + int(rowmin) : int(rowmin + rowmax), + int(colmin) : int(colmin + colmax), + ] = treetrunkunder if ttype == 0: # remove trees - if row1 < 1 or col1 < 1 or row1 + rowmax - 1 > vegdem.shape[0] or col1 + rowmax - 1 > vegdem.shape[1]: - vegdemtemp[int(rowmin):int(rowmin + trees.shape[0]), int(colmin):int(colmin + trees.shape[1])] = trees * 0 - vegdem2temp[int(rowmin):int(rowmin + trees.shape[0]), int(colmin):int(colmin + trees.shape[1])] = treetrunkunder * 0 + if ( + row1 < 1 + or col1 < 1 + or row1 + rowmax - 1 > vegdem.shape[0] + or col1 + rowmax - 1 > vegdem.shape[1] + ): + vegdemtemp[ + int(rowmin) : int(rowmin + trees.shape[0]), + int(colmin) : int(colmin + trees.shape[1]), + ] = ( + trees * 0 + ) + vegdem2temp[ + int(rowmin) : int(rowmin + trees.shape[0]), + int(colmin) : int(colmin + trees.shape[1]), + ] = ( + treetrunkunder * 0 + ) else: - vegdemtemp[int(rowmin):int(rowmin + rowmax), int(colmin):int(colmin + colmax)] = trees * 0 - vegdem2temp[int(rowmin):int(rowmin + rowmax), int(colmin):int(colmin + colmax)] = treetrunkunder * 0 + vegdemtemp[ + int(rowmin) : int(rowmin + rowmax), + int(colmin) : int(colmin + colmax), + ] = ( + trees * 0 + ) + vegdem2temp[ + int(rowmin) : int(rowmin + rowmax), + int(colmin) : int(colmin + colmax), + ] = ( + treetrunkunder * 0 + ) else: # add trees vegdem = np.maximum(vegdem, vegdemtemp) vegdem2temp[vegdemtemp == 0] = -1000 @@ -93,10 +154,13 @@ def conifertree(dia): index = 1 while dia - index * 2 >= 1: if dia - index * 2 >= 2: - circle2 = imcircle(dia-index*2) + circle2 = imcircle(dia - index * 2) circle3 = np.zeros([circle.shape[0], circle.shape[0]]) - circle3[index:circle2.shape[0]+index, index:circle2.shape[0]+index] = circle2 - circle = circle+circle3 + circle3[ + index : circle2.shape[0] + index, + index : circle2.shape[0] + index, + ] = circle2 + circle = circle + circle3 index = index + 1 if dia - index * 2 == 1: @@ -105,7 +169,7 @@ def conifertree(dia): circle = circle + circle3 index = index + 1 - tree = circle/np.max(circle) + tree = circle / np.max(circle) return tree @@ -129,10 +193,10 @@ def imcircle(n): for i in range(0, int(height_45)): upward = i + 1 - 0.5 sine = upward / radius - cosine = np.sqrt(1 - sine ** 2) + cosine = np.sqrt(1 - sine**2) width[0, i] = np.ceil(cosine * radius) - array = width[0, 0:int(height_45)] - height_45 + array = width[0, 0 : int(height_45)] - height_45 for j in range(int(np.max(array)), int(np.min(array)) - 1, -1): width[0, int(height_45 + j - 1)] = np.max(np.where(array == j)) + 1 @@ -140,14 +204,18 @@ def imcircle(n): if np.min(width) == 0: ind = np.where(width == 0) index = ind[1] - width[0, index] = np.round(np.mean([width[0, index - 1], width[0, index + 1]])) + width[0, index] = np.round( + np.mean([width[0, index - 1], width[0, index + 1]]) + ) width = np.append(np.fliplr(width), width, axis=1) for k in range(0, int(DIAMETER)): - semicircle[k, 0:int(width[0, k])] = np.ones([1, int(width[0, k])]) + semicircle[k, 0 : int(width[0, k])] = np.ones( + [1, int(width[0, k])] + ) - y = np.append(np.fliplr(semicircle), semicircle,axis=1) + y = np.append(np.fliplr(semicircle), semicircle, axis=1) else: # odd n DIAMETER = n @@ -161,10 +229,10 @@ def imcircle(n): for i in range(0, int(height_45)): upward = i + 1 sine = upward / radius - cosine = np.sqrt(1 - sine ** 2) + cosine = np.sqrt(1 - sine**2) width[0, i] = np.ceil(cosine * radius - 0.5) - array = width[0, 0:int(height_45)] - height_45 + array = width[0, 0 : int(height_45)] - height_45 for j in range(int(np.max(array)), int(np.min(array)) - 1, -1): width[0, int(height_45 + j - 1)] = np.max(np.where(array == j)) + 1 @@ -172,17 +240,23 @@ def imcircle(n): if np.min(width) == 0: ind = np.where(width == 0) index = ind[1] - width[0, index] = np.round(np.mean([width[0, index - 1], width[0, index + 1]])) + width[0, index] = np.round( + np.mean([width[0, index - 1], width[0, index + 1]]) + ) - width1 = np.append(np.fliplr(width), np.ones([1, 1]) * np.max(width), axis=1) - width = np.append(width1, width,axis=1) + width1 = np.append( + np.fliplr(width), np.ones([1, 1]) * np.max(width), axis=1 + ) + width = np.append(width1, width, axis=1) for k in range(0, int(DIAMETER)): - semicircle[k, 0:int(width[0, k])] = np.ones([1, int(width[0, k])]) + semicircle[k, 0 : int(width[0, k])] = np.ones( + [1, int(width[0, k])] + ) - y = np.append(np.fliplr(semicircle), np.ones([int(DIAMETER), 1]), axis=1) + y = np.append( + np.fliplr(semicircle), np.ones([int(DIAMETER), 1]), axis=1 + ) y = np.append(y, semicircle, axis=1) return y - - diff --git a/functions/TreePlanter/TreePlanter/GreedyAlgorithm.py b/functions/TreePlanter/TreePlanter/GreedyAlgorithm.py index b830296..d274224 100644 --- a/functions/TreePlanter/TreePlanter/GreedyAlgorithm.py +++ b/functions/TreePlanter/TreePlanter/GreedyAlgorithm.py @@ -1,15 +1,19 @@ import numpy as np + # from ..TreeGenerator import makevegdems from ..TreePlanter.TreePlanterTreeshade import tree_slice -def greedyplanter(treeinput,treedata,treerasters,tmrt_1d,trees,feedback): - treeinput.tmrt_s = treeinput.tmrt_s * treeinput.buildings # Remove all Tmrt values that are in shade or on top of buildings +def greedyplanter(treeinput, treedata, treerasters, tmrt_1d, trees, feedback): + + treeinput.tmrt_s = ( + treeinput.tmrt_s * treeinput.buildings + ) # Remove all Tmrt values that are in shade or on top of buildings bld_copy = treeinput.buildings.copy() # Creating boolean for where it is possible to plant a tree - bd_b = np.int_( np.ceil( (treedata.dia / 2) / treeinput.gt[1] ) ) + bd_b = np.int_(np.ceil((treedata.dia / 2) / treeinput.gt[1])) # Buffer on building raster so that trees can't be planted next to walls. Can be planted one radius from walls. for i1 in range(bd_b): @@ -17,7 +21,7 @@ def greedyplanter(treeinput,treedata,treerasters,tmrt_1d,trees,feedback): domain = np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) for i in range(1, treeinput.cols - 1): for j in range(1, treeinput.rows - 1): - dom = bld_copy[j - 1:j + 2, i - 1:i + 2] + dom = bld_copy[j - 1 : j + 2, i - 1 : i + 2] walls[j, i] = np.min(dom[np.where(domain == 1)]) walls = bld_copy - walls @@ -40,10 +44,16 @@ def greedyplanter(treeinput,treedata,treerasters,tmrt_1d,trees,feedback): tmrt_max = 0 # Calculating sum of Tmrt in shade for each possible position in the Tmrt matrix - sum_tmrt_tsh = np.zeros((treeinput.rows, treeinput.cols)) # Empty matrix for sum of Tmrt in tree shadow - sum_tmrt = np.zeros((treeinput.rows, treeinput.cols)) # Empty matrix for sum of Tmrt in sun under tree shadow + sum_tmrt_tsh = np.zeros( + (treeinput.rows, treeinput.cols) + ) # Empty matrix for sum of Tmrt in tree shadow + sum_tmrt = np.zeros( + (treeinput.rows, treeinput.cols) + ) # Empty matrix for sum of Tmrt in sun under tree shadow - res_y, res_x = np.where(bld_copy == 1) # Coordinates for where it is possible to plant a tree (buildings, area of interest excluded) + res_y, res_x = np.where( + bld_copy == 1 + ) # Coordinates for where it is possible to plant a tree (buildings, area of interest excluded) for tree in range(trees): if feedback.isCanceled(): @@ -58,57 +68,91 @@ def greedyplanter(treeinput,treedata,treerasters,tmrt_1d,trees,feedback): x1 = np.int_(res_x[i] - treerasters.buffer_x[0]) x2 = np.int_(res_x[i] + treerasters.buffer_x[1]) - yslice1, xslice1, yslice2, xslice2 = tree_slice(y1,y2,x1,x2,treeinput,treerasters) + yslice1, xslice1, yslice2, xslice2 = tree_slice( + y1, y2, x1, x2, treeinput, treerasters + ) ts_temp1 = np.zeros((treeinput.rows, treeinput.cols)) - ts_temp1[yslice2, xslice2] = treerasters.treeshade[yslice1, xslice1] + ts_temp1[yslice2, xslice2] = treerasters.treeshade[ + yslice1, xslice1 + ] # Estimating Tmrt in tree shade and in sun for j in range(tmrt_1d.__len__()): ts_temp2 = np.zeros((treeinput.rows, treeinput.cols)) - ts_temp2[yslice2, xslice2] = treerasters.treeshade_bool[yslice1, xslice1, j] - sum_tmrt[res_y[i],res_x[i]] += np.sum(ts_temp2 * treeinput.buildings * shadows_copy[:,:,j] * tmrt_copy[:,:,j]) - sum_tmrt_tsh[res_y[i], res_x[i]] += np.sum(ts_temp2 * treeinput.buildings * shadows_copy[:,:,j] * tmrt_1d[j,0]) + ts_temp2[yslice2, xslice2] = treerasters.treeshade_bool[ + yslice1, xslice1, j + ] + sum_tmrt[res_y[i], res_x[i]] += np.sum( + ts_temp2 + * treeinput.buildings + * shadows_copy[:, :, j] + * tmrt_copy[:, :, j] + ) + sum_tmrt_tsh[res_y[i], res_x[i]] += np.sum( + ts_temp2 + * treeinput.buildings + * shadows_copy[:, :, j] + * tmrt_1d[j, 0] + ) # Adding sum_tmrt and sum_tmrt_tsh to the Treerasters class as well as calculating the difference between sunlit and shaded treerasters.tmrt(sum_tmrt, sum_tmrt_tsh) if tree == 0: possible_locations = np.sum(treerasters.d_tmrt > 0) - feedback.setProgressText(str(possible_locations) + " possible locations for trees...") + feedback.setProgressText( + str(possible_locations) + " possible locations for trees..." + ) + + temp_y, temp_x = np.where( + treerasters.d_tmrt == np.max(treerasters.d_tmrt) + ) - temp_y, temp_x = np.where(treerasters.d_tmrt == np.max(treerasters.d_tmrt)) - tmrt_max += np.max(treerasters.d_tmrt) best_y[tree] = temp_y[0] best_x[tree] = temp_x[0] - + # Add new tree shade and remove Tmrt from Tmrt.SOLWEIG where there is shade from new tree y1 = np.int_(temp_y[0] - treerasters.buffer_y[0]) y2 = np.int_(temp_y[0] + treerasters.buffer_y[1]) x1 = np.int_(temp_x[0] - treerasters.buffer_x[0]) x2 = np.int_(temp_x[0] + treerasters.buffer_x[1]) - yslice1, xslice1, yslice2, xslice2 = tree_slice(y1,y2,x1,x2,treeinput,treerasters) + yslice1, xslice1, yslice2, xslice2 = tree_slice( + y1, y2, x1, x2, treeinput, treerasters + ) for j in range(tmrt_1d.__len__()): - temp_shadow = np.zeros((treeinput.rows,treeinput.cols)) - temp_shadow[yslice2, xslice2] = treerasters.treeshade_bool[yslice1 , xslice1, j] + temp_shadow = np.zeros((treeinput.rows, treeinput.cols)) + temp_shadow[yslice2, xslice2] = treerasters.treeshade_bool[ + yslice1, xslice1, j + ] temp_shadow = 1 - temp_shadow - shadows_copy[:,:,j] = shadows_copy[:,:,j] * temp_shadow - tmrt_copy[:,:,j] = tmrt_copy[:,:,j] * temp_shadow + shadows_copy[:, :, j] = shadows_copy[:, :, j] * temp_shadow + tmrt_copy[:, :, j] = tmrt_copy[:, :, j] * temp_shadow # Determine where to recalcaulate d_tmrt - y1 = np.int_(temp_y[0] - treerasters.buffer_y[0] - treerasters.buffer_y[1]) - y2 = np.int_(temp_y[0] + treerasters.buffer_y[1] + treerasters.buffer_y[0]) - x1 = np.int_(temp_x[0] - treerasters.buffer_x[0] - treerasters.buffer_x[1]) - x2 = np.int_(temp_x[0] + treerasters.buffer_x[1] + treerasters.buffer_x[0]) - - _, __, yslice2, xslice2 = tree_slice(y1,y2,x1,x2,treeinput,treerasters) - - recalc_positions = np.zeros((bld_copy.shape[0], bld_copy.shape[1])) - recalc_positions[yslice2, xslice2] = 1 + y1 = np.int_( + temp_y[0] - treerasters.buffer_y[0] - treerasters.buffer_y[1] + ) + y2 = np.int_( + temp_y[0] + treerasters.buffer_y[1] + treerasters.buffer_y[0] + ) + x1 = np.int_( + temp_x[0] - treerasters.buffer_x[0] - treerasters.buffer_x[1] + ) + x2 = np.int_( + temp_x[0] + treerasters.buffer_x[1] + treerasters.buffer_x[0] + ) + + _, __, yslice2, xslice2 = tree_slice( + y1, y2, x1, x2, treeinput, treerasters + ) + + recalc_positions = np.zeros((bld_copy.shape[0], bld_copy.shape[1])) + recalc_positions[yslice2, xslice2] = 1 treerasters.d_tmrt[yslice2, xslice2] = 0 sum_tmrt[yslice2, xslice2] = 0 sum_tmrt_tsh[yslice2, xslice2] = 0 @@ -119,7 +163,9 @@ def greedyplanter(treeinput,treedata,treerasters,tmrt_1d,trees,feedback): xt1 = np.int_(temp_x[0] - treerasters.buffer_x[0]) xt2 = np.int_(temp_x[0] + treerasters.buffer_x[1]) - yslice1, xslice1, yslice2, xslice2 = tree_slice(yt1,yt2,xt1,xt2,treeinput,treerasters) + yslice1, xslice1, yslice2, xslice2 = tree_slice( + yt1, yt2, xt1, xt2, treeinput, treerasters + ) added_tree = np.zeros((bld_copy.shape[0], bld_copy.shape[1])) added_tree[yslice2, xslice2] = treerasters.cdsm[yslice1, xslice1] @@ -131,21 +177,27 @@ def greedyplanter(treeinput,treedata,treerasters,tmrt_1d,trees,feedback): domain = np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) for i in range(1, treeinput.cols - 1): for j in range(1, treeinput.rows - 1): - dom = added_tree[j - 1:j + 2, i - 1:i + 2] + dom = added_tree[j - 1 : j + 2, i - 1 : i + 2] walls[j, i] = np.min(dom[np.where(domain == 1)]) - + added_tree = added_tree * walls bld_copy = bld_copy * added_tree recalc_positions = recalc_positions * bld_copy res_y, res_x = np.where(recalc_positions == 1) - - if (np.max(recalc_positions) == 0) & (tree != trees-1): + + if (np.max(recalc_positions) == 0) & (tree != trees - 1): best_bool = (best_y > 0) & (best_x > 0) best_y = best_y[best_bool] best_x = best_x[best_bool] - feedback.setProgressText('Not enough space for all trees! Found locations for ' + str(int(best_y.shape[0])) + ' out of ' + str(int(trees)) + ' trees.') + feedback.setProgressText( + "Not enough space for all trees! Found locations for " + + str(int(best_y.shape[0])) + + " out of " + + str(int(trees)) + + " trees." + ) break feedback.setProgress(int(tree * (100 / trees))) diff --git a/functions/TreePlanter/TreePlanter/HillClimberAlgorithm.py b/functions/TreePlanter/TreePlanter/HillClimberAlgorithm.py index 90f2a9a..47fd3d3 100644 --- a/functions/TreePlanter/TreePlanter/HillClimberAlgorithm.py +++ b/functions/TreePlanter/TreePlanter/HillClimberAlgorithm.py @@ -6,9 +6,22 @@ from ..TreePlanter.TreePlanterTreeshade import tsh_gen_mt1, tsh_gen_mt2 import itertools + # This function will look for better shading position for a tree by looking one pixel east, west, north, south of it's # current position. It will compare it's position to the other trees in the study area, i.e. other moving trees. -def topt(y, x, treerasters, treeinput, dia, shadow_rg, tmrt_1d, positions, ti, pos_m_pad, p_tmrt): +def topt( + y, + x, + treerasters, + treeinput, + dia, + shadow_rg, + tmrt_1d, + positions, + ti, + pos_m_pad, + p_tmrt, +): # x = x positions of trees # y = y positions of trees @@ -19,8 +32,8 @@ def topt(y, x, treerasters, treeinput, dia, shadow_rg, tmrt_1d, positions, ti, p # tmrt from SOLWEIG # i = which tree to move - t_y = y[ti] # y-position of tree to move - t_x = x[ti] # x-position of tree to move + t_y = y[ti] # y-position of tree to move + t_x = x[ti] # x-position of tree to move p_tmrt[0, 3] = t_y p_tmrt[0, 4] = t_x @@ -30,54 +43,60 @@ def topt(y, x, treerasters, treeinput, dia, shadow_rg, tmrt_1d, positions, ti, p yt = t_y + k_d xt = t_x + k_d - domain = np.array([[1,1,1], - [1,0,1], - [1,1,1]]) + domain = np.array([[1, 1, 1], [1, 0, 1], [1, 1, 1]]) # domain = np.array([[0,1,0], # [1,0,1], # [0,1,0]]) - t_pos = pos_m_pad[yt - k_d:yt + k_d+1, xt - k_d:xt + k_d+1] + t_pos = pos_m_pad[yt - k_d : yt + k_d + 1, xt - k_d : xt + k_d + 1] t_pos = t_pos * domain t_pos = t_pos.flatten() t_pos = t_pos[t_pos != 0] - t_yx = np.zeros((t_pos.shape[0],2)) + t_yx = np.zeros((t_pos.shape[0], 2)) for i in range(t_pos.shape[0]): - t_yx[i,0] = positions.pos[positions.pos[:, 0] == t_pos[i], 2] - t_yx[i,1] = positions.pos[positions.pos[:, 0] == t_pos[i], 1] + t_yx[i, 0] = positions.pos[positions.pos[:, 0] == t_pos[i], 2] + t_yx[i, 1] = positions.pos[positions.pos[:, 0] == t_pos[i], 1] t_yx = np.int_(t_yx) # Check if it is possible to move to all positions pos_bool = np.zeros((t_yx.shape[0])) for i in range(t_yx.shape[0]): - if np.any(positions.pos[((positions.pos[:, 1] == t_yx[i,1]) & (positions.pos[:, 2] == t_yx[i,0])), 0]): + if np.any( + positions.pos[ + ( + (positions.pos[:, 1] == t_yx[i, 1]) + & (positions.pos[:, 2] == t_yx[i, 0]) + ), + 0, + ] + ): pos_bool[i] = 1 pos_bool = np.bool_(pos_bool) - t_yx = t_yx[pos_bool,:] + t_yx = t_yx[pos_bool, :] # Where not to move, i.e. positions of all other trees - not_t = ((x[:] != t_x) | (y[:] != t_y)) + not_t = (x[:] != t_x) | (y[:] != t_y) - y_n = y[not_t] # y position of trees that are not moving - x_n = x[not_t] # x position of trees that are not moving + y_n = y[not_t] # y position of trees that are not moving + x_n = x[not_t] # x position of trees that are not moving # Check euclidean distance between moving tree and none-moving trees, i.e. where possible to move. # Also checking for canopy diameter / 2 to see if tree fits - eucl = np.zeros((y_n.shape[0],t_yx.shape[0])) + eucl = np.zeros((y_n.shape[0], t_yx.shape[0])) e_bool = np.ones((t_yx.shape[0])) e_bool_sh = np.zeros((t_yx.shape[0])) for i in range(y_n.shape[0]): yx_temp = np.array((y_n[i], x_n[i])) for j in range(t_yx.shape[0]): - eucl[i,j] = np.linalg.norm(t_yx[j,:] - yx_temp) - if (eucl[i,j] < dia): + eucl[i, j] = np.linalg.norm(t_yx[j, :] - yx_temp) + if eucl[i, j] < dia: e_bool[j] = 0 - if (eucl[i,j] < treerasters.euclidean_d): + if eucl[i, j] < treerasters.euclidean_d: e_bool_sh[j] = 1 - e_bool = np.bool_(e_bool) # Boolean of where it's possible and not to move - t_yx = t_yx[e_bool,:] # Positions where it's possible to move to + e_bool = np.bool_(e_bool) # Boolean of where it's possible and not to move + t_yx = t_yx[e_bool, :] # Positions where it's possible to move to e_bool_sh = np.bool_(e_bool_sh) # Checking euclidean distance between none-moving trees @@ -92,113 +111,181 @@ def topt(y, x, treerasters, treeinput, dia, shadow_rg, tmrt_1d, positions, ti, p by = y_n[yx_nmt[i][1]] b = np.array(([by, bx])) e_nmt[i] = np.linalg.norm(a - b) - if (e_nmt[i] < treerasters.euclidean_d): + if e_nmt[i] < treerasters.euclidean_d: e_bool_nmt[i] = 1 e_bool_nmt = np.bool_(e_bool_nmt) - tree_tmrt = np.zeros((t_yx.shape[0] + 1, 5)) # y, x, tmrt shade, tmrt sun, tmrt diff, sum tmrt all trees + tree_tmrt = np.zeros( + (t_yx.shape[0] + 1, 5) + ) # y, x, tmrt shade, tmrt sun, tmrt diff, sum tmrt all trees tree_tmrt[-1, :] = p_tmrt compare_mt = np.zeros((t_yx.shape[0])) compare = 0 - if ((np.any(e_bool_nmt == 1)) | (np.any(e_bool_sh == 1))): + if (np.any(e_bool_nmt == 1)) | (np.any(e_bool_sh == 1)): - # Check if tree shadows overlap - treesh_ts_bool_pad, treesh_ts_bool_pad_large, compare = tsh_gen_ts(y_n, x_n, treerasters, treeinput) # Boolean tree shadows for trees that are not moving + # Check if tree shadows overlap + treesh_ts_bool_pad, treesh_ts_bool_pad_large, compare = tsh_gen_ts( + y_n, x_n, treerasters, treeinput + ) # Boolean tree shadows for trees that are not moving - if (compare == 0): # If none of the none-moving trees overlap, go in here - treesh_bool_mt = tsh_gen_mt1(t_yx[:,0], t_yx[:,1], treerasters, treeinput) - if np.any(((treesh_bool_mt == 1) & (treesh_ts_bool_pad_large == 1))): + if ( + compare == 0 + ): # If none of the none-moving trees overlap, go in here + treesh_bool_mt = tsh_gen_mt1( + t_yx[:, 0], t_yx[:, 1], treerasters, treeinput + ) + if np.any( + ((treesh_bool_mt == 1) & (treesh_ts_bool_pad_large == 1)) + ): for j in range(t_yx.shape[0]): - if (e_bool_sh[j] == 1): # If boolean distance between coming position of the moving tree possibly have overlapping shadows with the other trees, continue - y_t = np.array([t_yx[j, 0]]) # Y-position of the current position of the currently moving tree - x_t = np.array([t_yx[j, 1]]) # X-position of the current position of the currently moving tree - treesh_bool_mt_pad, treesh_bool_mt_pad_large, _ = tsh_gen_ts(y_t, x_t, treerasters, treeinput) # Boolean tree shadows for moving tree - if np.any(((treesh_ts_bool_pad_large == 1) & (treesh_bool_mt_pad_large == 1))): - for ij in range(treesh_bool_mt_pad.shape[2]): # Check if timesteps overlap - temp_bool = ((treesh_ts_bool_pad[:,:,ij] == 1) | (treesh_bool_mt_pad[:,:,ij] == 1)) * (treeinput.shadow[:,:,ij] == 1) * (treeinput.buildings == 1) - tree_tmrt[j,0] += np.sum(temp_bool * tmrt_1d[ij, 0]) - tree_tmrt[j,1] += np.sum(temp_bool * treeinput.tmrt_ts[:,:,ij]) + if ( + e_bool_sh[j] == 1 + ): # If boolean distance between coming position of the moving tree possibly have overlapping shadows with the other trees, continue + y_t = np.array( + [t_yx[j, 0]] + ) # Y-position of the current position of the currently moving tree + x_t = np.array( + [t_yx[j, 1]] + ) # X-position of the current position of the currently moving tree + treesh_bool_mt_pad, treesh_bool_mt_pad_large, _ = ( + tsh_gen_ts(y_t, x_t, treerasters, treeinput) + ) # Boolean tree shadows for moving tree + if np.any( + ( + (treesh_ts_bool_pad_large == 1) + & (treesh_bool_mt_pad_large == 1) + ) + ): + for ij in range( + treesh_bool_mt_pad.shape[2] + ): # Check if timesteps overlap + temp_bool = ( + ( + (treesh_ts_bool_pad[:, :, ij] == 1) + | (treesh_bool_mt_pad[:, :, ij] == 1) + ) + * (treeinput.shadow[:, :, ij] == 1) + * (treeinput.buildings == 1) + ) + tree_tmrt[j, 0] += np.sum( + temp_bool * tmrt_1d[ij, 0] + ) + tree_tmrt[j, 1] += np.sum( + temp_bool * treeinput.tmrt_ts[:, :, ij] + ) compare_mt[j] = 1 - tree_tmrt[j,2] = tree_tmrt[j,1] - tree_tmrt[j,0] + tree_tmrt[j, 2] = tree_tmrt[j, 1] - tree_tmrt[j, 0] else: for j in range(t_yx.shape[0]): - if (e_bool_sh[j] == 1): + if e_bool_sh[j] == 1: y_t = np.array([t_yx[j, 0]]) x_t = np.array([t_yx[j, 1]]) - treesh_bool_mt_pad = tsh_gen_mt2(y_t, x_t, treerasters, treeinput) # Boolean tree shadows for moving tree + treesh_bool_mt_pad = tsh_gen_mt2( + y_t, x_t, treerasters, treeinput + ) # Boolean tree shadows for moving tree for ij in range(treesh_bool_mt_pad.shape[2]): - temp_bool = ((treesh_ts_bool_pad[:, :, ij] == 1) | (treesh_bool_mt_pad[:, :, ij] == 1)) * ( - treeinput.shadow[:, :, ij] == 1) * (treeinput.buildings == 1) + temp_bool = ( + ( + (treesh_ts_bool_pad[:, :, ij] == 1) + | (treesh_bool_mt_pad[:, :, ij] == 1) + ) + * (treeinput.shadow[:, :, ij] == 1) + * (treeinput.buildings == 1) + ) tree_tmrt[j, 0] += np.sum(temp_bool * tmrt_1d[ij, 0]) - tree_tmrt[j, 1] += np.sum(temp_bool * treeinput.tmrt_ts[:, :, ij]) + tree_tmrt[j, 1] += np.sum( + temp_bool * treeinput.tmrt_ts[:, :, ij] + ) compare_mt[j] = 1 tree_tmrt[j, 2] = tree_tmrt[j, 1] - tree_tmrt[j, 0] # Calculation of shadows for the currently moving tree for i in range(t_yx.shape[0]): - y_t = np.array([t_yx[i,0]]) - x_t = np.array([t_yx[i,1]]) + y_t = np.array([t_yx[i, 0]]) + x_t = np.array([t_yx[i, 1]]) - tree_tmrt[i, 3] = y_t # y position of currently moving tree - tree_tmrt[i, 4] = x_t # x position of currently moving tree + tree_tmrt[i, 3] = y_t # y position of currently moving tree + tree_tmrt[i, 4] = x_t # x position of currently moving tree # Comparison between the currently moving tree and other trees if their shadows overlap # If moving tree is overlapping with any of the other trees # If any of the none-moving trees are overlapping with each other but the moving tree is not overlapping with them - if ((compare == 1) & (compare_mt[i] == 0)): + if (compare == 1) & (compare_mt[i] == 0): start_time = time.time() for j in range(treesh_ts_bool_pad.shape[2]): - tree_bool_temp = treesh_ts_bool_pad[:,:,j] * treeinput.shadow[:,:,j] * treeinput.buildings + tree_bool_temp = ( + treesh_ts_bool_pad[:, :, j] + * treeinput.shadow[:, :, j] + * treeinput.buildings + ) tree_tmrt[i, 0] += np.sum(tree_bool_temp * tmrt_1d[j, 0]) - tree_tmrt[i, 1] += np.sum(tree_bool_temp * treeinput.tmrt_ts[:,:,j]) + tree_tmrt[i, 1] += np.sum( + tree_bool_temp * treeinput.tmrt_ts[:, :, j] + ) tree_tmrt[i, 0] += treerasters.tmrt_shade[y_t, x_t] tree_tmrt[i, 1] += treerasters.tmrt_sun[y_t, x_t] tree_tmrt[i, 2] = tree_tmrt[i, 1] - tree_tmrt[i, 0] # If no trees are overlapping - elif ((compare == 0) & (compare_mt[i] == 0)): + elif (compare == 0) & (compare_mt[i] == 0): start_time = time.time() for j in range(y_n.shape[0]): - tree_tmrt[i, 0] += treerasters.tmrt_shade[y_n[j], x_n[j]] # Tree shade - tree_tmrt[i, 1] += treerasters.tmrt_sun[y_n[j], x_n[j]] # Sunlit - tree_tmrt[i, 2] += treerasters.d_tmrt[y_n[j], x_n[j]] # Sunlit - Tree shade - tree_tmrt[i,0] += treerasters.tmrt_shade[y_t,x_t] # Potential decrease in Tmrt from shade due to other trees - tree_tmrt[i,1] += treerasters.tmrt_sun[y_t,x_t] # Tmrt in sun for the tree shadow - tree_tmrt[i,2] += treerasters.d_tmrt[y_t,x_t] # Difference in Tmrt between sunlit and shaded - - nc = 0 # no change - - if (np.sum(e_bool) > 0): - t_max = np.where(tree_tmrt[:, 2] == np.max(tree_tmrt[:, 2])) # Which position has highest tmrt - if (t_max[0].__len__() > 1): + tree_tmrt[i, 0] += treerasters.tmrt_shade[ + y_n[j], x_n[j] + ] # Tree shade + tree_tmrt[i, 1] += treerasters.tmrt_sun[ + y_n[j], x_n[j] + ] # Sunlit + tree_tmrt[i, 2] += treerasters.d_tmrt[ + y_n[j], x_n[j] + ] # Sunlit - Tree shade + tree_tmrt[i, 0] += treerasters.tmrt_shade[ + y_t, x_t + ] # Potential decrease in Tmrt from shade due to other trees + tree_tmrt[i, 1] += treerasters.tmrt_sun[ + y_t, x_t + ] # Tmrt in sun for the tree shadow + tree_tmrt[i, 2] += treerasters.d_tmrt[ + y_t, x_t + ] # Difference in Tmrt between sunlit and shaded + + nc = 0 # no change + + if np.sum(e_bool) > 0: + t_max = np.where( + tree_tmrt[:, 2] == np.max(tree_tmrt[:, 2]) + ) # Which position has highest tmrt + if t_max[0].__len__() > 1: t_rand = np.zeros((t_max[0].__len__())) for iy in range(t_max[0].__len__()): - t_out = tree_tmrt[[t_max[0][iy]],:] - if ((t_out[0, 3] == t_y) & (t_out[0, 4] == t_x)): + t_out = tree_tmrt[[t_max[0][iy]], :] + if (t_out[0, 3] == t_y) & (t_out[0, 4] == t_x): t_rand[iy] = 1 y[ti] = t_out[0, 3] x[ti] = t_out[0, 4] nc = 1 break - if (np.sum(t_rand) == 0): + if np.sum(t_rand) == 0: t_max_rand = np.random.choice(t_max[0], 1) t_out = tree_tmrt[t_max_rand, :] y[ti] = t_out[0, 3] x[ti] = t_out[0, 4] else: - t_out = tree_tmrt[t_max[0],:] # Position of where tmrt is highest - if ((np.int_(t_out[0,3]) == t_y) & (np.int_(t_out[0,4]) == t_x)): # If starting position, i.e. input position is best, don't move + t_out = tree_tmrt[t_max[0], :] # Position of where tmrt is highest + if (np.int_(t_out[0, 3]) == t_y) & ( + np.int_(t_out[0, 4]) == t_x + ): # If starting position, i.e. input position is best, don't move nc = 1 else: - y[ti] = t_out[0,3] - x[ti] = t_out[0,4] + y[ti] = t_out[0, 3] + x[ti] = t_out[0, 4] else: - t_out = tree_tmrt # If tree can't move because of other trees, no move + t_out = tree_tmrt # If tree can't move because of other trees, no move nc = 1 return t_out, nc, y, x diff --git a/functions/TreePlanter/TreePlanter/StartingPositions.py b/functions/TreePlanter/TreePlanter/StartingPositions.py index e102aa4..4148e58 100644 --- a/functions/TreePlanter/TreePlanter/StartingPositions.py +++ b/functions/TreePlanter/TreePlanter/StartingPositions.py @@ -1,6 +1,7 @@ import numpy as np import itertools + # Creating trees at random positions def random_start(pos, trees, tree_pos_all, r_iters): @@ -12,14 +13,25 @@ def random_start(pos, trees, tree_pos_all, r_iters): tree_pos = np.random.choice(pos, trees) tp_c += 1 # print('re-random') - if (tp_c == 100): + if tp_c == 100: break_loop = r_iters + 1 break return tree_pos, tp_c, break_loop + # Creating trees evolutionary from previous starting position. First population is random. -def genetic_start(tree_pos_x, tree_pos_y, tree_pos_c, positions, trees, tree_pos_all, r_iters, counter, dia): +def genetic_start( + tree_pos_x, + tree_pos_y, + tree_pos_c, + positions, + trees, + tree_pos_all, + r_iters, + counter, + dia, +): pos = positions.pos[:, 0] pos_y = positions.pos[:, 2] pos_x = positions.pos[:, 1] @@ -32,7 +44,9 @@ def genetic_start(tree_pos_x, tree_pos_y, tree_pos_c, positions, trees, tree_pos # Random for first run if counter == 0: - tree_pos, tp_c, break_loop = random_start(pos, trees, tree_pos_all, r_iters) + tree_pos, tp_c, break_loop = random_start( + pos, trees, tree_pos_all, r_iters + ) # Take either x or y position from previous local optimum for each tree, make the other coordinate random else: @@ -40,14 +54,16 @@ def genetic_start(tree_pos_x, tree_pos_y, tree_pos_c, positions, trees, tree_pos tries = np.zeros((trees)) while distance < 1: exists = np.zeros((trees)) - ti = itertools.cycle(range(trees)) # Iterator to move between trees moving around in the study area + ti = itertools.cycle( + range(trees) + ) # Iterator to move between trees moving around in the study area new_y = np.zeros((trees)) new_x = np.zeros((trees)) tree_pos_c_temp = tree_pos_c.copy() while np.sum(exists) < trees: i = ti.__next__() # Random y or x coordinate i.e. mutation - if ((tree_pos_c[i] > 3) or (tries[i] > 50)): + if (tree_pos_c[i] > 3) or (tries[i] > 50): # Decide which coordinate will be random; 0 = y, 1 = x xy_random = np.random.choice([0, 1], 1) # Random y-position @@ -61,7 +77,12 @@ def genetic_start(tree_pos_x, tree_pos_y, tree_pos_c, positions, trees, tree_pos pos_x_temp = pos_x[pos_y == new_y[i]] new_x[i] = np.random.choice(pos_x_temp, 1) tree_pos[i] = positions.pos[ - ((positions.pos[:, 1] == new_x[i]) & (positions.pos[:, 2] == new_y[i])), 0] + ( + (positions.pos[:, 1] == new_x[i]) + & (positions.pos[:, 2] == new_y[i]) + ), + 0, + ] exists[i] = 1 tree_pos_c_temp[i] = 0 tries[i] = 0 @@ -69,15 +90,30 @@ def genetic_start(tree_pos_x, tree_pos_y, tree_pos_c, positions, trees, tree_pos elif exists[i] == 0: y_temp = np.random.choice(tree_pos_y, 1) x_temp = np.random.choice(tree_pos_x, 1) - if np.any(positions.pos[((positions.pos[:, 1] == x_temp) & (positions.pos[:, 2] == y_temp)), 0]): + if np.any( + positions.pos[ + ( + (positions.pos[:, 1] == x_temp) + & (positions.pos[:, 2] == y_temp) + ), + 0, + ] + ): tree_pos[i] = positions.pos[ - ((positions.pos[:, 1] == x_temp) & (positions.pos[:, 2] == y_temp)), 0] + ( + (positions.pos[:, 1] == x_temp) + & (positions.pos[:, 2] == y_temp) + ), + 0, + ] new_y[i] = y_temp new_x[i] = x_temp exists[i] = 1 # Euclidean distance between random positions so that trees are not too close to each other - yxp = tuple(itertools.combinations(np.arange(tree_pos.shape[0]), 2)) + yxp = tuple( + itertools.combinations(np.arange(tree_pos.shape[0]), 2) + ) eucl_dist = np.zeros((yxp.__len__())) for j in range(yxp.__len__()): @@ -89,7 +125,7 @@ def genetic_start(tree_pos_x, tree_pos_y, tree_pos_c, positions, trees, tree_pos b = np.array(([by, bx])) eucl_dist[j] = np.linalg.norm(a - b) - if (np.min(eucl_dist) >= dia): + if np.min(eucl_dist) >= dia: distance = 1 tries[i] += 1 @@ -103,6 +139,7 @@ def genetic_start(tree_pos_x, tree_pos_y, tree_pos_c, positions, trees, tree_pos return tree_pos_y, tree_pos_x, tree_pos, tree_pos_c, tp_c, break_loop + ### Creating trees evolutionary from previous starting position. First population is random. ##def geneticstart(tree_pos_x, tree_pos_y, tree_pos_c, positions, trees, tree_pos_all, r_iters, counter): ## diff --git a/functions/TreePlanter/TreePlanter/TreePlanterClasses.py b/functions/TreePlanter/TreePlanter/TreePlanterClasses.py index 7cf45ba..438be4c 100644 --- a/functions/TreePlanter/TreePlanter/TreePlanterClasses.py +++ b/functions/TreePlanter/TreePlanter/TreePlanterClasses.py @@ -2,7 +2,9 @@ import numpy as np from scipy.ndimage import label import os -#from qgis.core import QgsRasterLayer + +# from qgis.core import QgsRasterLayer + def spatialReferenceData(self, feedback): # Find latlon etc for input data. @@ -10,7 +12,7 @@ def spatialReferenceData(self, feedback): dsm_ref = self.dataSet.GetProjectionRef() # dsm_ref = QgsRasterLayer(self.dataSet.GetDescription()).crs().toWkt() # This method requires lonlat = transform.TransformPoint(minx, miny) - + old_cs.ImportFromWkt(dsm_ref) old_cs = osr.SpatialReference(wkt=self.dataSet.GetProjection()) @@ -44,54 +46,92 @@ def spatialReferenceData(self, feedback): lonlat = transform.TransformPoint(miny, minx) else: lonlat = transform.TransformPoint(minx, miny) - + geotransform = self.dataSet.GetGeoTransform() self.scale = 1 / self.gt[1] gdalver = float(gdal.__version__[0]) - if gdalver == 3.: - self.lon = lonlat[1] #changed to gdal 3 - self.lat = lonlat[0] #changed to gdal 3 + if gdalver == 3.0: + self.lon = lonlat[1] # changed to gdal 3 + self.lat = lonlat[0] # changed to gdal 3 else: - self.lon = lonlat[0] #changed to gdal 2 - self.lat = lonlat[1] #changed to gdal 2 + self.lon = lonlat[0] # changed to gdal 2 + self.lat = lonlat[1] # changed to gdal 2 # alt = np.median(self.dsm) # if alt < 0: - # alt = 3 - lonPush = 'Longitude derived from DSM: ' + str(self.lon) - latPush = 'Latitude derived from DSM: ' + str(self.lat) + # alt = 3 + lonPush = "Longitude derived from DSM: " + str(self.lon) + latPush = "Latitude derived from DSM: " + str(self.lat) feedback.setProgressText(lonPush) feedback.setProgressText(latPush) return minx, miny, maxx, maxy -class Inputdata(): - '''Class containing input data for Tree planter''' - __slots__ = ('dataSet', 'buildings', 'selected_area', 'dsm', 'cdsm', 'cdsm_b', 'shadow', 'tmrt_ts', 'tmrt_s', 'tmrt_avg', 'rows', 'cols', 'scale', 'lat', 'lon', 'gt') - def __init__(self,r_range, sh_fl, tmrt_fl, infolder, inputPolygonlayer, feedback): - self.dataSet = gdal.Open(infolder + '/buildings.tif') # GIS data - self.buildings = self.dataSet.ReadAsArray().astype(float) # Building raster +class Inputdata: + """Class containing input data for Tree planter""" + + __slots__ = ( + "dataSet", + "buildings", + "selected_area", + "dsm", + "cdsm", + "cdsm_b", + "shadow", + "tmrt_ts", + "tmrt_s", + "tmrt_avg", + "rows", + "cols", + "scale", + "lat", + "lon", + "gt", + ) + + def __init__( + self, r_range, sh_fl, tmrt_fl, infolder, inputPolygonlayer, feedback + ): + + self.dataSet = gdal.Open(infolder + "/buildings.tif") # GIS data + self.buildings = self.dataSet.ReadAsArray().astype( + float + ) # Building raster self.buildings = self.buildings == 1.0 - self.rows = self.buildings.shape[0] # Rows of input rasters from SOLWEIG - self.cols = self.buildings.shape[1] # Cols of input rasters from SOLWEIG - self.cdsm = np.zeros((self.rows,self.cols)) # Canopy digital surface model - self.cdsm_b = np.zeros((self.rows,self.cols)) # Canopy digital surface model - self.shadow = np.zeros((self.rows,self.cols, r_range.__len__())) # Shadow rasters - self.tmrt_ts = np.zeros((self.rows,self.cols, r_range.__len__())) # Tmrt for each timestep - self.tmrt_s = np.zeros((self.rows,self.cols)) # Sum of tmrt for all timesteps + self.rows = self.buildings.shape[ + 0 + ] # Rows of input rasters from SOLWEIG + self.cols = self.buildings.shape[ + 1 + ] # Cols of input rasters from SOLWEIG + self.cdsm = np.zeros( + (self.rows, self.cols) + ) # Canopy digital surface model + self.cdsm_b = np.zeros( + (self.rows, self.cols) + ) # Canopy digital surface model + self.shadow = np.zeros( + (self.rows, self.cols, r_range.__len__()) + ) # Shadow rasters + self.tmrt_ts = np.zeros( + (self.rows, self.cols, r_range.__len__()) + ) # Tmrt for each timestep + self.tmrt_s = np.zeros( + (self.rows, self.cols) + ) # Sum of tmrt for all timesteps # Loading DEm, DSM (and CDSM) rasters - dataSet = gdal.Open(infolder + '/DSM.tif') + dataSet = gdal.Open(infolder + "/DSM.tif") self.dsm = dataSet.ReadAsArray().astype(float) # dataSet = gdal.Open(infolder + '/DEM.tif') # self.dem = dataSet.ReadAsArray().astype(float) # Check if CDSM exists - if os.path.exists(infolder + '/CDSM.tif'): - dataSet = gdal.Open(infolder + '/CDSM.tif') + if os.path.exists(infolder + "/CDSM.tif"): + dataSet = gdal.Open(infolder + "/CDSM.tif") self.cdsm = dataSet.ReadAsArray().astype(float) - self.cdsm[self.cdsm < 0] = 0. + self.cdsm[self.cdsm < 0] = 0.0 self.cdsm_b = self.cdsm == 0 # self.cdsm_b = self.cdsm == np.nan self.buildings = (self.buildings == True) & (self.cdsm_b == True) @@ -99,38 +139,54 @@ def __init__(self,r_range, sh_fl, tmrt_fl, infolder, inputPolygonlayer, feedback c = 0 for iy in r_range: dataSet1 = gdal.Open(sh_fl[iy]) - feedback.setProgressText('Loading ' + sh_fl[iy] + '..') + feedback.setProgressText("Loading " + sh_fl[iy] + "..") self.shadow[:, :, c] = dataSet1.ReadAsArray().astype(float) dataSet2 = gdal.Open(tmrt_fl[iy]) - feedback.setProgressText('Loading ' + tmrt_fl[iy] + '..') - self.tmrt_ts[:, :, c] = np.around(dataSet2.ReadAsArray().astype(float), decimals=1) * self.shadow[:, :, c] + feedback.setProgressText("Loading " + tmrt_fl[iy] + "..") + self.tmrt_ts[:, :, c] = ( + np.around(dataSet2.ReadAsArray().astype(float), decimals=1) + * self.shadow[:, :, c] + ) self.tmrt_s = self.tmrt_s + self.tmrt_ts[:, :, c] c += 1 - self.tmrt_avg = (self.tmrt_s / c) + self.tmrt_avg = self.tmrt_s / c # Get spatial reference data minx, miny, maxx, maxy = spatialReferenceData(self, feedback) # Import Planting area and rasterize - rasterize_options = gdal.RasterizeOptions(options=[ - '-burn', '1', - '-te', str(minx), str(miny), str(maxx), str(maxy), - '-tr', str(np.abs(self.gt[1])), str(np.abs(self.gt[5])) - ]) - - gdal.Rasterize(infolder + '/selected_area.tif', inputPolygonlayer, options=rasterize_options) - dataSetSel = gdal.Open(infolder + '/selected_area.tif') + rasterize_options = gdal.RasterizeOptions( + options=[ + "-burn", + "1", + "-te", + str(minx), + str(miny), + str(maxx), + str(maxy), + "-tr", + str(np.abs(self.gt[1])), + str(np.abs(self.gt[5])), + ] + ) + + gdal.Rasterize( + infolder + "/selected_area.tif", + inputPolygonlayer, + options=rasterize_options, + ) + dataSetSel = gdal.Open(infolder + "/selected_area.tif") self.selected_area = dataSetSel.ReadAsArray().astype(float) # Remove plant area raster from computer try: del dataSetSel - os.remove(infolder + '/selected_area.tif') - print('Successfully removed selected_area.tif') + os.remove(infolder + "/selected_area.tif") + print("Successfully removed selected_area.tif") except: - print('Could not remove selected_area.tif') + print("Could not remove selected_area.tif") # Buffer zone to remove potential edge effects buffer_percentage = 0.05 @@ -141,17 +197,41 @@ def __init__(self,r_range, sh_fl, tmrt_fl, infolder, inputPolygonlayer, feedback self.selected_area = self.selected_area * buffer_zone -class Treerasters(): - '''Class containing calculated shadows, regional grouping of shadows \ - if many timesteps, tmrt in shade, tmrt sunlit, difference between \ - shade and sunlit''' - __slots__ = ('treeshade', 'treeshade_rg', 'treeshade_bool', 'cdsm', 'buffer_y', 'buffer_x', 'tpy', 'tpx', 'rows', 'cols', 'rows_s', 'cols_s', 'euclidean', 'euclidean_d', 'tmrt_sun', 'tmrt_shade', 'd_tmrt') - def __init__(self, treeshade, treeshade_rg, treeshade_bool, cdsm, treedata): +class Treerasters: + """Class containing calculated shadows, regional grouping of shadows \ + if many timesteps, tmrt in shade, tmrt sunlit, difference between \ + shade and sunlit""" + + __slots__ = ( + "treeshade", + "treeshade_rg", + "treeshade_bool", + "cdsm", + "buffer_y", + "buffer_x", + "tpy", + "tpx", + "rows", + "cols", + "rows_s", + "cols_s", + "euclidean", + "euclidean_d", + "tmrt_sun", + "tmrt_shade", + "d_tmrt", + ) + + def __init__( + self, treeshade, treeshade_rg, treeshade_bool, cdsm, treedata + ): # Find min and max rows and cols where there are shadows shy, shx = np.where((treeshade > 0) | (cdsm > 0)) - shy_min = np.min(shy); shy_max = np.max(shy) + 1 - shx_min = np.min(shx); shx_max = np.max(shx) + 1 + shy_min = np.min(shy) + shy_max = np.max(shy) + 1 + shx_min = np.min(shx) + shx_max = np.max(shx) + 1 y = np.int_(np.abs(treedata.treey - shy_min)) x = np.int_(np.abs(treedata.treex - shx_min)) @@ -159,7 +239,9 @@ def __init__(self, treeshade, treeshade_rg, treeshade_bool, cdsm, treedata): # Cropping to only where there is a shadow self.treeshade = treeshade[shy_min:shy_max, shx_min:shx_max] self.treeshade_rg = treeshade_rg[shy_min:shy_max, shx_min:shx_max] - self.treeshade_bool = 1-treeshade_bool[shy_min:shy_max, shx_min:shx_max,:] + self.treeshade_bool = ( + 1 - treeshade_bool[shy_min:shy_max, shx_min:shx_max, :] + ) self.cdsm = cdsm[shy_min:shy_max, shx_min:shx_max] # y, x = np.where(cdsm_clip == treedata.height) # Position of tree in clipped shadow image self.buffer_y = np.zeros((2)) @@ -176,16 +258,22 @@ def __init__(self, treeshade, treeshade_rg, treeshade_bool, cdsm, treedata): self.cols_s = self.treeshade_rg.shape[1] a = np.array((self.tpy, self.tpx)) - b = np.zeros((4,2)) - b[0,:] = np.array((0, 0)) # Upper left corner - b[1,:] = np.array((0, self.treeshade.shape[0] - 1)) # Lower left corner - b[2,:] = np.array((self.treeshade.shape[1] - 1, 0)) # Upper right corner - b[3,:] = np.array((self.treeshade.shape[0] - 1, self.treeshade.shape[1] - 1)) # Lower right corner - eucl = np.zeros((b.shape[0],1)) + b = np.zeros((4, 2)) + b[0, :] = np.array((0, 0)) # Upper left corner + b[1, :] = np.array( + (0, self.treeshade.shape[0] - 1) + ) # Lower left corner + b[2, :] = np.array( + (self.treeshade.shape[1] - 1, 0) + ) # Upper right corner + b[3, :] = np.array( + (self.treeshade.shape[0] - 1, self.treeshade.shape[1] - 1) + ) # Lower right corner + eucl = np.zeros((b.shape[0], 1)) for i in range(b.shape[0]): - eucl[i,0] = np.linalg.norm(a - b[i,:]) + eucl[i, 0] = np.linalg.norm(a - b[i, :]) eucl_d = np.array([eucl[0] + eucl[3], eucl[1] + eucl[2]]) - self.euclidean = np.max(eucl[:,0]) + self.euclidean = np.max(eucl[:, 0]) self.euclidean_d = np.max(eucl_d) self.tmrt_sun = 0 @@ -193,17 +281,19 @@ def __init__(self, treeshade, treeshade_rg, treeshade_bool, cdsm, treedata): self.d_tmrt = 0 def tmrt(self, tmrt_sun, tmrt_shade): - '''Calculate difference in Tmrt between sun and shade''' - nr_dec = 1 + """Calculate difference in Tmrt between sun and shade""" + nr_dec = 1 self.tmrt_sun = np.around(tmrt_sun, decimals=nr_dec) self.tmrt_shade = np.around(tmrt_shade, decimals=nr_dec) - self.d_tmrt = (self.tmrt_sun - self.tmrt_shade) + self.d_tmrt = self.tmrt_sun - self.tmrt_shade + -class Position(): -# Class containing y and x positions of trees and their corresponding sum of Tmrt in shade and sum of Tmrt in same area as shade but sunlit -# as well as a unique number for each position. Also a matrix with the unique number in each y,x position in the matrix. - __slots__ = ('pos', 'pos_m') - def __init__(self,vector,rows,cols): +class Position: + # Class containing y and x positions of trees and their corresponding sum of Tmrt in shade and sum of Tmrt in same area as shade but sunlit + # as well as a unique number for each position. Also a matrix with the unique number in each y,x position in the matrix. + __slots__ = ("pos", "pos_m") + + def __init__(self, vector, rows, cols): self.pos = vector self.pos_m = np.zeros((rows, cols)) @@ -212,9 +302,11 @@ def __init__(self,vector,rows,cols): x = np.int_(vector[idx, 1]) self.pos_m[y, x] = vector[idx, 0] -class Treedata(): -# Class containing data for the tree that is used in Tree planter, i.e. the tree that is being "planted" and studied - __slots__ = ('ttype', 'height', 'trunk', 'dia', 'treey', 'treex') + +class Treedata: + # Class containing data for the tree that is used in Tree planter, i.e. the tree that is being "planted" and studied + __slots__ = ("ttype", "height", "trunk", "dia", "treey", "treex") + def __init__(self, ttype, height, trunk, dia, treey, treex): self.ttype = ttype self.height = height @@ -224,70 +316,121 @@ def __init__(self, ttype, height, trunk, dia, treey, treex): self.treex = treex -class Regional_groups(): +class Regional_groups: # Class for creation of regional groups for shadows, i.e. which timesteps shade which pixels # Returns a matrix with regional groups and a vector with the corresponding timesteps for each regional group # range_ = between which timesteps to calculate regional groups # shadow_ = matrix with sum of shadows for all timesteps # shadow_ts = shadows for each timestep - __slots__ = ('shadow', 'timesteps', 'tmrt_rg') + __slots__ = ("shadow", "timesteps", "tmrt_rg") + def __init__(self, range_, shadow_, shadow_ts, tmrt): t_r = range(range_.__len__()) t_l = t_r.__len__() - shade_u = np.unique(shadow_) # Unique values in summation matrix for tree shadows - shade_max = np.max(shade_u) # Maximum value of unique values - - for i in range(1, shade_u.shape[0]): # Loop over all unique values - shade_b = (shadow_ == shade_u[i]) # Boolean shadow for each timestep i - shade_r = label(shade_b) # Create regional groups - shade_r_u = np.unique(shade_r[0]) # Find out how many regional groups, i.e. unique values - if np.sum(shade_r_u) > 1: # If more than there groups, i.e. 0, 1, 2, ... , continue - for j in range(2, shade_r_u.shape[0]): # Loop over the unique values and give all but 1 new values - shade_b2 = (shade_r[0] == shade_r_u[j]) # Boolean of shadow for each unique value - shade_max += 1 # Add +1 to the maximum value of unique values, continues (creates new unique values) - shadow_[shade_b2] = shade_max # Add these to the building summation matrix - - shade_u_u = np.unique(shadow_) # New unique values of regional groups - sh_vec_t = np.zeros((shade_u_u.shape[0], t_l + 1)) # Empty array for storing which timesteps are found in each regional group - sh_vec_t[:, 0] = shade_u_u # Adding the unique regional groups to the first column + shade_u = np.unique( + shadow_ + ) # Unique values in summation matrix for tree shadows + shade_max = np.max(shade_u) # Maximum value of unique values + + for i in range(1, shade_u.shape[0]): # Loop over all unique values + shade_b = ( + shadow_ == shade_u[i] + ) # Boolean shadow for each timestep i + shade_r = label(shade_b) # Create regional groups + shade_r_u = np.unique( + shade_r[0] + ) # Find out how many regional groups, i.e. unique values + if ( + np.sum(shade_r_u) > 1 + ): # If more than there groups, i.e. 0, 1, 2, ... , continue + for j in range( + 2, shade_r_u.shape[0] + ): # Loop over the unique values and give all but 1 new values + shade_b2 = ( + shade_r[0] == shade_r_u[j] + ) # Boolean of shadow for each unique value + shade_max += 1 # Add +1 to the maximum value of unique values, continues (creates new unique values) + shadow_[shade_b2] = ( + shade_max # Add these to the building summation matrix + ) + + shade_u_u = np.unique(shadow_) # New unique values of regional groups + sh_vec_t = np.zeros( + (shade_u_u.shape[0], t_l + 1) + ) # Empty array for storing which timesteps are found in each regional group + sh_vec_t[:, 0] = ( + shade_u_u # Adding the unique regional groups to the first column + ) tmrt_t = np.zeros((shade_u_u.shape[0], 2)) - tmrt_t[:,0] = shade_u_u - - for i in range(1, shade_u_u.shape[0]): # Loop over the unique values - shade_b = (shadow_ == shade_u_u[i]) # Boolean of each regional group - for j in t_r: # Loop over each timestep - shade_b2 = (shadow_ts[:, :, j].copy() == 1) # Boolean of shadow for each timestep - shade_b3 = (shade_b) & (shade_b2) # Find out where they overlap, i.e. which timesteps are found in each regional group - if np.sum(shade_b3) > 0: # If they overlap, continue - sh_vec_t[i, 1 + j] = 1 # Add 1 to timestep column - tmrt_t[i,1] += tmrt[j,0] - - sh_vec_unique = np.unique(sh_vec_t[:,1:], axis=0) + tmrt_t[:, 0] = shade_u_u + + for i in range(1, shade_u_u.shape[0]): # Loop over the unique values + shade_b = shadow_ == shade_u_u[i] # Boolean of each regional group + for j in t_r: # Loop over each timestep + shade_b2 = ( + shadow_ts[:, :, j].copy() == 1 + ) # Boolean of shadow for each timestep + shade_b3 = (shade_b) & ( + shade_b2 + ) # Find out where they overlap, i.e. which timesteps are found in each regional group + if np.sum(shade_b3) > 0: # If they overlap, continue + sh_vec_t[i, 1 + j] = 1 # Add 1 to timestep column + tmrt_t[i, 1] += tmrt[j, 0] + + sh_vec_unique = np.unique(sh_vec_t[:, 1:], axis=0) for i in range(sh_vec_unique.shape[0]): - sh_rg = sh_vec_t[:,1:] == sh_vec_unique[i,:] + sh_rg = sh_vec_t[:, 1:] == sh_vec_unique[i, :] sh_b = np.all(sh_rg, axis=1) if np.sum(sh_b) > 1: - sh_temp = sh_vec_t[sh_b,0] - sh_vec_t[sh_b,0] = sh_temp[0] + sh_temp = sh_vec_t[sh_b, 0] + sh_vec_t[sh_b, 0] = sh_temp[0] sh_temp2 = sh_temp[1:] for j in sh_temp2: shadow_[shadow_ == j] = sh_temp[0] - sh_vec_t, sh_idx, sh_inv = np.unique(sh_vec_t, return_index=True, return_inverse=True, axis=0) - tmrt_t = tmrt_t[sh_idx,:] + sh_vec_t, sh_idx, sh_inv = np.unique( + sh_vec_t, return_index=True, return_inverse=True, axis=0 + ) + tmrt_t = tmrt_t[sh_idx, :] self.shadow = shadow_ self.timesteps = sh_vec_t self.tmrt_rg = tmrt_t -class ClippedInputdata(): - '''This class clips the input rasters based on a buffer zone around the selected area. - This buffer zone is based on how far the tree shadow can reach outside the study area if a tree is at the edge ''' - #__slots__ = ('buildings', 'selected_area', 'dem', 'dsm', 'cdsm', 'cdsm_b', 'shadow', 'tmrt_ts', 'tmrt_s') - __slots__ = ('dataSet', 'buildings', 'selected_area', 'dsm', 'cdsm', 'cdsm_b', 'shadow', 'tmrt_ts', 'tmrt_s', 'rows', 'cols', 'scale', 'lat', 'lon', 'gt', 'shadows_pad', 'tmrt_ts_pad', 'buildings_pad', 'rows_pad', 'cols_pad', - 'clip_rows', 'clip_cols') + +class ClippedInputdata: + """This class clips the input rasters based on a buffer zone around the selected area. + This buffer zone is based on how far the tree shadow can reach outside the study area if a tree is at the edge + """ + + # __slots__ = ('buildings', 'selected_area', 'dem', 'dsm', 'cdsm', 'cdsm_b', 'shadow', 'tmrt_ts', 'tmrt_s') + __slots__ = ( + "dataSet", + "buildings", + "selected_area", + "dsm", + "cdsm", + "cdsm_b", + "shadow", + "tmrt_ts", + "tmrt_s", + "rows", + "cols", + "scale", + "lat", + "lon", + "gt", + "shadows_pad", + "tmrt_ts_pad", + "buildings_pad", + "rows_pad", + "cols_pad", + "clip_rows", + "clip_cols", + ) + def __init__(self, treeinput, treerasters): # Estimate extent of selected area sa_rows, sa_cols = np.where(treeinput.selected_area == 1) @@ -311,18 +454,43 @@ def __init__(self, treeinput, treerasters): self.clip_cols[1] = treeinput.selected_area.shape[1] # Turn into integers to be able to use as indices - self.clip_rows = np.int_(self.clip_rows); self.clip_cols = np.int_(self.clip_cols) + self.clip_rows = np.int_(self.clip_rows) + self.clip_cols = np.int_(self.clip_cols) # Clip input rasters - self.buildings = treeinput.buildings[self.clip_rows[0]:self.clip_rows[1], self.clip_cols[0]:self.clip_cols[1]] - self.selected_area = treeinput.selected_area[self.clip_rows[0]:self.clip_rows[1], self.clip_cols[0]:self.clip_cols[1]] + self.buildings = treeinput.buildings[ + self.clip_rows[0] : self.clip_rows[1], + self.clip_cols[0] : self.clip_cols[1], + ] + self.selected_area = treeinput.selected_area[ + self.clip_rows[0] : self.clip_rows[1], + self.clip_cols[0] : self.clip_cols[1], + ] # self.dem = treeinput.dem[self.clip_rows[0]:self.clip_rows[1], self.clip_cols[0]:self.clip_cols[1]] - self.dsm = treeinput.dsm[self.clip_rows[0]:self.clip_rows[1], self.clip_cols[0]:self.clip_cols[1]] - self.cdsm = treeinput.cdsm[self.clip_rows[0]:self.clip_rows[1], self.clip_cols[0]:self.clip_cols[1]] - self.cdsm_b = treeinput.cdsm_b[self.clip_rows[0]:self.clip_rows[1], self.clip_cols[0]:self.clip_cols[1]] - self.shadow = treeinput.shadow[self.clip_rows[0]:self.clip_rows[1], self.clip_cols[0]:self.clip_cols[1]] - self.tmrt_ts = treeinput.tmrt_ts[self.clip_rows[0]:self.clip_rows[1], self.clip_cols[0]:self.clip_cols[1]] - self.tmrt_s = treeinput.tmrt_s[self.clip_rows[0]:self.clip_rows[1], self.clip_cols[0]:self.clip_cols[1]] + self.dsm = treeinput.dsm[ + self.clip_rows[0] : self.clip_rows[1], + self.clip_cols[0] : self.clip_cols[1], + ] + self.cdsm = treeinput.cdsm[ + self.clip_rows[0] : self.clip_rows[1], + self.clip_cols[0] : self.clip_cols[1], + ] + self.cdsm_b = treeinput.cdsm_b[ + self.clip_rows[0] : self.clip_rows[1], + self.clip_cols[0] : self.clip_cols[1], + ] + self.shadow = treeinput.shadow[ + self.clip_rows[0] : self.clip_rows[1], + self.clip_cols[0] : self.clip_cols[1], + ] + self.tmrt_ts = treeinput.tmrt_ts[ + self.clip_rows[0] : self.clip_rows[1], + self.clip_cols[0] : self.clip_cols[1], + ] + self.tmrt_s = treeinput.tmrt_s[ + self.clip_rows[0] : self.clip_rows[1], + self.clip_cols[0] : self.clip_cols[1], + ] # Save other stuff from input rasters self.dataSet = treeinput.dataSet diff --git a/functions/TreePlanter/TreePlanter/TreePlanterHillClimber.py b/functions/TreePlanter/TreePlanter/TreePlanterHillClimber.py index f5251be..3e7de79 100644 --- a/functions/TreePlanter/TreePlanter/TreePlanterHillClimber.py +++ b/functions/TreePlanter/TreePlanter/TreePlanterHillClimber.py @@ -5,37 +5,62 @@ from ..TreePlanter.adjustments import treenudge from ..TreePlanter import StartingPositions + def combine(tup, t): return tuple(itertools.combinations(tup, t)) -def treeoptinit(treerasters, treeinput, positions, treedata, shadow_rg, tmrt_1d, trees, r_iters, sa, feedback): - #time_sum = 0 +def treeoptinit( + treerasters, + treeinput, + positions, + treedata, + shadow_rg, + tmrt_1d, + trees, + r_iters, + sa, + feedback, +): + + # time_sum = 0 dia = treedata.dia # Diameter of tree canopy - i_tmrt = np.zeros((r_iters)) # Empty vector to be filled with Tmrt values for each tree - i_y = np.zeros((r_iters, trees)) # Empty vector to be filled with corresponding y position of the above - i_x = np.zeros((r_iters, trees)) # Empty vector to be filled with corresponding x position of the above + i_tmrt = np.zeros( + (r_iters) + ) # Empty vector to be filled with Tmrt values for each tree + i_y = np.zeros( + (r_iters, trees) + ) # Empty vector to be filled with corresponding y position of the above + i_x = np.zeros( + (r_iters, trees) + ) # Empty vector to be filled with corresponding x position of the above - tree_pos_all = np.zeros((r_iters,trees)) + tree_pos_all = np.zeros((r_iters, trees)) # Pad for kernel for neighboring trees - pos_m_pad_t = np.pad(positions.pos_m, pad_width=((1, 1), (1, 1)), mode='constant', - constant_values=0) + pos_m_pad_t = np.pad( + positions.pos_m, + pad_width=((1, 1), (1, 1)), + mode="constant", + constant_values=0, + ) break_loop = 0 - #sa = 0 # Hill-climbing with random restarts - #sa = 1 # Genetic x or y random - #sa = 2 # Genetic parents + # sa = 0 # Hill-climbing with random restarts + # sa = 1 # Genetic x or y random + # sa = 2 # Genetic parents - percentage_progress = np.array([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]) + percentage_progress = np.array( + [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9] + ) iters_progress = np.int_(percentage_progress * r_iters) progress_counter = itertools.cycle(range(percentage_progress.shape[0])) progress = progress_counter.__next__() - if (sa == 1): + if sa == 1: tree_pos_c = np.zeros((trees)) tree_pos_y = 0 tree_pos_x = 0 @@ -50,7 +75,10 @@ def treeoptinit(treerasters, treeinput, positions, treedata, shadow_rg, tmrt_1d, # Printing progress if counter == iters_progress[progress]: - feedback.setProgressText(str(percentage_progress[progress] * 100) + " percent of iterations finished...") + feedback.setProgressText( + str(percentage_progress[progress] * 100) + + " percent of iterations finished..." + ) progress = progress_counter.__next__() r_count = 0 @@ -63,24 +91,51 @@ def treeoptinit(treerasters, treeinput, positions, treedata, shadow_rg, tmrt_1d, # If sa = 0, random restart, i # If sa = 1 evolutionary restart, i.e. y or x is random, the other is kept from previous run (previous local optimum) if sa == 0: - tree_pos, tp_c, break_loop = StartingPositions.random_start(positions.pos[:,0], trees, tree_pos_all, r_iters) + tree_pos, tp_c, break_loop = StartingPositions.random_start( + positions.pos[:, 0], trees, tree_pos_all, r_iters + ) elif sa == 1: - tree_pos_y, tree_pos_x, tree_pos, tree_pos_c, tp_c, break_loop = \ - StartingPositions.genetic_start(tree_pos_x, tree_pos_y, tree_pos_c, positions, trees, tree_pos_all, - r_iters, counter, dia) - - if (tp_c == 100): - feedback.setProgressText('Possibly too many trees to fit in planting area. Try a lower number.') + ( + tree_pos_y, + tree_pos_x, + tree_pos, + tree_pos_c, + tp_c, + break_loop, + ) = StartingPositions.genetic_start( + tree_pos_x, + tree_pos_y, + tree_pos_c, + positions, + trees, + tree_pos_all, + r_iters, + counter, + dia, + ) + + if tp_c == 100: + feedback.setProgressText( + "Possibly too many trees to fit in planting area. Try a lower number." + ) break - if ((counter == 0) | (sa == 0)): - tree_pos_y = np.zeros((trees), dtype=int) # Random y-positions for trees - tree_pos_x = np.zeros((trees), dtype=int) # Random x-positions for trees + if (counter == 0) | (sa == 0): + tree_pos_y = np.zeros( + (trees), dtype=int + ) # Random y-positions for trees + tree_pos_x = np.zeros( + (trees), dtype=int + ) # Random x-positions for trees # Y and X positions of starting positions for i2 in range(tree_pos.__len__()): - tree_pos_x[i2] = positions.pos[positions.pos[:, 0] == tree_pos[i2], 1] - tree_pos_y[i2] = positions.pos[positions.pos[:, 0] == tree_pos[i2], 2] + tree_pos_x[i2] = positions.pos[ + positions.pos[:, 0] == tree_pos[i2], 1 + ] + tree_pos_y[i2] = positions.pos[ + positions.pos[:, 0] == tree_pos[i2], 2 + ] # Euclidean distance between random positions so that trees are not too close to each other it_comb = combine(tree_pos, 2) @@ -95,80 +150,116 @@ def treeoptinit(treerasters, treeinput, positions, treedata, shadow_rg, tmrt_1d, b = np.array(([by[0], bx[0]])) eucl_dist[i3, 0] = np.linalg.norm(a - b) - if (np.min(eucl_dist[:, 0]) >= dia): + if np.min(eucl_dist[:, 0]) >= dia: r_count = 1 # Lägg till alla positioner för eucl >= dia - #r_count = 1 + # r_count = 1 tree_pos_all[counter] = np.sort(tree_pos) - if (break_loop == (r_iters + 1)): + if break_loop == (r_iters + 1): break - tp_nc = np.zeros((trees, 1)) # 1 if tree is in the same position as in previous iteration, otherwise 0 - tp_nc_a = np.zeros((trees)) # 1 if tree is stuck but have been checked for better position and none was found, otherwise 0 + tp_nc = np.zeros( + (trees, 1) + ) # 1 if tree is in the same position as in previous iteration, otherwise 0 + tp_nc_a = np.zeros( + (trees) + ) # 1 if tree is stuck but have been checked for better position and none was found, otherwise 0 - ti = itertools.cycle(range(trees)) # Iterator to move between trees moving around in the study area + ti = itertools.cycle( + range(trees) + ) # Iterator to move between trees moving around in the study area # Create matrices for tree paths and add starting positions tree_paths_temp = np.zeros((treerasters.rows, treerasters.cols, trees)) for t_i in range(trees): - tree_paths_temp[tree_pos_y[t_i],tree_pos_x[t_i], t_i] = 1 + tree_paths_temp[tree_pos_y[t_i], tree_pos_x[t_i], t_i] = 1 - t1 = np.zeros((1,5)) + t1 = np.zeros((1, 5)) # Moving trees, i.e. optimization - while np.sum(tp_nc[:,0]) < trees: + while np.sum(tp_nc[:, 0]) < trees: i = ti.__next__() # Running optimizer # t1 = best shading position - t1, nc, y_out, x_out = HillClimberAlgorithm.topt(tree_pos_y, tree_pos_x, treerasters, treeinput, dia, shadow_rg, tmrt_1d, positions, i, pos_m_pad_t, t1) + t1, nc, y_out, x_out = HillClimberAlgorithm.topt( + tree_pos_y, + tree_pos_x, + treerasters, + treeinput, + dia, + shadow_rg, + tmrt_1d, + positions, + i, + pos_m_pad_t, + t1, + ) tp_nc[i, 0] = nc - if (tp_nc[i,0] == 0): + if tp_nc[i, 0] == 0: tree_pos_y = y_out tree_pos_x = x_out tp_nc_a[i] = 0 # Possibly moving trees that are stuck, where tree shadows intersect - elif ((tp_nc_a[i] == 0) & (tp_nc[i,0] == 1)): - y_out, x_out, tp_nc, tp_nc_a, t1 = treenudge(y_out, x_out, tp_nc, tp_nc_a, i, t1, counter, i_tmrt, treerasters, treeinput, positions, - tmrt_1d) - if ((tp_nc_a[i] == 1) & (tp_nc[i, 0] == 0)): + elif (tp_nc_a[i] == 0) & (tp_nc[i, 0] == 1): + y_out, x_out, tp_nc, tp_nc_a, t1 = treenudge( + y_out, + x_out, + tp_nc, + tp_nc_a, + i, + t1, + counter, + i_tmrt, + treerasters, + treeinput, + positions, + tmrt_1d, + ) + if (tp_nc_a[i] == 1) & (tp_nc[i, 0] == 0): tree_pos_y = y_out tree_pos_x = x_out - if (tp_nc[i,0] == 0): # Tree paths of current random run + if tp_nc[i, 0] == 0: # Tree paths of current random run tree_paths_temp[tree_pos_y[i], tree_pos_x[i], i] = 1 # Changing position of tree - if (t1[0, 2] > i_tmrt[counter]): + if t1[0, 2] > i_tmrt[counter]: i_tmrt[counter] = t1[0, 2] i_x[counter, :] = tree_pos_x[:] i_y[counter, :] = tree_pos_y[:] # Check whether there is a new equal or worse position for each individual tree. - if (sa == 1): + if sa == 1: if i_tmrt[counter] > i_tmrt_max: i_tmrt_max = np.max(i_tmrt) for idx in range(trees): - d_tmrt_p[idx] = treerasters.d_tmrt[tree_pos_y[idx], tree_pos_x[idx]] + d_tmrt_p[idx] = treerasters.d_tmrt[ + tree_pos_y[idx], tree_pos_x[idx] + ] else: d_tmrt_temp = np.zeros((trees)) for idx in range(trees): - d_tmrt_temp[idx] = treerasters.d_tmrt[tree_pos_y[idx], tree_pos_x[idx]] + d_tmrt_temp[idx] = treerasters.d_tmrt[ + tree_pos_y[idx], tree_pos_x[idx] + ] low_p = d_tmrt_temp <= d_tmrt_p tree_pos_c[low_p] += 1 high_p = d_tmrt_temp > d_tmrt_p tree_pos_c[high_p] = 0 - if (i_tmrt[counter] == np.max(i_tmrt)): # Path of trees with best positions from starting to ending + if i_tmrt[counter] == np.max( + i_tmrt + ): # Path of trees with best positions from starting to ending tree_paths = tree_paths_temp.copy() - if (tp_c == 100): + if tp_c == 100: break # Progress bar @@ -185,12 +276,12 @@ def treeoptinit(treerasters, treeinput, positions, treedata, shadow_rg, tmrt_1d, # Calculate heat map for best 33 % positions # Returns all the unique positions found by the algorithm and the potential decrease in tmrt - #from misc import max_tmrt - #unique_tmrt, unique_tmrt_max = max_tmrt(i_y, i_x, i_tmrt, trees, positions.pos) + # from misc import max_tmrt + # unique_tmrt, unique_tmrt_max = max_tmrt(i_y, i_x, i_tmrt, trees, positions.pos) # Optimal positions of trees i_y = i_y[y[0][0], :] i_x = i_x[y[0][0], :] return i_y, i_x, t_max, i_y_all, i_x_all - #return i_y, i_x, unique_tmrt, unique_tmrt_max, tree_paths + # return i_y, i_x, unique_tmrt, unique_tmrt_max, tree_paths diff --git a/functions/TreePlanter/TreePlanter/TreePlanterPrepare.py b/functions/TreePlanter/TreePlanter/TreePlanterPrepare.py index b104007..7153648 100644 --- a/functions/TreePlanter/TreePlanter/TreePlanterPrepare.py +++ b/functions/TreePlanter/TreePlanter/TreePlanterPrepare.py @@ -2,14 +2,17 @@ from ..TreePlanter.TreePlanterClasses import Position from ..TreePlanter.TreePlanterTreeshade import tree_slice -def treeplanter(treeinput,treedata,treerasters,tmrt_1d): - treeinput.tmrt_s = treeinput.tmrt_s * treeinput.buildings # Remove all Tmrt values that are in shade or on top of buildings +def treeplanter(treeinput, treedata, treerasters, tmrt_1d): + + treeinput.tmrt_s = ( + treeinput.tmrt_s * treeinput.buildings + ) # Remove all Tmrt values that are in shade or on top of buildings bld_copy = treeinput.buildings.copy() # Creating boolean for where it is possible to plant a tree - bd_b = np.int_( np.ceil( (treedata.dia / 2) / treeinput.gt[1] ) ) + bd_b = np.int_(np.ceil((treedata.dia / 2) / treeinput.gt[1])) # Buffer on building raster so that trees can't be planted next to walls. Can be planted one radius from walls. for i1 in range(bd_b): @@ -17,7 +20,7 @@ def treeplanter(treeinput,treedata,treerasters,tmrt_1d): domain = np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) for i in range(1, treeinput.cols - 1): for j in range(1, treeinput.rows - 1): - dom = bld_copy[j - 1:j + 2, i - 1:i + 2] + dom = bld_copy[j - 1 : j + 2, i - 1 : i + 2] walls[j, i] = np.min(dom[np.where(domain == 1)]) walls = bld_copy - walls @@ -29,12 +32,20 @@ def treeplanter(treeinput,treedata,treerasters,tmrt_1d): bld_copy = bld_copy * treeinput.selected_area # Calculating sum of Tmrt in shade for each possible position in the Tmrt matrix - sum_tmrt_tsh = np.zeros((treeinput.rows, treeinput.cols)) # Empty matrix for sum of Tmrt in tree shadow - sum_tmrt = np.zeros((treeinput.rows, treeinput.cols)) # Empty matrix for sum of Tmrt in sun under tree shadow + sum_tmrt_tsh = np.zeros( + (treeinput.rows, treeinput.cols) + ) # Empty matrix for sum of Tmrt in tree shadow + sum_tmrt = np.zeros( + (treeinput.rows, treeinput.cols) + ) # Empty matrix for sum of Tmrt in sun under tree shadow - res_y, res_x = np.where(bld_copy == 1) # Coordinates for where it is possible to plant a tree (buildings, area of interest excluded) + res_y, res_x = np.where( + bld_copy == 1 + ) # Coordinates for where it is possible to plant a tree (buildings, area of interest excluded) - pos_ls = np.zeros((res_y.__len__(), 6)) # Length of vectors with y and x positions. Will have x and y positions, tmrt in shade and in sun and an id for each position + pos_ls = np.zeros( + (res_y.__len__(), 6) + ) # Length of vectors with y and x positions. Will have x and y positions, tmrt in shade and in sun and an id for each position index = 0 @@ -46,7 +57,9 @@ def treeplanter(treeinput,treedata,treerasters,tmrt_1d): x1 = np.int_(res_x[i] - treerasters.buffer_x[0]) x2 = np.int_(res_x[i] + treerasters.buffer_x[1]) - yslice1, xslice1, yslice2, xslice2 = tree_slice(y1,y2,x1,x2,treeinput,treerasters) + yslice1, xslice1, yslice2, xslice2 = tree_slice( + y1, y2, x1, x2, treeinput, treerasters + ) ts_temp1 = np.zeros((treeinput.rows, treeinput.cols)) ts_temp1[yslice2, xslice2] = treerasters.treeshade[yslice1, xslice1] @@ -55,27 +68,43 @@ def treeplanter(treeinput,treedata,treerasters,tmrt_1d): # Gör samma som i TreePlanterOptimizer (regional groups, etc) for j in range(tmrt_1d.__len__()): ts_temp2 = np.zeros((treeinput.rows, treeinput.cols)) - ts_temp2[yslice2, xslice2] = treerasters.treeshade_bool[yslice1, xslice1, j] - sum_tmrt[res_y[i],res_x[i]] += np.sum(ts_temp2 * treeinput.buildings * treeinput.shadow[:,:,j] * treeinput.tmrt_ts[:,:,j]) - sum_tmrt_tsh[res_y[i], res_x[i]] += np.sum(ts_temp2 * treeinput.buildings * treeinput.shadow[:,:,j] * tmrt_1d[j,0]) - - pos_ls[index, 1] = res_x[i] # X position of tree - pos_ls[index, 2] = res_y[i] # Y position of tree - pos_ls[index, 3] = sum_tmrt_tsh[res_y[i], res_x[i]] # Sum of Tmrt in tree shade - vector - pos_ls[index, 4] = sum_tmrt[res_y[i], res_x[i]] # Sum of Tmrt in same area as tree shade but sunlit - vector + ts_temp2[yslice2, xslice2] = treerasters.treeshade_bool[ + yslice1, xslice1, j + ] + sum_tmrt[res_y[i], res_x[i]] += np.sum( + ts_temp2 + * treeinput.buildings + * treeinput.shadow[:, :, j] + * treeinput.tmrt_ts[:, :, j] + ) + sum_tmrt_tsh[res_y[i], res_x[i]] += np.sum( + ts_temp2 + * treeinput.buildings + * treeinput.shadow[:, :, j] + * tmrt_1d[j, 0] + ) + + pos_ls[index, 1] = res_x[i] # X position of tree + pos_ls[index, 2] = res_y[i] # Y position of tree + pos_ls[index, 3] = sum_tmrt_tsh[ + res_y[i], res_x[i] + ] # Sum of Tmrt in tree shade - vector + pos_ls[index, 4] = sum_tmrt[ + res_y[i], res_x[i] + ] # Sum of Tmrt in same area as tree shade but sunlit - vector pos_ls[index, 5] = 1 index += 1 - pos_bool = pos_ls[:,3] != 0 - pos_ls = pos_ls[pos_bool,:] + pos_bool = pos_ls[:, 3] != 0 + pos_ls = pos_ls[pos_bool, :] # Gives a unique value ranging from 1 to length of pos_ls.shape[0]+1, for each position where it is possible to plant a tree - pos_ls[:, 0] = np.arange(1,pos_ls.shape[0]+1) + pos_ls[:, 0] = np.arange(1, pos_ls.shape[0] + 1) # Adding sum_tmrt and sum_tmrt_tsh to the Treerasters class as well as calculating the difference between sunlit and shaded treerasters.tmrt(sum_tmrt, sum_tmrt_tsh) # Adding pos_ls and adding all unique values from pos_ls to their respective positions in a 2D matrix and returns a Positions class - positions = Position(pos_ls,treeinput.rows,treeinput.cols) + positions = Position(pos_ls, treeinput.rows, treeinput.cols) return treerasters, positions diff --git a/functions/TreePlanter/TreePlanter/TreePlanterTreeshade.py b/functions/TreePlanter/TreePlanter/TreePlanterTreeshade.py index 66731d3..c367db9 100644 --- a/functions/TreePlanter/TreePlanter/TreePlanterTreeshade.py +++ b/functions/TreePlanter/TreePlanter/TreePlanterTreeshade.py @@ -1,8 +1,10 @@ import numpy as np + # from ..TreePlanter.adjustments import tree_slice + # This function returns a raster with a boolean shadow and the regional group shadow for the tree in position y,x -def tsh_gen(y,x,treerasters,treeinput): +def tsh_gen(y, x, treerasters, treeinput): tsh_pos_pad = np.zeros((treeinput.rows, treeinput.cols, y.__len__())) tsh_pos_bool_pad = np.zeros((treeinput.rows, treeinput.cols, y.__len__())) tsh_pos_bool_pad_all = np.zeros((treeinput.rows, treeinput.cols)) @@ -14,12 +16,16 @@ def tsh_gen(y,x,treerasters,treeinput): x1 = np.int_(x[i] - treerasters.buffer_x[0]) x2 = np.int_(x[i] + treerasters.buffer_x[1]) - yslice1, xslice1, yslice2, xslice2 = tree_slice(y1,y2,x1,x2,treeinput,treerasters) + yslice1, xslice1, yslice2, xslice2 = tree_slice( + y1, y2, x1, x2, treeinput, treerasters + ) - tsh_pos_pad[yslice2, xslice2,i] = treerasters.treeshade_rg[yslice1, xslice1] - tsh_pos_pad[:, :, i] = tsh_pos_pad[:,:,i] * treeinput.buildings_pad - tsh_pos_bool_pad[:,:,i] = tsh_pos_pad[:,:,i] > 0 - tsh_pos_bool_pad_all += tsh_pos_bool_pad[:,:,i] + tsh_pos_pad[yslice2, xslice2, i] = treerasters.treeshade_rg[ + yslice1, xslice1 + ] + tsh_pos_pad[:, :, i] = tsh_pos_pad[:, :, i] * treeinput.buildings_pad + tsh_pos_bool_pad[:, :, i] = tsh_pos_pad[:, :, i] > 0 + tsh_pos_bool_pad_all += tsh_pos_bool_pad[:, :, i] compare = 0 if y.__len__() > 1: @@ -29,10 +35,19 @@ def tsh_gen(y,x,treerasters,treeinput): tsh_pos_bool_pad_all = tsh_pos_bool_pad_all > 0 - return tsh_pos_pad, tsh_pos_bool_pad, tsh_pos_bool_pad_all, tsh_pos_bool_pad_all_, compare + return ( + tsh_pos_pad, + tsh_pos_bool_pad, + tsh_pos_bool_pad_all, + tsh_pos_bool_pad_all_, + compare, + ) + -def tsh_gen_ts(y,x,treerasters,treeinput): - tsh_bool_pad = np.zeros((treeinput.rows, treeinput.cols, treerasters.treeshade_bool.shape[2])) +def tsh_gen_ts(y, x, treerasters, treeinput): + tsh_bool_pad = np.zeros( + (treeinput.rows, treeinput.cols, treerasters.treeshade_bool.shape[2]) + ) tsh_bool_pad_large = np.zeros((treeinput.rows, treeinput.cols)) compare = 0 @@ -42,10 +57,16 @@ def tsh_gen_ts(y,x,treerasters,treeinput): x1 = np.int_(x[i] - treerasters.buffer_x[0]) x2 = np.int_(x[i] + treerasters.buffer_x[1]) - yslice1, xslice1, yslice2, xslice2 = tree_slice(y1,y2,x1,x2,treeinput,treerasters) + yslice1, xslice1, yslice2, xslice2 = tree_slice( + y1, y2, x1, x2, treeinput, treerasters + ) - tsh_bool_pad[yslice2 , xslice2, :] += treerasters.treeshade_bool[yslice1, xslice1, :] - tsh_bool_pad_large[yslice2, xslice2] += treerasters.treeshade_rg[yslice1, xslice1] > 0 + tsh_bool_pad[yslice2, xslice2, :] += treerasters.treeshade_bool[ + yslice1, xslice1, : + ] + tsh_bool_pad_large[yslice2, xslice2] += ( + treerasters.treeshade_rg[yslice1, xslice1] > 0 + ) if y.__len__() > 1: if np.any(tsh_bool_pad > 1): @@ -56,7 +77,8 @@ def tsh_gen_ts(y,x,treerasters,treeinput): return tsh_bool_pad, tsh_bool_pad_large, compare -def tsh_gen_mt1(y,x,treerasters,treeinput): + +def tsh_gen_mt1(y, x, treerasters, treeinput): tsh_bool_pad_large = np.zeros((treeinput.rows, treeinput.cols)) for i in range(y.__len__()): @@ -65,16 +87,23 @@ def tsh_gen_mt1(y,x,treerasters,treeinput): x1 = np.int_(x[i] - treerasters.buffer_x[0]) x2 = np.int_(x[i] + treerasters.buffer_x[1]) - yslice1, xslice1, yslice2, xslice2 = tree_slice(y1,y2,x1,x2,treeinput,treerasters) + yslice1, xslice1, yslice2, xslice2 = tree_slice( + y1, y2, x1, x2, treeinput, treerasters + ) - tsh_bool_pad_large[yslice2, xslice2] += treerasters.treeshade_rg[yslice1, xslice1] > 0 + tsh_bool_pad_large[yslice2, xslice2] += ( + treerasters.treeshade_rg[yslice1, xslice1] > 0 + ) tsh_bool_pad_large = tsh_bool_pad_large > 0 return tsh_bool_pad_large -def tsh_gen_mt2(y,x,treerasters,treeinput): - tsh_bool_pad = np.zeros((treeinput.rows, treeinput.cols, treerasters.treeshade_bool.shape[2])) + +def tsh_gen_mt2(y, x, treerasters, treeinput): + tsh_bool_pad = np.zeros( + (treeinput.rows, treeinput.cols, treerasters.treeshade_bool.shape[2]) + ) for i in range(y.__len__()): y1 = np.int_(y[i] - treerasters.buffer_y[0]) @@ -82,16 +111,23 @@ def tsh_gen_mt2(y,x,treerasters,treeinput): x1 = np.int_(x[i] - treerasters.buffer_x[0]) x2 = np.int_(x[i] + treerasters.buffer_x[1]) - yslice1, xslice1, yslice2, xslice2 = tree_slice(y1,y2,x1,x2,treeinput,treerasters) + yslice1, xslice1, yslice2, xslice2 = tree_slice( + y1, y2, x1, x2, treeinput, treerasters + ) - tsh_bool_pad[yslice2, xslice2, :] += treerasters.treeshade_bool[yslice1, xslice1, :] + tsh_bool_pad[yslice2, xslice2, :] += treerasters.treeshade_bool[ + yslice1, xslice1, : + ] tsh_bool_pad = tsh_bool_pad > 0 return tsh_bool_pad -''' Slicing to fit shadows, cdsm, etc, into larger rasters''' -def tree_slice(y1,y2,x1,x2,treeinput,treerasters): + +""" Slicing to fit shadows, cdsm, etc, into larger rasters""" + + +def tree_slice(y1, y2, x1, x2, treeinput, treerasters): if y1 < 0: y1t = np.abs(y1) y1 = 0 @@ -102,7 +138,7 @@ def tree_slice(y1,y2,x1,x2,treeinput,treerasters): y2 = treeinput.rows else: y2t = treerasters.treeshade.shape[0] - + if x1 < 0: x1t = np.abs(x1) x1 = 0 @@ -119,4 +155,4 @@ def tree_slice(y1,y2,x1,x2,treeinput,treerasters): yslice2 = slice(y1, y2) xslice2 = slice(x1, x2) - return yslice1,xslice1,yslice2,xslice2 \ No newline at end of file + return yslice1, xslice1, yslice2, xslice2 diff --git a/functions/TreePlanter/TreePlanter/adjustments.py b/functions/TreePlanter/TreePlanter/adjustments.py index 081ecac..830e083 100644 --- a/functions/TreePlanter/TreePlanter/adjustments.py +++ b/functions/TreePlanter/TreePlanter/adjustments.py @@ -3,18 +3,42 @@ from ..TreePlanter.TreePlanterTreeshade import tsh_gen_ts -def treenudge(y_out, x_out, tp_nc, tp_nc_a, i, t1, counter, i_tmrt, treerasters, treeinput, positions, tmrt_1d): + +def treenudge( + y_out, + x_out, + tp_nc, + tp_nc_a, + i, + t1, + counter, + i_tmrt, + treerasters, + treeinput, + positions, + tmrt_1d, +): tree_adjusted = 0 - if (np.sum(tp_nc[:, 0]) > 1): # If more than one tree is standing still, check if shadows are next to each other + if ( + np.sum(tp_nc[:, 0]) > 1 + ): # If more than one tree is standing still, check if shadows are next to each other nc_r = np.where(tp_nc[:, 0] == 1) # Rows of trees standing still nc_y = np.squeeze(y_out[nc_r]) # Y-positions of trees nc_x = np.squeeze(x_out[nc_r]) # X-positions of trees - tsh_, tsh_bool, compare, = tsh_gen_ts(nc_y, nc_x, treerasters, treeinput) # Shadows of trees - - if (compare == 1): # If there are less regional groups than trees standing still, some must be side by side + ( + tsh_, + tsh_bool, + compare, + ) = tsh_gen_ts( + nc_y, nc_x, treerasters, treeinput + ) # Shadows of trees + + if ( + compare == 1 + ): # If there are less regional groups than trees standing still, some must be side by side label_nc, num_feat_nc = label(tsh_bool) # Regional groups nc_y_c = np.squeeze(y_out.copy()) @@ -33,9 +57,11 @@ def treenudge(y_out, x_out, tp_nc, tp_nc_a, i, t1, counter, i_tmrt, treerasters, # Only look at the regional group of the currently moving tree nc_i_r = np.where(nc_label == nc_i) - nc_sum = np.sum(tp_nc[nc_i_r, 0]) # Calculate the sum stuck trees in rg nc_i (should be all if adjustment should be made) + nc_sum = np.sum( + tp_nc[nc_i_r, 0] + ) # Calculate the sum stuck trees in rg nc_i (should be all if adjustment should be made) - if ((nc_i_r[0].__len__() > 1) & (nc_sum == nc_i_r[0].__len__())): + if (nc_i_r[0].__len__() > 1) & (nc_sum == nc_i_r[0].__len__()): y_dir = np.array([1, -1, 0, 0]) # New temp y-position x_dir = np.array([0, 0, 1, -1]) # New temp x-position @@ -45,38 +71,80 @@ def treenudge(y_out, x_out, tp_nc, tp_nc_a, i, t1, counter, i_tmrt, treerasters, y_dir_temp = np.squeeze(y_out[nc_i_r]) + y_dir[iy] x_dir_temp = np.squeeze(x_out[nc_i_r]) + x_dir[iy] for iy1 in range(y_dir_temp.shape[0]): - if not (np.any(positions.pos[(positions.pos[:, 1] == x_dir_temp[iy1]) & ( - positions.pos[:, 2] == y_dir_temp[iy1]), 0])): + if not ( + np.any( + positions.pos[ + (positions.pos[:, 1] == x_dir_temp[iy1]) + & (positions.pos[:, 2] == y_dir_temp[iy1]), + 0, + ] + ) + ): dir_bool[iy] = 0 dir_bool = np.bool_(dir_bool) - y_dir = y_dir[dir_bool] # Remove positions where it is not possible to put a tree - x_dir = x_dir[dir_bool] # Remove positions where it is not possible to put a tree + y_dir = y_dir[ + dir_bool + ] # Remove positions where it is not possible to put a tree + x_dir = x_dir[ + dir_bool + ] # Remove positions where it is not possible to put a tree tmrt_nc = np.zeros((y_dir.shape[0], 3)) # New tmrt - if (np.any(y_dir.shape[0])): # Only go in to if if there are any possible locations for trees - for iy in range(y_dir.shape[0]): # Loop for shadows for new positions - nc_y_c[nc_i_r] = np.squeeze(y_out[nc_i_r]) + y_dir[iy] # New y-position - nc_x_c[nc_i_r] = np.squeeze(x_out[nc_i_r]) + x_dir[iy] # New x-position - - tsh_bool_nc_t, tsh_bool_nc_t_large, comp_ = tsh_gen_ts(nc_y_c, nc_x_c, treerasters, - treeinput) # Shadows and rg for new position + if np.any( + y_dir.shape[0] + ): # Only go in to if if there are any possible locations for trees + for iy in range( + y_dir.shape[0] + ): # Loop for shadows for new positions + nc_y_c[nc_i_r] = ( + np.squeeze(y_out[nc_i_r]) + y_dir[iy] + ) # New y-position + nc_x_c[nc_i_r] = ( + np.squeeze(x_out[nc_i_r]) + x_dir[iy] + ) # New x-position + + tsh_bool_nc_t, tsh_bool_nc_t_large, comp_ = tsh_gen_ts( + nc_y_c, nc_x_c, treerasters, treeinput + ) # Shadows and rg for new position # Calculate Tmrt for intersecting shadows for j in range(tmrt_1d.shape[0]): - tsh_bool_nc_t[:, :, j] = tsh_bool_nc_t[:, :, j] * treeinput.shadow[:, :, - j] * treeinput.buildings - tmrt_nc[iy, 0] += np.sum(tsh_bool_nc_t[:, :, j] * tmrt_1d[j, 0]) # Tree shade - tmrt_nc[iy, 1] += np.sum(tsh_bool_nc_t[:, :, j] * treeinput.tmrt_ts[:, :, j]) # Sunlit - tmrt_nc[iy, 2] = tmrt_nc[iy, 1] - tmrt_nc[iy, 0] # New Tmrt (sunlit - tree shade) - - if (np.around(np.max(tmrt_nc[:, 2]), decimals=1) > np.around(i_tmrt[counter], decimals=1)): # If any new Tmrt decrease is higher, continue - nc_max_r = np.where(tmrt_nc[:, 2] == np.max(tmrt_nc[:, 2])) # Where is new Tmrt decrease highest - - if (nc_max_r[0].__len__() > 1): # If more than one positions has max tmrt, choose a random of the positions + tsh_bool_nc_t[:, :, j] = ( + tsh_bool_nc_t[:, :, j] + * treeinput.shadow[:, :, j] + * treeinput.buildings + ) + tmrt_nc[iy, 0] += np.sum( + tsh_bool_nc_t[:, :, j] * tmrt_1d[j, 0] + ) # Tree shade + tmrt_nc[iy, 1] += np.sum( + tsh_bool_nc_t[:, :, j] + * treeinput.tmrt_ts[:, :, j] + ) # Sunlit + tmrt_nc[iy, 2] = ( + tmrt_nc[iy, 1] - tmrt_nc[iy, 0] + ) # New Tmrt (sunlit - tree shade) + + if np.around( + np.max(tmrt_nc[:, 2]), decimals=1 + ) > np.around( + i_tmrt[counter], decimals=1 + ): # If any new Tmrt decrease is higher, continue + nc_max_r = np.where( + tmrt_nc[:, 2] == np.max(tmrt_nc[:, 2]) + ) # Where is new Tmrt decrease highest + + if ( + nc_max_r[0].__len__() > 1 + ): # If more than one positions has max tmrt, choose a random of the positions nc_max_r = np.random.choice(nc_max_r[0], 1) - nc_y_out = np.squeeze(y_out[nc_i_r]) + y_dir[nc_max_r] # New y-positions - nc_x_out = np.squeeze(x_out[nc_i_r]) + x_dir[nc_max_r] # New x-positions + nc_y_out = ( + np.squeeze(y_out[nc_i_r]) + y_dir[nc_max_r] + ) # New y-positions + nc_x_out = ( + np.squeeze(x_out[nc_i_r]) + x_dir[nc_max_r] + ) # New x-positions nc_tmrt_out = np.max(tmrt_nc[nc_max_r, 2]) # New Tmrt y_out[nc_i_r] = nc_y_out # New y-positions @@ -86,72 +154,134 @@ def treenudge(y_out, x_out, tp_nc, tp_nc_a, i, t1, counter, i_tmrt, treerasters, t1[0, 3] = y_out[i] t1[0, 4] = x_out[i] - tp_nc[nc_i_r, 0] = 0 # Reset tp_nc, i.e. trees have moved - tp_nc_a[nc_i_r] = 1 # Reset tp_nc_a, i.e. trees have been adjusted + tp_nc[nc_i_r, 0] = ( + 0 # Reset tp_nc, i.e. trees have moved + ) + tp_nc_a[nc_i_r] = ( + 1 # Reset tp_nc_a, i.e. trees have been adjusted + ) return y_out, x_out, tp_nc, tp_nc_a, t1 + ## Adjusting tree with lowest Tmrt def tree_adjust(i_y, i_x, i_tmrt, counter, trees, treerasters, treeinput): - a_counter = 0 # Adjustment counter + a_counter = 0 # Adjustment counter while a_counter < trees: tmrt_temp = np.zeros((trees)) for ix in range(trees): - tmrt_temp[ix] = treerasters.d_tmrt[np.int_(i_y[counter,ix]),np.int_(i_x[counter,ix])] + tmrt_temp[ix] = treerasters.d_tmrt[ + np.int_(i_y[counter, ix]), np.int_(i_x[counter, ix]) + ] - y_min = tmrt_temp[:] == np.min(tmrt_temp[:]) # Bool of position of tree with least decrease in Tmrt + y_min = tmrt_temp[:] == np.min( + tmrt_temp[:] + ) # Bool of position of tree with least decrease in Tmrt if y_min.shape[0] > 1: - y_min = y_min.cumsum(axis=0).cumsum(axis=0) == 1 # If more than one index with np.min, return only first - y_max = tmrt_temp[:] != np.min(tmrt_temp[:]) # Bool of positions of the other trees (highest) - y_te = i_y[counter,:] # Current y-positions of all trees for current run - x_te = i_x[counter,:] # Current x-positions of all trees for current run - y_high = np.int_(y_te[y_max]) # y-positions of trees with highest decrease in Tmrt - x_high = np.int_(x_te[y_max]) # x-positions of trees with highest decrease in Tmrt - y_low = np.int_(y_te[y_min]) # y-position of tree with lowest decrease in Tmrt - x_low = np.int_(x_te[y_min]) # x-position of tree with lowest decrease in Tmrt - - tsh_rg_temp, tsh_bool_temp, comp_ = tsh_gen_ts(y_high, x_high, treerasters, treeinput) # Shadows for high trees - tsh_rg_min, tsh_bool_min, comp__ = tsh_gen_ts(y_low, x_low, treerasters, treeinput) # Shadows for low tree - - d_tmrt_pad = np.pad(treerasters.d_tmrt, pad_width=((treerasters.tpy[0], treerasters.tpy[0]), (treerasters.tpx[0], treerasters.tpx[0])), mode='constant', - constant_values=0) # Padded sun vs shade tmrt rasters - - tsh_bool_all = tsh_bool_temp == 0 # All pixels that are sunlit - - d_tmrt_pad = treeinput.d_tmrt * tsh_bool_all # Remaining pixels, i.e. pixels that are not shaded for sun vs. shade - - tsh_bool_all = np.bool_(1 - tsh_bool_all) # Reverse tsh_bool_all + y_min = ( + y_min.cumsum(axis=0).cumsum(axis=0) == 1 + ) # If more than one index with np.min, return only first + y_max = tmrt_temp[:] != np.min( + tmrt_temp[:] + ) # Bool of positions of the other trees (highest) + y_te = i_y[ + counter, : + ] # Current y-positions of all trees for current run + x_te = i_x[ + counter, : + ] # Current x-positions of all trees for current run + y_high = np.int_( + y_te[y_max] + ) # y-positions of trees with highest decrease in Tmrt + x_high = np.int_( + x_te[y_max] + ) # x-positions of trees with highest decrease in Tmrt + y_low = np.int_( + y_te[y_min] + ) # y-position of tree with lowest decrease in Tmrt + x_low = np.int_( + x_te[y_min] + ) # x-position of tree with lowest decrease in Tmrt + + tsh_rg_temp, tsh_bool_temp, comp_ = tsh_gen_ts( + y_high, x_high, treerasters, treeinput + ) # Shadows for high trees + tsh_rg_min, tsh_bool_min, comp__ = tsh_gen_ts( + y_low, x_low, treerasters, treeinput + ) # Shadows for low tree + + d_tmrt_pad = np.pad( + treerasters.d_tmrt, + pad_width=( + (treerasters.tpy[0], treerasters.tpy[0]), + (treerasters.tpx[0], treerasters.tpx[0]), + ), + mode="constant", + constant_values=0, + ) # Padded sun vs shade tmrt rasters + + tsh_bool_all = tsh_bool_temp == 0 # All pixels that are sunlit + + d_tmrt_pad = ( + treeinput.d_tmrt * tsh_bool_all + ) # Remaining pixels, i.e. pixels that are not shaded for sun vs. shade + + tsh_bool_all = np.bool_(1 - tsh_bool_all) # Reverse tsh_bool_all # See if old trees overlap, i.e. the tree that is to be adjusted overlaps with the other trees - if np.any((tsh_bool_all == 1) & (tsh_bool_min == 1)): # If not, proceed. If they overlap, adjustment for overlap needs to be made, etc. + if np.any( + (tsh_bool_all == 1) & (tsh_bool_min == 1) + ): # If not, proceed. If they overlap, adjustment for overlap needs to be made, etc. - d_tmrt_vec = d_tmrt_pad.flatten() # Flatten d_tmrt_pad and create vector + d_tmrt_vec = ( + d_tmrt_pad.flatten() + ) # Flatten d_tmrt_pad and create vector - d_tmrt_vec_s = -np.sort(-d_tmrt_vec) # Sort vector to find pixels with highest difference in Tmrt (sun vs. shade) + d_tmrt_vec_s = -np.sort( + -d_tmrt_vec + ) # Sort vector to find pixels with highest difference in Tmrt (sun vs. shade) - d_bool = d_tmrt_vec_s > np.min(tmrt_temp[:]) # All pixels that have higher difference in Tmrt compared to position of tree with lowest difference (decrease) - d_tmrt_vec_s = d_tmrt_vec_s[d_bool] # Remove all other pixels + d_bool = d_tmrt_vec_s > np.min( + tmrt_temp[:] + ) # All pixels that have higher difference in Tmrt compared to position of tree with lowest difference (decrease) + d_tmrt_vec_s = d_tmrt_vec_s[d_bool] # Remove all other pixels d_tmrt_vec_s = np.unique(d_tmrt_vec_s) # Only save unique values - a_nc = 0 # Adjustment change parameter + a_nc = 0 # Adjustment change parameter for ix in range(d_tmrt_vec_s.shape[0]): - tmrt_adjust = d_tmrt_vec_s[ix] # Trying values from d_tmrt_vec_s - y_adjust, x_adjust = np.where(d_tmrt_pad == tmrt_adjust) # Find coordinates for tmrt_adjust - for iy in range(y_adjust.shape[0]): # If there are more than one position with tmrt_adjust - y_temp = np.array([y_adjust[iy] - treerasters.tpy[0]]) # y-Position of tmrt_adjust - x_temp = np.array([x_adjust[iy] - treerasters.tpx[0]]) # x-Position of tmrt_adjust - tsh_rg_adjust, tsh_bool_adjust, comp___ = tsh_gen_ts(y_temp, x_temp, treerasters, treeinput) # Create boolean shadow from y_temp, x_temp - if not np.any((tsh_bool_all == 1) & (tsh_bool_adjust == 1)): # If the shadow of the new position does not overlap with the the other trees, proceed + tmrt_adjust = d_tmrt_vec_s[ + ix + ] # Trying values from d_tmrt_vec_s + y_adjust, x_adjust = np.where( + d_tmrt_pad == tmrt_adjust + ) # Find coordinates for tmrt_adjust + for iy in range( + y_adjust.shape[0] + ): # If there are more than one position with tmrt_adjust + y_temp = np.array( + [y_adjust[iy] - treerasters.tpy[0]] + ) # y-Position of tmrt_adjust + x_temp = np.array( + [x_adjust[iy] - treerasters.tpx[0]] + ) # x-Position of tmrt_adjust + tsh_rg_adjust, tsh_bool_adjust, comp___ = tsh_gen_ts( + y_temp, x_temp, treerasters, treeinput + ) # Create boolean shadow from y_temp, x_temp + if not np.any( + (tsh_bool_all == 1) & (tsh_bool_adjust == 1) + ): # If the shadow of the new position does not overlap with the the other trees, proceed y_te[y_min] = y_temp x_te[y_min] = x_temp i_y[counter, :] = y_te # New y-position i_x[counter, :] = x_te # New x-position - i_tmrt[counter] += (tmrt_adjust - np.min(tmrt_temp)) # New adjusted Tmrt + i_tmrt[counter] += tmrt_adjust - np.min( + tmrt_temp + ) # New adjusted Tmrt a_nc = 1 - print('Adjusted') + print("Adjusted") break else: break @@ -163,4 +293,4 @@ def tree_adjust(i_y, i_x, i_tmrt, counter, trees, treerasters, treeinput): break a_counter += 1 - return i_y, i_x, i_tmrt \ No newline at end of file + return i_y, i_x, i_tmrt diff --git a/functions/URock/CalculatesIndicators.py b/functions/URock/CalculatesIndicators.py index e412b7d..5b277d6 100644 --- a/functions/URock/CalculatesIndicators.py +++ b/functions/URock/CalculatesIndicators.py @@ -1,5 +1,5 @@ -#bandit: reload_config -#nosec B608 +# bandit: reload_config +# nosec B608 #!/usr/bin/env python3 # -*- coding: utf-8 -*- @@ -10,30 +10,28 @@ @author: Jérémy Bernard, University of Gothenburg """ - - - from . import DataUtil from .DataUtil import safe from .GlobalVariables import * import pandas as pd -def obstacleProperties(cursor, obstaclesTable, prefix = PREFIX_NAME): - """ Calculates obstacle properties (effective width and length) + +def obstacleProperties(cursor, obstaclesTable, prefix=PREFIX_NAME): + """Calculates obstacle properties (effective width and length) for a wind coming from North (thus you first need to rotate your - obstacles to make them facing north if you + obstacles to make them facing north if you want to study a different wind direction). The calculation method is based on Figure 1 of Nelson et al. (2008). Note that it is however adapted to our specific geometries which are sometimes far from rectangles... - + References: - Nelson, Matthew, Bhagirath Addepalli, Fawn Hornsby, Akshay Gowardhan, - Eric Pardyjak, et Michael Brown. « 5.2 Improvements to a Fast-Response + Nelson, Matthew, Bhagirath Addepalli, Fawn Hornsby, Akshay Gowardhan, + Eric Pardyjak, et Michael Brown. « 5.2 Improvements to a Fast-Response Urban Wind Model », 2008. - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries @@ -41,26 +39,25 @@ def obstacleProperties(cursor, obstaclesTable, prefix = PREFIX_NAME): Name of the table containing the obstacle geometries to characterize prefix: String, default PREFIX_NAME Prefix to add to the output table name - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ obstaclePropertiesTable: String Name of the table containing the properties of each obstacle""" print("Calculates obstacle properties") - + # Output base name outputBaseName = "PROPERTIES" - + # Name of the output table - obstaclePropertiesTable = DataUtil.prefix(outputBaseName, - prefix = prefix) - + obstaclePropertiesTable = DataUtil.prefix(outputBaseName, prefix=prefix) + # Calculates the effective width (Weff) and effective length (Leff) # of each obstacle, respectively based on their maximum cross-wind and # along-wind extends of the obstacle, weighted by the area ratio between # obstacle area and envelope area - query =safe(""" + query = safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0} @@ -72,22 +69,25 @@ def obstacleProperties(cursor, obstaclesTable, prefix = PREFIX_NAME): (ST_YMAX(ST_ENVELOPE({3}))-ST_YMIN({3}))*ST_AREA({3})/ST_AREA(ST_ENVELOPE({3})) AS {7}, {8}, {9} - FROM {5}""").format(obstaclePropertiesTable, - ID_FIELD_STACKED_BLOCK, - ID_FIELD_BLOCK, - GEOM_FIELD, - HEIGHT_FIELD, - obstaclesTable, - EFFECTIVE_WIDTH_FIELD, - EFFECTIVE_LENGTH_FIELD, - BASE_HEIGHT_FIELD, - CAVITY_BASE_HEIGHT_FIELD) + FROM {5}""").format( + obstaclePropertiesTable, + ID_FIELD_STACKED_BLOCK, + ID_FIELD_BLOCK, + GEOM_FIELD, + HEIGHT_FIELD, + obstaclesTable, + EFFECTIVE_WIDTH_FIELD, + EFFECTIVE_LENGTH_FIELD, + BASE_HEIGHT_FIELD, + CAVITY_BASE_HEIGHT_FIELD, + ) cursor.execute(query) - + return obstaclePropertiesTable -def zoneProperties(cursor, obstaclePropertiesTable, prefix = PREFIX_NAME): - """ Calculates properties of the "Röckle" (some are not) zones: + +def zoneProperties(cursor, obstaclePropertiesTable, prefix=PREFIX_NAME): + """Calculates properties of the "Röckle" (some are not) zones: - for displacement: length Lf and vortex length Lfv (Bagal et al. - 2004), - for cavity: length Lr (equation 3 in Kaplan et al. - 1996), - for wake: length Lw (3*Lr, Kaplan et al. - 1996) @@ -95,20 +95,20 @@ def zoneProperties(cursor, obstaclePropertiesTable, prefix = PREFIX_NAME): downstream left facade angle and downstream right facade angle - for rooftop perpendicular: height Hcm and length Lc (Pol et al. 2006) - for rooftop corner: wind speed factor C1 (Bagal et al. 2004 "Implementation of rooftop...) - Note that L and W in the equation are respectively replaced by the + Note that L and W in the equation are respectively replaced by the effective length and width of each obstacle. - + References: - Bagal, N, ER Pardyjak, et MJ Brown. « Improved upwind cavity + Bagal, N, ER Pardyjak, et MJ Brown. « Improved upwind cavity parameterization for a fast response urban wind model ». In 84th Annual AMS Meeting. Seattle, WA, 2004. Kaplan, H., et N. Dinar. « A Lagrangian Dispersion Model for Calculating - Concentration Distribution within a Built-up Domain ». Atmospheric + Concentration Distribution within a Built-up Domain ». Atmospheric Environment 30, nᵒ 24 (1 décembre 1996): 4197‑4207. https://doi.org/10.1016/1352-2310(96)00144-6. - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries @@ -117,27 +117,27 @@ def zoneProperties(cursor, obstaclePropertiesTable, prefix = PREFIX_NAME): and height prefix: String, default PREFIX_NAME Prefix to add to the output table name - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ zonePropertiesTable: String - Name of the table containing the properties of each obstacle zones""" + Name of the table containing the properties of each obstacle zones + """ print("Calculates zone properties") - + # Output base name outputBaseName = "ZONE_LENGTH" - + # Name of the output table - zoneLengthTable = DataUtil.prefix(outputBaseName, - prefix = prefix) - + zoneLengthTable = DataUtil.prefix(outputBaseName, prefix=prefix) + # Create temporary table names (for tables that will be removed at the end of the process) tempoStackedLengthTab = DataUtil.postfix("TEMPO_STACKED_LENGTH_TAB") pointsStackedBlocks = DataUtil.postfix("POINTS_STACKED_BLOCKS") stackedBlocksXExt = DataUtil.postfix("STACKED_BLOCKS_X_EXT") stackedBlockAzimuths = DataUtil.postfix("STACKED_BLOCKS_AZIMUTHS") - + # Calculates the length (and sometimes height) of each zone: # - for displacement: Lf and Lfv (Bagal et al. - 2004), # - for cavity: Lr (equation 3 in Kaplan et al. - 1996), @@ -162,29 +162,32 @@ def zoneProperties(cursor, obstaclePropertiesTable, prefix = PREFIX_NAME): {15}, (ST_XMAX({2}) + ST_XMIN({2})) / 2 AS {16}, (ST_XMAX({2}) - ST_XMIN({2})) AS {17} - FROM {7}""").format(tempoStackedLengthTab, - ID_FIELD_STACKED_BLOCK, - GEOM_FIELD, - HEIGHT_FIELD, - DISPLACEMENT_LENGTH_FIELD, - CAVITY_LENGTH_FIELD, - WAKE_LENGTH_FIELD, - obstaclePropertiesTable, - EFFECTIVE_LENGTH_FIELD, - EFFECTIVE_WIDTH_FIELD, - DISPLACEMENT_LENGTH_VORTEX_FIELD, - ROOFTOP_PERP_HEIGHT, - ROOFTOP_PERP_LENGTH, - ROOFTOP_WIND_FACTOR, - ID_FIELD_BLOCK, - BASE_HEIGHT_FIELD, - STACKED_BLOCK_X_MED, - STACKED_BLOCK_WIDTH) # nosec B608 + FROM {7}""").format( + tempoStackedLengthTab, + ID_FIELD_STACKED_BLOCK, + GEOM_FIELD, + HEIGHT_FIELD, + DISPLACEMENT_LENGTH_FIELD, + CAVITY_LENGTH_FIELD, + WAKE_LENGTH_FIELD, + obstaclePropertiesTable, + EFFECTIVE_LENGTH_FIELD, + EFFECTIVE_WIDTH_FIELD, + DISPLACEMENT_LENGTH_VORTEX_FIELD, + ROOFTOP_PERP_HEIGHT, + ROOFTOP_PERP_LENGTH, + ROOFTOP_WIND_FACTOR, + ID_FIELD_BLOCK, + BASE_HEIGHT_FIELD, + STACKED_BLOCK_X_MED, + STACKED_BLOCK_WIDTH, + ) # nosec B608 cursor.execute(query) - + # Calculates the table containing the points corresponding to all polygons, # the polygon id and the extremum of the polygon we are looking for - cursor.execute(safe(""" + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0} AS SELECT {1}, CAST(ST_X({2}) AS INTEGER) AS X, CAST(ST_Y({2}) AS INTEGER) AS Y, X_MAX, X_MIN, Y_MIN @@ -194,49 +197,71 @@ def zoneProperties(cursor, obstaclePropertiesTable, prefix = PREFIX_NAME): {1}, ST_TOMULTIPOINT({2}) AS {2} FROM {3})'); - """).format( pointsStackedBlocks , ID_FIELD_STACKED_BLOCK, - GEOM_FIELD , tempoStackedLengthTab)) # nosec B608 - + """).format( + pointsStackedBlocks, + ID_FIELD_STACKED_BLOCK, + GEOM_FIELD, + tempoStackedLengthTab, + ) + ) # nosec B608 + # Calculates points table corresponding to xmin, xmax and ymin - xMinPointTable = DataUtil.getExtremumPoint(pointsTable = pointsStackedBlocks, - axis = "X", - extremum = "MIN", - secondAxisExtremum = "MIN", - cursor = cursor, - prefix_name = prefix) - xMaxPointTable = DataUtil.getExtremumPoint(pointsTable = pointsStackedBlocks, - axis = "X", - extremum = "MAX", - secondAxisExtremum = "MIN", - cursor = cursor, - prefix_name = prefix) - yMinPointTable = DataUtil.getExtremumPoint(pointsTable = pointsStackedBlocks, - axis = "Y", - extremum = "MIN", - secondAxisExtremum = "AVG", - cursor = cursor, - prefix_name = prefix) - + xMinPointTable = DataUtil.getExtremumPoint( + pointsTable=pointsStackedBlocks, + axis="X", + extremum="MIN", + secondAxisExtremum="MIN", + cursor=cursor, + prefix_name=prefix, + ) + xMaxPointTable = DataUtil.getExtremumPoint( + pointsTable=pointsStackedBlocks, + axis="X", + extremum="MAX", + secondAxisExtremum="MIN", + cursor=cursor, + prefix_name=prefix, + ) + yMinPointTable = DataUtil.getExtremumPoint( + pointsTable=pointsStackedBlocks, + axis="Y", + extremum="MIN", + secondAxisExtremum="AVG", + cursor=cursor, + prefix_name=prefix, + ) + # Join x extremum into a single table - cursor.execute(safe(""" + cursor.execute( + safe(""" {0}{1} DROP TABLE IF EXISTS {2}; CREATE TABLE {2} AS SELECT a.{3}, a.{4} AS {4}_MIN, b.{4} AS {4}_MAX FROM {5} AS a LEFT JOIN {6} AS b ON a.{3} = b.{3}; - """).format( DataUtil.createIndex(tableName=xMinPointTable, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(tableName=xMaxPointTable, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - stackedBlocksXExt , ID_FIELD_STACKED_BLOCK, - GEOM_FIELD , xMinPointTable, - xMaxPointTable)) # nosec B608 - + """).format( + DataUtil.createIndex( + tableName=xMinPointTable, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=xMaxPointTable, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + stackedBlocksXExt, + ID_FIELD_STACKED_BLOCK, + GEOM_FIELD, + xMinPointTable, + xMaxPointTable, + ) + ) # nosec B608 + # Calculates main azimuth of left-side and right-side downstream facades - cursor.execute(safe(""" + cursor.execute( + safe(""" {0}{1} DROP TABLE IF EXISTS {2}; CREATE TABLE {2} @@ -245,18 +270,29 @@ def zoneProperties(cursor, obstaclePropertiesTable, prefix = PREFIX_NAME): ST_AZIMUTH(a.{4}, b.{4}_MAX)-PI()/2 AS THETA_RIGHT FROM {6} AS a LEFT JOIN {7} AS b ON a.{3} = b.{3}; - """).format( DataUtil.createIndex(tableName=stackedBlocksXExt, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(tableName=yMinPointTable, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - stackedBlockAzimuths , ID_FIELD_STACKED_BLOCK, - GEOM_FIELD , STACKED_BLOCK_UPSTREAMEST_X, - yMinPointTable , stackedBlocksXExt)) # nosec B608 - + """).format( + DataUtil.createIndex( + tableName=stackedBlocksXExt, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=yMinPointTable, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + stackedBlockAzimuths, + ID_FIELD_STACKED_BLOCK, + GEOM_FIELD, + STACKED_BLOCK_UPSTREAMEST_X, + yMinPointTable, + stackedBlocksXExt, + ) + ) # nosec B608 + # Join calculated indicators to previous ones - cursor.execute(safe(""" + cursor.execute( + safe(""" {0}{1} DROP TABLE IF EXISTS {2}; CREATE TABLE {2} @@ -264,50 +300,75 @@ def zoneProperties(cursor, obstaclePropertiesTable, prefix = PREFIX_NAME): COS(b.THETA_RIGHT) AS {6}, SIN(b.THETA_RIGHT) AS {7} FROM {8} AS a LEFT JOIN {9} AS b ON a.{10} = b.{10}; - """).format( DataUtil.createIndex(tableName=xMinPointTable, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(tableName=xMaxPointTable, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - zoneLengthTable , STACKED_BLOCK_UPSTREAMEST_X, - COS_BLOCK_LEFT_AZIMUTH , SIN_BLOCK_LEFT_AZIMUTH, - COS_BLOCK_RIGHT_AZIMUTH , SIN_BLOCK_RIGHT_AZIMUTH, - tempoStackedLengthTab , stackedBlockAzimuths, - ID_FIELD_STACKED_BLOCK , ID_FIELD_BLOCK)) # nosec B608 - + """).format( + DataUtil.createIndex( + tableName=xMinPointTable, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=xMaxPointTable, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + zoneLengthTable, + STACKED_BLOCK_UPSTREAMEST_X, + COS_BLOCK_LEFT_AZIMUTH, + SIN_BLOCK_LEFT_AZIMUTH, + COS_BLOCK_RIGHT_AZIMUTH, + SIN_BLOCK_RIGHT_AZIMUTH, + tempoStackedLengthTab, + stackedBlockAzimuths, + ID_FIELD_STACKED_BLOCK, + ID_FIELD_BLOCK, + ) + ) # nosec B608 + if not DEBUG: # Drop intermediate tables - cursor.execute(safe(""" + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0} - """).format(",".join([tempoStackedLengthTab , pointsStackedBlocks, - stackedBlocksXExt , stackedBlockAzimuths]))) # nosec B608 - + """).format( + ",".join( + [ + tempoStackedLengthTab, + pointsStackedBlocks, + stackedBlocksXExt, + stackedBlockAzimuths, + ] + ) + ) + ) # nosec B608 + return zoneLengthTable -def studyAreaProperties(cursor, upwindTable, stackedBlockTable, vegetationTable): - """ Calculates roughness height (z0) and displacement length (d) of the study area + +def studyAreaProperties( + cursor, upwindTable, stackedBlockTable, vegetationTable +): + """Calculates roughness height (z0) and displacement length (d) of the study area for a wind coming from North (thus you first need to rotate your - obstacles to make them facing north if you + obstacles to make them facing north if you want to study a different wind direction). - The calculation method is based on Equations 17a to 18c from Hanna and + The calculation method is based on Equations 17a to 18c from Hanna and Britter (2002). For building, each facade facing the wind is considered while the calculation is simplified for vegetation : the frontal area is - simply calculated as the cross wind width of each vegetation patch + simply calculated as the cross wind width of each vegetation patch multiplied by its crown vegetation height. - + WARNING: Hanna and Britter (2002) say that: "It is suggested that an upper limit to H, should be 20 m and an upper limit to z, is therefore about 3 m. Conse- quently these methods should not be used for skyscrapers in a large city center or for the Rocky Mountains" - + References: Hanna, SR, et RE Britter. « Wind flow and vapor cloud dispersion at industrial sites. Am. Inst ». Chem Eng, New York, 2002. - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries @@ -318,9 +379,9 @@ def studyAreaProperties(cursor, upwindTable, stackedBlockTable, vegetationTable) Name of the table containing the stacked blocks vegetationTable: String Name of the table containing the vegetation patches - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ z0: float Value of the study area roughness height @@ -331,28 +392,35 @@ def studyAreaProperties(cursor, upwindTable, stackedBlockTable, vegetationTable) lambda_f: float Value of the study area frontal density""" print("Calculates study area properties") - + # Calculate the area of the study area - cursor.execute(safe(""" + cursor.execute( + safe(""" SELECT ST_AREA(ST_BUFFER(ST_EXTENT({0}), 15)) FROM (SELECT {0} FROM {1} UNION ALL SELECT {0} FROM {2}) AS STUDY_AREA_AREA_TAB - """).format( GEOM_FIELD, - stackedBlockTable, - vegetationTable)) # nosec B608 + """).format(GEOM_FIELD, stackedBlockTable, vegetationTable) + ) # nosec B608 area = cursor.fetchall()[0][0] - - # Calculates the obstacle (stacked blocks and vegetation) + + # Calculates the obstacle (stacked blocks and vegetation) # geometric mean height weighted by the area (H_r) - cursor.execute(safe(""" + cursor.execute( + safe(""" {0}; - """).format(DataUtil.createIndex( tableName=stackedBlockTable, - fieldName=ID_FIELD_BLOCK, - isSpatial=False))) # nosec B608 - cursor.execute(safe(""" + """).format( + DataUtil.createIndex( + tableName=stackedBlockTable, + fieldName=ID_FIELD_BLOCK, + isSpatial=False, + ) + ) + ) # nosec B608 + cursor.execute( + safe(""" SELECT EXP(1.0 / SUM(OBSTACLE_HEIGHT_TAB.AREA) * SUM(OBSTACLE_HEIGHT_TAB.AREA * LOG(OBSTACLE_HEIGHT_TAB.HEIGHT))) AS H_r, MAX(OBSTACLE_HEIGHT_TAB.HEIGHT) AS H_max @@ -364,17 +432,21 @@ def studyAreaProperties(cursor, upwindTable, stackedBlockTable, vegetationTable) SELECT {2} AS HEIGHT, ST_AREA({5}) AS AREA FROM {3}) AS OBSTACLE_HEIGHT_TAB; - """).format(HEIGHT_FIELD, - stackedBlockTable, - VEGETATION_CROWN_TOP_HEIGHT, - vegetationTable, - ID_FIELD_BLOCK, - GEOM_FIELD)) # nosec B608 + """).format( + HEIGHT_FIELD, + stackedBlockTable, + VEGETATION_CROWN_TOP_HEIGHT, + vegetationTable, + ID_FIELD_BLOCK, + GEOM_FIELD, + ) + ) # nosec B608 H_r, H_max = cursor.fetchall()[0] - - # Calculates the obstacle (stacked blocks and vegetation) + + # Calculates the obstacle (stacked blocks and vegetation) # and frontal density (lambda_f) - cursor.execute(safe(""" + cursor.execute( + safe(""" SELECT SUM(FRONTAL_AREA_TAB.CROSS_WIND_LENGTH* FRONTAL_AREA_TAB.CROSS_WIND_HEIGHT)/{7} AS LAMBDA_f @@ -385,16 +457,19 @@ def studyAreaProperties(cursor, upwindTable, stackedBlockTable, vegetationTable) SELECT ST_XMAX({3})- ST_XMIN({3}) AS CROSS_WIND_LENGTH, {1}-{6} AS CROSS_WIND_HEIGHT FROM {2}) AS FRONTAL_AREA_TAB - """).format(HEIGHT_FIELD, - VEGETATION_CROWN_TOP_HEIGHT, - vegetationTable, - GEOM_FIELD, - BASE_HEIGHT_FIELD, - upwindTable, - VEGETATION_CROWN_BASE_HEIGHT, - area)) # nosec B608 + """).format( + HEIGHT_FIELD, + VEGETATION_CROWN_TOP_HEIGHT, + vegetationTable, + GEOM_FIELD, + BASE_HEIGHT_FIELD, + upwindTable, + VEGETATION_CROWN_BASE_HEIGHT, + area, + ) + ) # nosec B608 lambda_f = cursor.fetchall()[0][0] - + # Calculates z0 and d according to Hanna and Britter (2002) Equations 16-17 z0 = 0 d = 0 @@ -409,41 +484,46 @@ def studyAreaProperties(cursor, upwindTable, stackedBlockTable, vegetationTable) lambda_f = 1 z0 = 0.15 * H_r d = (0.7 + 0.35 * (lambda_f - 0.15)) * H_r - + return z0, d, H_r, H_max, lambda_f + def maxObstacleHeight(cursor, stackedBlockTable, vegetationTable): - """ Calculates the maximum height of the obstacles within the study area. + """Calculates the maximum height of the obstacles within the study area. - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ - cursor: conn.cursor - A cursor object, used to perform spatial SQL queries - stackedBlockTable: String - Name of the table containing the stacked blocks - vegetationTable: String - Name of the table containing the vegetation patches - - Returns - _ _ _ _ _ _ _ _ _ _ + cursor: conn.cursor + A cursor object, used to perform spatial SQL queries + stackedBlockTable: String + Name of the table containing the stacked blocks + vegetationTable: String + Name of the table containing the vegetation patches + + Returns + _ _ _ _ _ _ _ _ _ _ - Hmax: float - Value of the maximum obstacle height within the study area""" + Hmax: float + Value of the maximum obstacle height within the study area""" print("Calculates maximum obstacle height within the study area") - - # Calculates the obstacle (stacked blocks and vegetation) + + # Calculates the obstacle (stacked blocks and vegetation) # maximum height (Hmax) - cursor.execute(safe(""" + cursor.execute( + safe(""" SELECT MAX(HEIGHT) AS Hmax, FROM (SELECT MAX({0}) AS HEIGHT FROM {1} UNION ALL SELECT MAX({2}) AS HEIGHT - FROM {3}) AS OBSTACLE_HEIGHT_TAB;""").format(HEIGHT_FIELD, - stackedBlockTable, - VEGETATION_CROWN_TOP_HEIGHT, - vegetationTable)) # nosec B608 + FROM {3}) AS OBSTACLE_HEIGHT_TAB;""").format( + HEIGHT_FIELD, + stackedBlockTable, + VEGETATION_CROWN_TOP_HEIGHT, + vegetationTable, + ) + ) # nosec B608 H_max = cursor.fetchall()[0][0] - - return H_max \ No newline at end of file + + return H_max diff --git a/functions/URock/DataUtil.py b/functions/URock/DataUtil.py index 22e6cab..7f88333 100644 --- a/functions/URock/DataUtil.py +++ b/functions/URock/DataUtil.py @@ -13,15 +13,16 @@ import re -def decompressZip(dirPath, inputFileName, outputFileBaseName=None, - deleteZip = False): +def decompressZip( + dirPath, inputFileName, outputFileBaseName=None, deleteZip=False +): """ Decompress zip file. Parameters - _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ dirPath: String - Directory path where is located the zip file + Directory path where is located the zip file inputFileName: String Name of the file to unzip (with .zip at the end) outputFileBaseName: String @@ -34,21 +35,21 @@ def decompressZip(dirPath, inputFileName, outputFileBaseName=None, None """ print("Start decompressing zip file") - - with open(os.path.join(dirPath,inputFileName), "rb") as zipsrc: + + with open(os.path.join(dirPath, inputFileName), "rb") as zipsrc: zfile = zipfile.ZipFile(zipsrc) for member in zfile.infolist(): - print(member.filename+" is being decompressed" ) + print(member.filename + " is being decompressed") if outputFileBaseName is None: - target_path=os.path.join(dirPath,member.filename) + target_path = os.path.join(dirPath, member.filename) else: # Initialize output file path target_path = os.path.join(dirPath, outputFileBaseName) extension = "." + member.filename.split(".")[-1] - target_path+=extension - + target_path += extension + # Create a folder if needed - if target_path.endswith('/'): # folder entry, create + if target_path.endswith("/"): # folder entry, create try: os.makedirs(target_path) except (OSError, IOError) as err: @@ -56,105 +57,112 @@ def decompressZip(dirPath, inputFileName, outputFileBaseName=None, if err.errno != errno.EEXIST: raise continue - with open(target_path, 'wb') as outfile, zfile.open(member) as infile: + with open(target_path, "wb") as outfile, zfile.open( + member + ) as infile: shutil.copyfileobj(infile, outfile) - + return None -def degToRad(angleDeg, origin = 0, direction = "CLOCKWISE"): + +def degToRad(angleDeg, origin=0, direction="CLOCKWISE"): """Convert angle arrays from degrees to radian. - + Parameters - _ _ _ _ _ _ _ _ _ _ - angleDeg : float - Angle in degrees - origin : float, default 0 - Origin of the input degree coordinates (given in a reference North clockwise coordinate system) - direction : {"CLOCKWISE", "COUNTER-CLOCKWISE"}, default "CLOCKWISE" - Direction where go the input coordinate - + _ _ _ _ _ _ _ _ _ _ + angleDeg : float + Angle in degrees + origin : float, default 0 + Origin of the input degree coordinates (given in a reference North clockwise coordinate system) + direction : {"CLOCKWISE", "COUNTER-CLOCKWISE"}, default "CLOCKWISE" + Direction where go the input coordinate + Returns - _ _ _ _ _ _ _ _ _ _ - angle in radian (trigonometric reference). + _ _ _ _ _ _ _ _ _ _ + angle in radian (trigonometric reference). """ if direction == "CLOCKWISE": d = 1 if direction == "COUNTER-CLOCKWISE": d = -1 - - return (angleDeg+d*origin)*np.pi/180 -def postfix(tableName, suffix = None, separator = "_"): - """ Add a suffix to an input table name - + return (angleDeg + d * origin) * np.pi / 180 + + +def postfix(tableName, suffix=None, separator="_"): + """Add a suffix to an input table name + Parameters - _ _ _ _ _ _ _ _ _ _ - tableName : String - Name of the input table + _ _ _ _ _ _ _ _ _ _ + tableName : String + Name of the input table suffix : String, default None (then current datetime is used as string) Suffix to add to the table name separator : String, default "_" Character to separate tableName from suffix - - + + Returns - _ _ _ _ _ _ _ _ _ _ - The input table name with the suffix""" + _ _ _ _ _ _ _ _ _ _ + The input table name with the suffix""" if suffix is None: suffix = datetime.now().strftime("%Y%m%d%H%M%S") - - return tableName+separator+suffix -def prefix(tableName, prefix = PREFIX_NAME, separator = "_"): - """ Add a suffix to an input table name - + return tableName + separator + suffix + + +def prefix(tableName, prefix=PREFIX_NAME, separator="_"): + """Add a suffix to an input table name + Parameters - _ _ _ _ _ _ _ _ _ _ - tableName : String - Name of the input table + _ _ _ _ _ _ _ _ _ _ + tableName : String + Name of the input table prefix : String Prefix to add to the table name separator : String, default "_" - Character to separate prefix from tableName - + Character to separate prefix from tableName + Returns - _ _ _ _ _ _ _ _ _ _ - The input table name with the prefix""" + _ _ _ _ _ _ _ _ _ _ + The input table name with the prefix""" if prefix == "": return tableName else: - return prefix+separator+tableName + return prefix + separator + tableName + def getColumns(cursor, tableName): - """ Get the column name of a table into a list - + """Get the column name of a table into a list + Parameters - _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries - tableName : String - Name of the input table - + tableName : String + Name of the input table + Returns - _ _ _ _ _ _ _ _ _ _ - columnNames: list + _ _ _ _ _ _ _ _ _ _ + columnNames: list A list of the table column names""" cursor.execute(safe("""SELECT * FROM {0}""").format(tableName)) columnNames = [info[0] for info in cursor.description] - + return columnNames + def readFunction(extension): - """ Return the name of the right H2GIS function to use depending of the file extension - + """Return the name of the right H2GIS function to use depending of the file extension + Parameters - _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ extension: String Extension of the vector file (shp or geojson) - + Returns - _ _ _ _ _ _ _ _ _ _ - h2gisFunctionName: String + _ _ _ _ _ _ _ _ _ _ + h2gisFunctionName: String Return the name of the H2GIS function to use""" if extension.lower() == "shp": return "SHPREAD" @@ -164,14 +172,15 @@ def readFunction(extension): return "CSVREAD" elif extension.lower() == "fgb": return "FGBREAD" - + + def createIndex(tableName, fieldName, isSpatial): - """ Return the SQL query needed to create an index on a given field of a + """Return the SQL query needed to create an index on a given field of a given table. The index should be indicated as spatial if the field is a geometry field. - + Parameters - _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ tableName: String Name of the table fieldName: String @@ -179,10 +188,10 @@ def createIndex(tableName, fieldName, isSpatial): isSpatial: boolean Whether or not the index is a spatial index (should be True if the field is a geometry field) - + Returns - _ _ _ _ _ _ _ _ _ _ - query: String + _ _ _ _ _ _ _ _ _ _ + query: String Return the SQL query needed to create the index""" spatialKeyWord = "" if isSpatial: @@ -195,75 +204,88 @@ def createIndex(tableName, fieldName, isSpatial): ON {tableName}({fieldName});""" return query -def radToDeg(data, origin = 90, direction = "CLOCKWISE"): + +def radToDeg(data, origin=90, direction="CLOCKWISE"): """Convert angle arrays from radian to degree. - + Parameters - _ _ _ _ _ _ _ _ _ _ - data : pd.Series() - Array containing the angle values to convert from radian to degree. - origin : float - Origin of the output coordinate (given in a reference trigonometric coordinate) - direction : {"CLOCKWISE", "COUNTER-CLOCKWISE"}, default "CLOCKWISE" - Direction where go the output coordinate - + _ _ _ _ _ _ _ _ _ _ + data : pd.Series() + Array containing the angle values to convert from radian to degree. + origin : float + Origin of the output coordinate (given in a reference trigonometric coordinate) + direction : {"CLOCKWISE", "COUNTER-CLOCKWISE"}, default "CLOCKWISE" + Direction where go the output coordinate + Returns - _ _ _ _ _ _ _ _ _ _ - Array containing the data in degree coordinate. + _ _ _ _ _ _ _ _ _ _ + Array containing the data in degree coordinate. """ if direction == "CLOCKWISE": degree = (360 - data * 180 / np.pi) + origin if direction == "COUNTER-CLOCKWISE": degree = (data * 180 / np.pi) - origin - - degree[degree>360] = degree[degree>360] - 360 - degree[degree<0] = degree[degree<0] + 360 - + + degree[degree > 360] = degree[degree > 360] - 360 + degree[degree < 0] = degree[degree < 0] + 360 + return degree + def windDirectionFromXY(windSpeedEast, windSpeedNorth): """ Calculates wind direction from wind speeds in carthesian coordinates. - + Parameters - _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ windSpeedEast: pd.Series Wind speed along a West->East axis (m/s) windSpeedNorth: pd.Series Wind speed along a South->North axis (m/s) - + Returns ------- pd.Series containing the wind direction from East counterclockwise. """ # Calculate the angle in Radian in a [-pi/2, pi/2] radAngle = np.zeros(windSpeedEast.shape) - radAngle[windSpeedEast==0] = 0 + radAngle[windSpeedEast == 0] = 0 if type(windSpeedEast) == type(pd.Series()): - radAngle[windSpeedEast!=0] = np.arctan(windSpeedNorth[windSpeedEast!=0]\ - .divide(windSpeedEast[windSpeedEast!=0])) + radAngle[windSpeedEast != 0] = np.arctan( + windSpeedNorth[windSpeedEast != 0].divide( + windSpeedEast[windSpeedEast != 0] + ) + ) else: - radAngle[windSpeedEast!=0] = np.arctan(windSpeedNorth[windSpeedEast!=0] - /windSpeedEast[windSpeedEast!=0]) - + radAngle[windSpeedEast != 0] = np.arctan( + windSpeedNorth[windSpeedEast != 0] + / windSpeedEast[windSpeedEast != 0] + ) + # Add or subtract pi.2 for left side trigonometric circle vectors - radAngle[(windSpeedEast<=0)&(windSpeedNorth>0)] = \ - radAngle[(windSpeedEast<=0)&(windSpeedNorth>0)] + np.pi - radAngle[(windSpeedEast<0)&(windSpeedNorth<=0)] = \ - radAngle[(windSpeedEast<0)&(windSpeedNorth<=0)] + np.pi - radAngle[(windSpeedEast>=0)&(windSpeedNorth<0)] = \ - radAngle[(windSpeedEast>=0)&(windSpeedNorth<0)] + 2*np.pi - + radAngle[(windSpeedEast <= 0) & (windSpeedNorth > 0)] = ( + radAngle[(windSpeedEast <= 0) & (windSpeedNorth > 0)] + np.pi + ) + radAngle[(windSpeedEast < 0) & (windSpeedNorth <= 0)] = ( + radAngle[(windSpeedEast < 0) & (windSpeedNorth <= 0)] + np.pi + ) + radAngle[(windSpeedEast >= 0) & (windSpeedNorth < 0)] = ( + radAngle[(windSpeedEast >= 0) & (windSpeedNorth < 0)] + 2 * np.pi + ) + return radAngle -def getExtremumPoint(pointsTable, axis, extremum, secondAxisExtremum, cursor, prefix_name): - """ Identify the point geometry being an extremum ("MIN" or "MAX"") of a polygon + +def getExtremumPoint( + pointsTable, axis, extremum, secondAxisExtremum, cursor, prefix_name +): + """Identify the point geometry being an extremum ("MIN" or "MAX"") of a polygon along a given axis ("X" or "Y"). If two points are at the same "X" (or "Y"), - keep only lowest value or highest ("MIN" or "MAX") along the second + keep only lowest value or highest ("MIN" or "MAX") along the second axis "Y" (or "X"). - + Parameters - _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ pointsTable: String Name of the table containing the points corresponding to all polygons, the polygon id and the extremum of the polygon we are looking for @@ -288,36 +310,38 @@ def getExtremumPoint(pointsTable, axis, extremum, secondAxisExtremum, cursor, pr A cursor object, used to perform spatial SQL queries prefix: String, default PREFIX_NAME Prefix to add to the output table name - + Returns - _ _ _ _ _ _ _ _ _ _ - extremumPointTable: String - Return the table containing the expected extremum point for each polygon""" + _ _ _ _ _ _ _ _ _ _ + extremumPointTable: String + Return the table containing the expected extremum point for each polygon + """ # Output base name - outputBaseName = safe("{0}_{1}_{2}_POINTS").format(pointsTable, - axis, - extremum) - + outputBaseName = safe("{0}_{1}_{2}_POINTS").format( + pointsTable, axis, extremum + ) + # Name of the output table - extremumPointTable = prefix(outputBaseName, prefix = prefix_name) + extremumPointTable = prefix(outputBaseName, prefix=prefix_name) # Extremum field name extremumField = axis + "_" + extremum - + # Set the secondary axis and the point creation query depending on 1st axis if axis == "X": secondaryAxis = "Y" - pointCreationQuery = safe("ST_POINT({0}, {1}({2}))").format(axis, - secondAxisExtremum, - secondaryAxis) + pointCreationQuery = safe("ST_POINT({0}, {1}({2}))").format( + axis, secondAxisExtremum, secondaryAxis + ) else: secondaryAxis = "X" - pointCreationQuery = safe("ST_POINT({1}({2}), {0})").format(axis, - secondAxisExtremum, - secondaryAxis) - + pointCreationQuery = safe("ST_POINT({1}({2}), {0})").format( + axis, secondAxisExtremum, secondaryAxis + ) + # Identify the extremum point - cursor.execute(safe(""" + cursor.execute( + safe(""" {0}{1}{2} DROP TABLE IF EXISTS {3}; CREATE TABLE {3}({4} INTEGER, {5} GEOMETRY) @@ -325,27 +349,36 @@ def getExtremumPoint(pointsTable, axis, extremum, secondAxisExtremum, cursor, pr FROM {7} WHERE {8} = {9} GROUP BY {4}; - """).format( createIndex(tableName=pointsTable, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - createIndex(tableName=pointsTable, - fieldName=axis, - isSpatial=False), - createIndex(tableName=pointsTable, - fieldName=extremumField, - isSpatial=False), - extremumPointTable, - ID_FIELD_STACKED_BLOCK , GEOM_FIELD, - pointCreationQuery , pointsTable, - axis , extremumField)) + """).format( + createIndex( + tableName=pointsTable, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + createIndex( + tableName=pointsTable, fieldName=axis, isSpatial=False + ), + createIndex( + tableName=pointsTable, fieldName=extremumField, isSpatial=False + ), + extremumPointTable, + ID_FIELD_STACKED_BLOCK, + GEOM_FIELD, + pointCreationQuery, + pointsTable, + axis, + extremumField, + ) + ) return extremumPointTable + ####### SHOULD BE DELETED SINCE ALREADY IN UMEP !!! def locate_py(): # get Python version - str_ver_qgis = sys.version.split(' ')[0] - + str_ver_qgis = sys.version.split(" ")[0] + try: # non-Linux path_py = os.environ["PYTHONHOME"] @@ -359,7 +392,14 @@ def locate_py(): # pre-defined paths for python executable dict_pybin = { "Darwin": path_py / "bin" / "python3", - "Windows": path_py / ("../../bin/pythonw.exe" if version.parse(str_ver_qgis) >= version.parse("3.9.1") else "pythonw.exe"), + "Windows": ( + path_py + / ( + "../../bin/pythonw.exe" + if version.parse(str_ver_qgis) >= version.parse("3.9.1") + else "pythonw.exe" + ) + ), "Linux": path_py, } @@ -369,21 +409,31 @@ def locate_py(): if path_pybin.exists(): return path_pybin else: - raise RuntimeError("UMEP cannot locate the Python interpreter used by QGIS!") - + raise RuntimeError( + "UMEP cannot locate the Python interpreter used by QGIS!" + ) + -def validate_sql_inputs(idLines=None, idPolygons=None, srid_lines=None, srid_polygons=None, urock_srid=None, lines_file=None, polygons_file=None): +def validate_sql_inputs( + idLines=None, + idPolygons=None, + srid_lines=None, + srid_polygons=None, + urock_srid=None, + lines_file=None, + polygons_file=None, +): """ Validate inputs to prevent SQL injection. """ # Validate field names: must be valid SQL identifiers (alphanumeric + underscore, start with letter or underscore) - identifier_pattern = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$') - + identifier_pattern = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$") + if idLines and not identifier_pattern.match(idLines): raise ValueError(f"Invalid idLines field name: {idLines}") if idPolygons and not identifier_pattern.match(idPolygons): raise ValueError(f"Invalid idPolygons field name: {idPolygons}") - + # Validate SRIDs: must be integers def validate_srid(srid, name): if srid is not None: @@ -391,22 +441,25 @@ def validate_srid(srid, name): int(srid) except (ValueError, TypeError): raise ValueError(f"Invalid {name}: {srid} (must be integer)") - + validate_srid(srid_lines, "srid_lines") validate_srid(srid_polygons, "srid_polygons") validate_srid(urock_srid, "urock_srid") - + # Validate file paths: ensure no single quotes (basic check) def validate_file_path(file_path, name): if file_path and "'" in file_path: - raise ValueError(f"Invalid {name}: {file_path} (contains single quotes)") - + raise ValueError( + f"Invalid {name}: {file_path} (contains single quotes)" + ) + validate_file_path(lines_file, "lines_file") validate_file_path(polygons_file, "polygons_file") - -def safe(s): + + +def safe(s): """ Dummy function to mark a string as safe for SQL queries. This is used to bypass the SQL injection check for specific queries that are already validated. """ - return s \ No newline at end of file + return s diff --git a/functions/URock/GlobalVariables.py b/functions/URock/GlobalVariables.py index 88efbcd..44078dd 100644 --- a/functions/URock/GlobalVariables.py +++ b/functions/URock/GlobalVariables.py @@ -5,6 +5,7 @@ @author: Jérémy Bernard, University of Gothenburg """ + import pandas as pd import tempfile import os @@ -30,8 +31,8 @@ # Temporary directory where are saved database and specific files exchanged between # the H2Database and Python TEMPO_DIRECTORY = tempfile.gettempdir() -INPUT_DIRECTORY = os.path.join("./Resources","Inputs") -OUTPUT_DIRECTORY = os.path.join("./Resources","Outputs") +INPUT_DIRECTORY = os.path.join("./Resources", "Inputs") +OUTPUT_DIRECTORY = os.path.join("./Resources", "Outputs") BUILDING_TABLE_NAME = "BUILDINGS" VEGETATION_TABLE_NAME = "VEGETATION" CAD_TRIANGLE_NAME = "ALL_TRIANGLES" @@ -63,7 +64,7 @@ # Informations to set the DB used for geographical calculations INSTANCE_NAME = "myDbH2" -INSTANCE_ID ="sa" +INSTANCE_ID = "sa" INSTANCE_PASS = "sa" NEW_DB = True @@ -75,13 +76,13 @@ DEBUG = False ONLY_INITIALIZATION = False SAVE_ROCKLE_ZONES = False -MAX_ITERATIONS = 500 # Based on QUIC-URB default values (2021) -THRESHOLD_ITERATIONS = 1e-4 # Based on QUIC-URB default values (2021) +MAX_ITERATIONS = 500 # Based on QUIC-URB default values (2021) +THRESHOLD_ITERATIONS = 1e-4 # Based on QUIC-URB default values (2021) # Note that the number of points of an ellipse is only used to identify whether # the upper or lower part of an ellipse should be used (fro displacement zones), # the number of points used to create an ellipse can not be chosen yet -# Need to create this variable in H2GIS +# Need to create this variable in H2GIS # https://github.com/locationtech/jts/blob/9d4097312d68cb8f9ae591bec69ce3b403e41e98/modules/core/src/main/java/org/locationtech/jts/util/GeometricShapeFactory.java#L101 NPOINTS_ELLIPSE = 100 MESH_SIZE = 2 @@ -99,8 +100,11 @@ # the perpendicular to the wind direction (Bagal et al., 2004) CORNER_THRESHOLD_ANGLE_MIN = 30 CORNER_THRESHOLD_ANGLE_MAX = 70 -CORNER_THRESHOLD_ANGLE = [CORNER_THRESHOLD_ANGLE_MIN, CORNER_THRESHOLD_ANGLE_MAX] -ELLIPSOID_MIN_LENGTH = float(MESH_SIZE)/4 +CORNER_THRESHOLD_ANGLE = [ + CORNER_THRESHOLD_ANGLE_MIN, + CORNER_THRESHOLD_ANGLE_MAX, +] +ELLIPSOID_MIN_LENGTH = float(MESH_SIZE) / 4 GEOMETRY_MERGE_TOLERANCE = 0.05 SNAPPING_TOLERANCE = 0.3 GEOMETRY_SIMPLIFICATION_DISTANCE = 0.25 @@ -201,10 +205,10 @@ # Default vegetation crown base height (in % of crown top height) DEFAULT_VEG_CROWN_BASE_HEIGHT_FRAC = 0.25 -# Defines priorities (column "priority") when the zone comes from a same +# Defines priorities (column "priority") when the zone comes from a same # obstacle of same height. Also contains a column "ref_height" to # know by which wind speed height should be multiplied a weigthing factor -# (1: "Associated building height", +# (1: "Associated building height", # 2: "Reference wind speed measurement height Z_REF", # 3: "Point height") REF_HEIGHT_FIELD = "REF_HEIGHT" @@ -214,21 +218,30 @@ REF_HEIGHT_DOWNSTREAM_WEIGHTING = 3 IS_UPSTREAM_UPSTREAM_WEIGHTING = 0 IS_UPSTREAM_DOWNSTREAM_WEIGHTING = 0 -UPSTREAM_PRIORITY_TABLES = pd.DataFrame({PRIORITY_FIELD: [1, 2, 3, 3, 3, 4, 5], - REF_HEIGHT_FIELD: [1, 1, 2, 2, 1, 1, 3], - IS_UPSTREAM_FIELD: [0, 0, 0, 0, 1, 1, 0]}, - index = [STREET_CANYON_NAME, - CAVITY_NAME, - ROOFTOP_PERP_NAME, - ROOFTOP_CORN_NAME, - DISPLACEMENT_VORTEX_NAME, - DISPLACEMENT_NAME, - WAKE_NAME]) -UPSTREAM_BACKWARD_PRIORITY_TABLES = pd.DataFrame({PRIORITY_FIELD: [1, 2], - REF_HEIGHT_FIELD: [1, 1], - IS_UPSTREAM_FIELD: [0, 0]}, - index = [CAVITY_BACKWARD_NAME, - WAKE_BACKWARD_NAME]) +UPSTREAM_PRIORITY_TABLES = pd.DataFrame( + { + PRIORITY_FIELD: [1, 2, 3, 3, 3, 4, 5], + REF_HEIGHT_FIELD: [1, 1, 2, 2, 1, 1, 3], + IS_UPSTREAM_FIELD: [0, 0, 0, 0, 1, 1, 0], + }, + index=[ + STREET_CANYON_NAME, + CAVITY_NAME, + ROOFTOP_PERP_NAME, + ROOFTOP_CORN_NAME, + DISPLACEMENT_VORTEX_NAME, + DISPLACEMENT_NAME, + WAKE_NAME, + ], +) +UPSTREAM_BACKWARD_PRIORITY_TABLES = pd.DataFrame( + { + PRIORITY_FIELD: [1, 2], + REF_HEIGHT_FIELD: [1, 1], + IS_UPSTREAM_FIELD: [0, 0], + }, + index=[CAVITY_BACKWARD_NAME, WAKE_BACKWARD_NAME], +) UPSTREAM_WEIGHTING_TABLES = [WAKE_NAME] UPSTREAM_BACKWARD_WEIGHTING_TABLES = [WAKE_BACKWARD_NAME] UPSTREAM_WEIGHTING_INTRA_RULES = "upstream" diff --git a/functions/URock/H2gisConnection.py b/functions/URock/H2gisConnection.py index 8374a88..dfc8d6b 100644 --- a/functions/URock/H2gisConnection.py +++ b/functions/URock/H2gisConnection.py @@ -11,19 +11,25 @@ import os import urllib3 from . import DataUtil -from .GlobalVariables import INSTANCE_NAME, INSTANCE_ID, INSTANCE_PASS,\ - JAVA_PATH_FILENAME, TEMPO_DIRECTORY, NEW_DB +from .GlobalVariables import ( + INSTANCE_NAME, + INSTANCE_ID, + INSTANCE_PASS, + JAVA_PATH_FILENAME, + TEMPO_DIRECTORY, + NEW_DB, +) import subprocess import re import pandas as pd try: - #path_pybin = DataUtil.locate_py() - #subprocess.check_call([str(path_pybin), "-m", "pip", "install", "jaydebeapi"]) + # path_pybin = DataUtil.locate_py() + # subprocess.check_call([str(path_pybin), "-m", "pip", "install", "jaydebeapi"]) import jaydebeapi except ImportError: - #print("'jaydebeapi' Python package is missing, cannot connect to H2 Driver") - #exit(1) + # print("'jaydebeapi' Python package is missing, cannot connect to H2 Driver") + # exit(1) exit("'jaydebeapi' Python package is missing") # Global variables # H2GIS_VERSION = "1.5.0" @@ -34,44 +40,56 @@ H2GIS_VERSION = "2.2.4-SNAPSHOT" H2GIS_URL = "https://nightly.link/orbisgis/h2gis/workflows/CI_snapshot/master/h2gis-standalone-bin.zip" -H2GIS_UNZIPPED_NAME = "h2gis-standalone"+os.sep+"h2gis-dist-"+H2GIS_VERSION+".jar" +H2GIS_UNZIPPED_NAME = ( + "h2gis-standalone" + os.sep + "h2gis-dist-" + H2GIS_VERSION + ".jar" +) JAVA_PATH_POSIX = [os.path.join(os.sep, "usr", "lib", "jvm")] -JAVA_PATH_NT = [os.path.join("C:", os.sep, "Program Files", "Java"), os.path.join("C:", os.sep, "Program Files", "OpenJDK"), - os.path.join("C:", os.sep, "Program Files (x86)", "Java"), os.path.join("C:", os.sep, "Program Files (x86)", "OpenJDK")] +JAVA_PATH_NT = [ + os.path.join("C:", os.sep, "Program Files", "Java"), + os.path.join("C:", os.sep, "Program Files", "OpenJDK"), + os.path.join("C:", os.sep, "Program Files (x86)", "Java"), + os.path.join("C:", os.sep, "Program Files (x86)", "OpenJDK"), +] # DB extension DB_EXTENSION = ".mv.db" DB_TRACE_EXTENSION = ".trace.db" + def downloadH2gis(dbDirectory): - """ Download the H2GIS spatial database management system (used for Röckle zone calculation) - For more information about use with Python: https://github.com/orbisgis/h2gis/wiki/4.4-Use-H2GIS-with-Python + """Download the H2GIS spatial database management system (used for Röckle zone calculation) + For more information about use with Python: https://github.com/orbisgis/h2gis/wiki/4.4-Use-H2GIS-with-Python - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ - dbDirectory: String - Directory where shoud be stored the H2GIS jar - - Returns - _ _ _ _ _ _ _ _ _ _ + dbDirectory: String + Directory where shoud be stored the H2GIS jar - None""" + Returns + _ _ _ _ _ _ _ _ _ _ + + None""" # Get the zip file name and create the local file directory zipFileName = H2GIS_URL.split("/")[-1] - localH2ZipDir = (dbDirectory+os.sep+zipFileName).encode('utf-8') - localH2JarDir = (dbDirectory+os.sep+H2GIS_UNZIPPED_NAME).encode('utf-8') - + localH2ZipDir = (dbDirectory + os.sep + zipFileName).encode("utf-8") + localH2JarDir = (dbDirectory + os.sep + H2GIS_UNZIPPED_NAME).encode( + "utf-8" + ) + # Test whether the .jar already downloaded - if(os.path.exists(localH2ZipDir) or os.path.exists(localH2JarDir)): + if os.path.exists(localH2ZipDir) or os.path.exists(localH2JarDir): print("H2GIS version %s already downloaded" % (H2GIS_VERSION)) else: - print("Downloading H2GIS version %s at %s..." % (H2GIS_URL, H2GIS_VERSION)) + print( + "Downloading H2GIS version %s at %s..." + % (H2GIS_URL, H2GIS_VERSION) + ) # Download the archive file and save it into the 'dbDirectory' http = urllib3.PoolManager() - r = http.request('GET', url = H2GIS_URL, preload_content=False) - with open(localH2ZipDir, 'wb') as out: + r = http.request("GET", url=H2GIS_URL, preload_content=False) + with open(localH2ZipDir, "wb") as out: while True: data = r.read(2**16) if not data: @@ -79,9 +97,9 @@ def downloadH2gis(dbDirectory): out.write(data) r.release_conn() print("H2GIS version %s downloaded !!" % (H2GIS_VERSION)) - + # Test whether the .jar already exists - if(os.path.exists(localH2JarDir)): + if os.path.exists(localH2JarDir): print("H2GIS version %s already unzipped" % (H2GIS_VERSION)) else: print("Unzipping H2GIS version %s..." % (H2GIS_VERSION)) @@ -91,19 +109,23 @@ def downloadH2gis(dbDirectory): DataUtil.decompressZip(dbDirectory, zipFileName) print("H2GIS version %s unzipped !!" % (H2GIS_VERSION)) - -def startH2gisInstance(dbDirectory, dbInstanceDir = TEMPO_DIRECTORY, - instanceName = INSTANCE_NAME, suffix = "", - instanceId=INSTANCE_ID, - instancePass = INSTANCE_PASS): - """ Start an H2GIS spatial database instance (used for Röckle zone calculation) + +def startH2gisInstance( + dbDirectory, + dbInstanceDir=TEMPO_DIRECTORY, + instanceName=INSTANCE_NAME, + suffix="", + instanceId=INSTANCE_ID, + instancePass=INSTANCE_PASS, +): + """Start an H2GIS spatial database instance (used for Röckle zone calculation) For more information about use with Python: https://github.com/orbisgis/h2gis/wiki/4.4-Use-H2GIS-with-Python - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ - dbDirectory: String - Directory where is stored the H2GIS jar + dbDirectory: String + Directory where is stored the H2GIS jar dbInstanceDir: String Directory where should be started the H2GIS instance instanceName: String, default INSTANCE_NAME @@ -114,68 +136,72 @@ def startH2gisInstance(dbDirectory, dbInstanceDir = TEMPO_DIRECTORY, ID used to connect to the database instancePass: String, default INSTANCE_PASS password used to connect to the database - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ cur: conn.cursor A cursor object, used to perform queries - conn: + conn: A connection object to the database localH2InstanceDir: String - File directory of the database to delete (without extension)""" + File directory of the database to delete (without extension)""" # Define where are the jar of the DB and the H2GIS instance (in absolute paths) - localH2JarDir = dbDirectory+os.sep+H2GIS_UNZIPPED_NAME - localH2InstanceDir = dbInstanceDir+os.sep+instanceName + suffix + localH2JarDir = dbDirectory + os.sep + H2GIS_UNZIPPED_NAME + localH2InstanceDir = dbInstanceDir + os.sep + instanceName + suffix + + isDbExist = os.path.exists(localH2InstanceDir + DB_EXTENSION) - isDbExist = os.path.exists(localH2InstanceDir+DB_EXTENSION) - # print the connection string we will use to connect print("Connecting to database\n ->%s" % (localH2InstanceDir)) - print (localH2JarDir) + print(localH2JarDir) # If the DB already exists and if 'newDB' is set to True, delete all the DB files if isDbExist: - os.remove(localH2InstanceDir+DB_EXTENSION) - if os.path.exists(localH2InstanceDir+DB_TRACE_EXTENSION): - os.remove(localH2InstanceDir+DB_TRACE_EXTENSION) - + os.remove(localH2InstanceDir + DB_EXTENSION) + if os.path.exists(localH2InstanceDir + DB_TRACE_EXTENSION): + os.remove(localH2InstanceDir + DB_TRACE_EXTENSION) + # get a connection, if a connect cannot be made an exception will be raised here - conn = jaydebeapi.connect( "org.h2.Driver", - "jdbc:h2:"+localH2InstanceDir+";AUTO_SERVER=TRUE;", - [instanceId, instancePass], - localH2JarDir,) + conn = jaydebeapi.connect( + "org.h2.Driver", + "jdbc:h2:" + localH2InstanceDir + ";AUTO_SERVER=TRUE;", + [instanceId, instancePass], + localH2JarDir, + ) # conn.cursor will return a cursor object, you can use this cursor to perform queries cur = conn.cursor() print("Connected!\n") - # Init spatial features - cur.execute("CREATE ALIAS IF NOT EXISTS H2GIS_SPATIAL FOR \"org.h2gis.functions.factory.H2GISFunctions.load\";") + cur.execute( + 'CREATE ALIAS IF NOT EXISTS H2GIS_SPATIAL FOR "org.h2gis.functions.factory.H2GISFunctions.load";' + ) cur.execute("CALL H2GIS_SPATIAL();") print("Spatial functions added!\n") - + return cur, conn, localH2InstanceDir + def closeAndRemoveH2gisInstance(localH2InstanceDir, conn, cur): - """ Close an H2GIS instance and remove the database file + """Close an H2GIS instance and remove the database file - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ - localH2InstanceDir: String - File directory of the database to delete (without extension) - conn: - A connection object to the database - cur: conn.cursor - A cursor object, used to perform queries - - - Returns - _ _ _ _ _ _ _ _ _ _ + localH2InstanceDir: String + File directory of the database to delete (without extension) + conn: + A connection object to the database + cur: conn.cursor + A cursor object, used to perform queries - None""" + + Returns + _ _ _ _ _ _ _ _ _ _ + + None""" # Close cursor and connection and then delete the H2GIS database file cur.close() conn.close() @@ -184,36 +210,38 @@ def closeAndRemoveH2gisInstance(localH2InstanceDir, conn, cur): if os.path.exists(localH2InstanceDir + DB_TRACE_EXTENSION): os.remove(localH2InstanceDir + DB_TRACE_EXTENSION) + def setJavaDir(javaPath): - """ If there is no JAVA variable environment set or neither already one + """If there is no JAVA variable environment set or neither already one saved in the URock repository, ask the user to enter one for local use - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ javaPath: String JAVA variable path - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ None""" os.environ.setdefault("JAVA_HOME", javaPath) - + + def getJavaDir(pluginDirectory): - """ Try to get the JAVA variable environment if already set. + """Try to get the JAVA variable environment if already set. Otherwise try to get it from the user plugin repository. Otherwise try to set from the OS type - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ plugin_directory: String Path of the plugin directory where could be saved the java path - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ javaPath: String JAVA variable path""" @@ -236,27 +264,28 @@ def getJavaDir(pluginDirectory): # javaPath = identifyJavaDir(JAVA_PATH_POSIX) # else: # javaPath = identifyJavaDir(JAVA_PATH_NT) - + return javaPath + def saveJavaDir(javaPath, pluginDirectory): - """ Save the java path into a file in the user plugin repository + """Save the java path into a file in the user plugin repository - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ - javaPath: String - JAVA variable path - - Returns - _ _ _ _ _ _ _ _ _ _ + javaPath: String + JAVA variable path - None""" + Returns + _ _ _ _ _ _ _ _ _ _ + + None""" javaPathFile = os.path.join(pluginDirectory, JAVA_PATH_FILENAME) if not os.path.exists(pluginDirectory): os.makedirs(pluginDirectory) if os.path.exists(javaPathFile): - existingJavaFilePath = open(javaPathFile, "r", encoding='utf8') + existingJavaFilePath = open(javaPathFile, "r", encoding="utf8") existingJavaPath = existingJavaFilePath.read() existingJavaFilePath.close() else: @@ -264,28 +293,29 @@ def saveJavaDir(javaPath, pluginDirectory): if (existingJavaPath != javaPath) or existingJavaPath is None: if os.path.exists(javaPathFile): os.remove(javaPathFile) - javaFilePath = open(javaPathFile, "w", encoding='utf8') + javaFilePath = open(javaPathFile, "w", encoding="utf8") javaFilePath.write(javaPath) javaFilePath.close() + def identifyJavaDir(java_path_os_list): - """ Try to get the directory where is located the last Java version in the OS... + """Try to get the directory where is located the last Java version in the OS... - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ - java_path_os_list: list of String - Possible locations of the Java directory - - Returns - _ _ _ _ _ _ _ _ _ _ + java_path_os_list: list of String + Possible locations of the Java directory - javaPath: String - JAVA variable path""" + Returns + _ _ _ _ _ _ _ _ _ _ + + javaPath: String + JAVA variable path""" JavaExists = False i = 0 # Test some common folder paths to check whether a Java installation exists and stops once found - while(not (JavaExists or i >= len(java_path_os_list))): + while not (JavaExists or i >= len(java_path_os_list)): javaBaseDir = java_path_os_list[i] JavaExists = os.path.exists(javaBaseDir) i += 1 @@ -293,70 +323,94 @@ def identifyJavaDir(java_path_os_list): listJavaVersion = os.listdir(javaBaseDir) listSplit = pd.Series() for i, v in enumerate(listJavaVersion): - tempo_split = re.split('\.|\-', v) - # Previous command supposed to split version number based on usual characters. + tempo_split = re.split("\.|\-", v) + # Previous command supposed to split version number based on usual characters. # If unusual need the following... if len(tempo_split) != 3: - pattern = re.compile(r'java|jdk|jre1') + pattern = re.compile(r"java|jdk|jre1") jvm_match = pattern.search(v).group(0) - tempo_split = re.split('{0}|u'.format(jvm_match), v) + tempo_split = re.split("{0}|u".format(jvm_match), v) tempo_split[0] = jvm_match listSplit.loc[i] = tempo_split - df_version = pd.DataFrame({"version": [listSplit[i][1] \ - for i in listSplit.index], - "startWith": [listSplit[i][0] \ - for i in listSplit.index]}) - highestVersion = df_version[(df_version.startWith == "java")\ - | (df_version.startWith == "jdk")\ - | (df_version.startWith == "jre1")\ - | (df_version.startWith == "jre")].version.astype(int).idxmax() + df_version = pd.DataFrame( + { + "version": [listSplit[i][1] for i in listSplit.index], + "startWith": [listSplit[i][0] for i in listSplit.index], + } + ) + highestVersion = ( + df_version[ + (df_version.startWith == "java") + | (df_version.startWith == "jdk") + | (df_version.startWith == "jre1") + | (df_version.startWith == "jre") + ] + .version.astype(int) + .idxmax() + ) javaPath = os.path.join(javaBaseDir, listJavaVersion[highestVersion]) else: javaPath = None - + return javaPath + def getJavaHome(os_type): - """ Try to get the Java home path of the machine if Java installed... + """Try to get the Java home path of the machine if Java installed... Inspired from https://www.baeldung.com/find-java-home - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ os_type: String Type of OS (POSIX or NT) - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ javaPath: String JAVA variable path""" # Within a subprocess calling Java, get the line corresponding to java_home if os_type == "posix": command = "java -XshowSettings:properties -version 2>&1 > /dev/null | grep 'java.home'" - proc = subprocess.Popen(['bash', '-c', command], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - stdin=subprocess.PIPE) + proc = subprocess.Popen( + ["bash", "-c", command], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + stdin=subprocess.PIPE, + ) output, err = proc.communicate() # Identify the string corresponding to the java_home in the resulting line - javaPath = os.path.abspath(str(output).split("java.home = ")[1].split("\\n")[0]) - + javaPath = os.path.abspath( + str(output).split("java.home = ")[1].split("\\n")[0] + ) + else: import winreg try: - java_key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\JavaSoft\Java Runtime Environment") + java_key = winreg.OpenKey( + winreg.HKEY_LOCAL_MACHINE, + r"SOFTWARE\JavaSoft\Java Runtime Environment", + ) except FileNotFoundError: try: - java_key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\JavaSoft\JRE") + java_key = winreg.OpenKey( + winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\JavaSoft\JRE" + ) except FileNotFoundError: try: - java_key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\JavaSoft\Java Development Kit") + java_key = winreg.OpenKey( + winreg.HKEY_LOCAL_MACHINE, + r"SOFTWARE\JavaSoft\Java Development Kit", + ) except FileNotFoundError: try: - java_key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\JavaSoft\JDK") + java_key = winreg.OpenKey( + winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\JavaSoft\JDK" + ) except: print("Java not found") exit() @@ -364,5 +418,5 @@ def getJavaHome(os_type): current_version, _ = winreg.QueryValueEx(java_key, "CurrentVersion") java_version_key = winreg.OpenKey(java_key, current_version) javaPath, _ = winreg.QueryValueEx(java_version_key, "JavaHome") - - return javaPath \ No newline at end of file + + return javaPath diff --git a/functions/URock/InitWindField.py b/functions/URock/InitWindField.py index 508d726..3d5fb1c 100644 --- a/functions/URock/InitWindField.py +++ b/functions/URock/InitWindField.py @@ -9,55 +9,131 @@ from . import DataUtil from .DataUtil import safe import pandas as pd + idx = pd.IndexSlice -from .GlobalVariables import ALONG_WIND_ZONE_EXTEND, CROSS_WIND_ZONE_EXTEND,\ - MESH_SIZE, PREFIX_NAME, GEOM_FIELD, ID_POINT, ID_POINT_X, ID_POINT_Y,\ - CAVITY_NAME, WAKE_NAME, DISPLACEMENT_NAME, DISPLACEMENT_VORTEX_NAME,\ - STREET_CANYON_NAME, ROOFTOP_PERP_NAME, UPWIND_FACADE_FIELD,\ - ID_FIELD_STACKED_BLOCK, ID_FIELD_CANYON, ROOFTOP_CORN_NAME,\ - HEIGHT_FIELD, UPSTREAM_HEIGHT_FIELD, DOWNSTREAM_HEIGHT_FIELD,\ - ROOFTOP_CORNER_FACADE_LENGTH, ROOFTOP_CORNER_LENGTH,\ - UPWIND_FACADE_ANGLE_FIELD, ROOFTOP_WIND_FACTOR,\ - LENGTH_ZONE_FIELD, Y_WALL, MAX_CANYON_HEIGHT_FIELD,\ - ROOFTOP_PERP_LENGTH, ROOFTOP_PERP_HEIGHT, BASE_HEIGHT_FIELD,\ - UPPER_VERTICAL_THRESHOLD, POINT_RELATIVE_POSITION_FIELD, U, V, W,\ - DISTANCE_BUILD_TO_POINT_FIELD, ROOFTOP_PERP_VAR_HEIGHT,\ - WAKE_RELATIVE_POSITION_FIELD, ROOFTOP_CORNER_VAR_HEIGHT, DEBUG,\ - VEGETATION_CROWN_TOP_HEIGHT, ID_VEGETATION, TOP_CANOPY_HEIGHT_POINT,\ - VEGETATION_ATTENUATION_FACTOR, VEGETATION_CROWN_BASE_HEIGHT,\ - DZ, ID_POINT_Z, C_DZ, P_DZ, Z, X, Y,\ - Z_REF, P_RTP, VEGETATION_OPEN_NAME, VEGETATION_BUILT_NAME,\ - VEGETATION_FACTOR, UPSTREAM_PRIORITY_TABLES, UPSTREAM_WEIGHTING_TABLES,\ - UPSTREAM_WEIGHTING_INTER_RULES, UPSTREAM_WEIGHTING_INTRA_RULES,\ - DOWNSTREAM_WEIGTHING_TABLE, REF_HEIGHT_UPSTREAM_WEIGHTING,\ - REF_HEIGHT_FIELD, REF_HEIGHT_DOWNSTREAM_WEIGHTING,PRIORITY_FIELD,\ - ID_3D_POINT, V_REF, TEMPO_DIRECTORY, Y_POINT, ID_UPSTREAM_STACKED_BLOCK,\ - GEOMETRY_MERGE_TOLERANCE, SNAPPING_TOLERANCE, GEOMETRY_SIMPLIFICATION_DISTANCE,\ - ID_DOWNSTREAM_STACKED_BLOCK, DOWNWIND_FACADE_FIELD, PROFILE_TYPE,\ - HORIZ_WIND_SPEED, CAVITY_BACKWARD_NAME, WAKE_BACKWARD_NAME,\ - UPSTREAM_BACKWARD_PRIORITY_TABLES, UPSTREAM_BACKWARD_WEIGHTING_TABLES,\ - STACKED_BLOCK_UPSTREAMEST_X, COS_BLOCK_RIGHT_AZIMUTH,\ - COS_BLOCK_LEFT_AZIMUTH, COS_BLOCK_AZIMUTH, SIN_BLOCK_RIGHT_AZIMUTH,\ - SIN_BLOCK_LEFT_AZIMUTH, SIN_BLOCK_AZIMUTH, STACKED_BLOCK_WIDTH,\ - DOWNSTREAM_X_RELATIVE_POSITION, V_WEIGHT, U_WEIGHT, W_WEIGHT,\ - STACKED_BLOCK_X_MED, REMOVE_INITIALIZATION_OFFSET, IS_UPSTREAM_FIELD,\ - IS_UPSTREAM_UPSTREAM_WEIGHTING, CANYON_DELTAH_FIELD +from .GlobalVariables import ( + ALONG_WIND_ZONE_EXTEND, + CROSS_WIND_ZONE_EXTEND, + MESH_SIZE, + PREFIX_NAME, + GEOM_FIELD, + ID_POINT, + ID_POINT_X, + ID_POINT_Y, + CAVITY_NAME, + WAKE_NAME, + DISPLACEMENT_NAME, + DISPLACEMENT_VORTEX_NAME, + STREET_CANYON_NAME, + ROOFTOP_PERP_NAME, + UPWIND_FACADE_FIELD, + ID_FIELD_STACKED_BLOCK, + ID_FIELD_CANYON, + ROOFTOP_CORN_NAME, + HEIGHT_FIELD, + UPSTREAM_HEIGHT_FIELD, + DOWNSTREAM_HEIGHT_FIELD, + ROOFTOP_CORNER_FACADE_LENGTH, + ROOFTOP_CORNER_LENGTH, + UPWIND_FACADE_ANGLE_FIELD, + ROOFTOP_WIND_FACTOR, + LENGTH_ZONE_FIELD, + Y_WALL, + MAX_CANYON_HEIGHT_FIELD, + ROOFTOP_PERP_LENGTH, + ROOFTOP_PERP_HEIGHT, + BASE_HEIGHT_FIELD, + UPPER_VERTICAL_THRESHOLD, + POINT_RELATIVE_POSITION_FIELD, + U, + V, + W, + DISTANCE_BUILD_TO_POINT_FIELD, + ROOFTOP_PERP_VAR_HEIGHT, + WAKE_RELATIVE_POSITION_FIELD, + ROOFTOP_CORNER_VAR_HEIGHT, + DEBUG, + VEGETATION_CROWN_TOP_HEIGHT, + ID_VEGETATION, + TOP_CANOPY_HEIGHT_POINT, + VEGETATION_ATTENUATION_FACTOR, + VEGETATION_CROWN_BASE_HEIGHT, + DZ, + ID_POINT_Z, + C_DZ, + P_DZ, + Z, + X, + Y, + Z_REF, + P_RTP, + VEGETATION_OPEN_NAME, + VEGETATION_BUILT_NAME, + VEGETATION_FACTOR, + UPSTREAM_PRIORITY_TABLES, + UPSTREAM_WEIGHTING_TABLES, + UPSTREAM_WEIGHTING_INTER_RULES, + UPSTREAM_WEIGHTING_INTRA_RULES, + DOWNSTREAM_WEIGTHING_TABLE, + REF_HEIGHT_UPSTREAM_WEIGHTING, + REF_HEIGHT_FIELD, + REF_HEIGHT_DOWNSTREAM_WEIGHTING, + PRIORITY_FIELD, + ID_3D_POINT, + V_REF, + TEMPO_DIRECTORY, + Y_POINT, + ID_UPSTREAM_STACKED_BLOCK, + GEOMETRY_MERGE_TOLERANCE, + SNAPPING_TOLERANCE, + GEOMETRY_SIMPLIFICATION_DISTANCE, + ID_DOWNSTREAM_STACKED_BLOCK, + DOWNWIND_FACADE_FIELD, + PROFILE_TYPE, + HORIZ_WIND_SPEED, + CAVITY_BACKWARD_NAME, + WAKE_BACKWARD_NAME, + UPSTREAM_BACKWARD_PRIORITY_TABLES, + UPSTREAM_BACKWARD_WEIGHTING_TABLES, + STACKED_BLOCK_UPSTREAMEST_X, + COS_BLOCK_RIGHT_AZIMUTH, + COS_BLOCK_LEFT_AZIMUTH, + COS_BLOCK_AZIMUTH, + SIN_BLOCK_RIGHT_AZIMUTH, + SIN_BLOCK_LEFT_AZIMUTH, + SIN_BLOCK_AZIMUTH, + STACKED_BLOCK_WIDTH, + DOWNSTREAM_X_RELATIVE_POSITION, + V_WEIGHT, + U_WEIGHT, + W_WEIGHT, + STACKED_BLOCK_X_MED, + REMOVE_INITIALIZATION_OFFSET, + IS_UPSTREAM_FIELD, + IS_UPSTREAM_UPSTREAM_WEIGHTING, + CANYON_DELTAH_FIELD, +) import math import numpy as np import os -def createGrid(cursor, dicOfInputTables, srid, - alongWindZoneExtend = ALONG_WIND_ZONE_EXTEND, - crossWindZoneExtend = CROSS_WIND_ZONE_EXTEND, - meshSize = MESH_SIZE, - prefix = PREFIX_NAME): - """ Creates a grid of points which will be used to initiate the wind - speed field. The grid limits are defined by the enveloppe of a set of + +def createGrid( + cursor, + dicOfInputTables, + srid, + alongWindZoneExtend=ALONG_WIND_ZONE_EXTEND, + crossWindZoneExtend=CROSS_WIND_ZONE_EXTEND, + meshSize=MESH_SIZE, + prefix=PREFIX_NAME, +): + """Creates a grid of points which will be used to initiate the wind + speed field. The grid limits are defined by the enveloppe of a set of geometries ('dicOfInputTables') extended to a certain distance - along wind ('alongWindZoneExtend') and cross wind ('crossWindZoneExtend') - - Parameters - _ _ _ _ _ _ _ _ _ _ + along wind ('alongWindZoneExtend') and cross wind ('crossWindZoneExtend') + + Parameters + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries @@ -73,28 +149,31 @@ def createGrid(cursor, dicOfInputTables, srid, Distance (in meter) of the extend of the zone around the rotated obstacles in the cross-wind direction meshSize: float, default MESH_SIZE - Resolution (in meter) of the grid + Resolution (in meter) of the grid prefix: String, default PREFIX_NAME Prefix to add to the output table name - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ gridTable: String Name of the grid point table""" print("Creates the grid of points") - + # Output base name outputBaseName = "GRID" - + # Name of the output table - gridTable = DataUtil.prefix(outputBaseName, prefix = prefix) - + gridTable = DataUtil.prefix(outputBaseName, prefix=prefix) + # Gather all tables in one - gatherQuery = [safe("""SELECT {0} AS {0} FROM {1}""").format(GEOM_FIELD, - dicOfInputTables[t]) - for t in dicOfInputTables.keys()] - + gatherQuery = [ + safe("""SELECT {0} AS {0} FROM {1}""").format( + GEOM_FIELD, dicOfInputTables[t] + ) + for t in dicOfInputTables.keys() + ] + # Calculate the extend of the envelope of all geometries finalQuery = safe(""" DROP TABLE IF EXISTS {0}; @@ -108,28 +187,32 @@ def createGrid(cursor, dicOfInputTables, srid, {2}, {3}) FROM ({5})), {4}, - {4})""").format(gridTable, - GEOM_FIELD, - crossWindZoneExtend, - alongWindZoneExtend, - meshSize, - " UNION ALL ".join(gatherQuery), - ID_POINT, - ID_POINT_X, - ID_POINT_Y, - srid, - Y_POINT) + {4})""").format( + gridTable, + GEOM_FIELD, + crossWindZoneExtend, + alongWindZoneExtend, + meshSize, + " UNION ALL ".join(gatherQuery), + ID_POINT, + ID_POINT_X, + ID_POINT_Y, + srid, + Y_POINT, + ) cursor.execute(finalQuery) - + return gridTable -def affectsPointToBuildZone(cursor, gridTable, dicOfBuildRockleZoneTable, - prefix = PREFIX_NAME): - """ Affects each point to a building Rockle zone and calculates relative + +def affectsPointToBuildZone( + cursor, gridTable, dicOfBuildRockleZoneTable, prefix=PREFIX_NAME +): + """Affects each point to a building Rockle zone and calculates relative point position within the zone for some of them. - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries @@ -140,9 +223,9 @@ def affectsPointToBuildZone(cursor, gridTable, dicOfBuildRockleZoneTable, as value the corresponding table name prefix: String, default PREFIX_NAME Prefix to add to the output table name - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ dicOfOutputTables: dictionary of table name Dictionary having as key the type of Rockle zone and as value @@ -150,81 +233,113 @@ def affectsPointToBuildZone(cursor, gridTable, dicOfBuildRockleZoneTable, verticalLineTable: String Vertical lines (not along z but along the grid "north-south") useful for future calculations""" - print("""Affects each grid point to a building Rockle zone and calculates needed - variables for 3D wind speed""") - + print( + """Affects each grid point to a building Rockle zone and calculates needed + variables for 3D wind speed""" + ) + # Name of the output tables - dicOfOutputTables = {t: DataUtil.postfix(tableName = DataUtil.prefix(tableName = t, - prefix = prefix), - suffix = "INIT_POINTS") for t in dicOfBuildRockleZoneTable} - verticalLineTable = DataUtil.prefix(tableName = "VERTICAL_LINES", - prefix = prefix) - + dicOfOutputTables = { + t: DataUtil.postfix( + tableName=DataUtil.prefix(tableName=t, prefix=prefix), + suffix="INIT_POINTS", + ) + for t in dicOfBuildRockleZoneTable + } + verticalLineTable = DataUtil.prefix( + tableName="VERTICAL_LINES", prefix=prefix + ) + # Temporary tables (and prefix for temporary tables) tempoCavity = DataUtil.postfix("TEMPO_CAVITY") - dicOfTempoOutput = {t: DataUtil.postfix(DataUtil.prefix(tableName = dicOfOutputTables[t], - prefix = "TEMPO")) - for t in dicOfBuildRockleZoneTable} - dicOfPrefixZoneLim = {t: DataUtil.postfix(DataUtil.prefix(tableName = t, - prefix = "ZONE_LIMITS")) - for t in dicOfBuildRockleZoneTable} - + dicOfTempoOutput = { + t: DataUtil.postfix( + DataUtil.prefix(tableName=dicOfOutputTables[t], prefix="TEMPO") + ) + for t in dicOfBuildRockleZoneTable + } + dicOfPrefixZoneLim = { + t: DataUtil.postfix(DataUtil.prefix(tableName=t, prefix="ZONE_LIMITS")) + for t in dicOfBuildRockleZoneTable + } + # Tables that should keep y value (distance from upwind building) - listTabYvalues = [CAVITY_NAME, WAKE_NAME , DISPLACEMENT_NAME, - DISPLACEMENT_VORTEX_NAME , STREET_CANYON_NAME, - ROOFTOP_PERP_NAME] - + listTabYvalues = [ + CAVITY_NAME, + WAKE_NAME, + DISPLACEMENT_NAME, + DISPLACEMENT_VORTEX_NAME, + STREET_CANYON_NAME, + ROOFTOP_PERP_NAME, + ] + # ID_ZONE field to use for join depending on zone type - idZone = { DISPLACEMENT_NAME : UPWIND_FACADE_FIELD, - DISPLACEMENT_VORTEX_NAME: UPWIND_FACADE_FIELD, - CAVITY_NAME : DOWNWIND_FACADE_FIELD, - WAKE_NAME : DOWNWIND_FACADE_FIELD, - STREET_CANYON_NAME : ID_FIELD_CANYON, - ROOFTOP_PERP_NAME : UPWIND_FACADE_FIELD, - ROOFTOP_CORN_NAME : UPWIND_FACADE_FIELD} - - query = [safe("""{0}; + idZone = { + DISPLACEMENT_NAME: UPWIND_FACADE_FIELD, + DISPLACEMENT_VORTEX_NAME: UPWIND_FACADE_FIELD, + CAVITY_NAME: DOWNWIND_FACADE_FIELD, + WAKE_NAME: DOWNWIND_FACADE_FIELD, + STREET_CANYON_NAME: ID_FIELD_CANYON, + ROOFTOP_PERP_NAME: UPWIND_FACADE_FIELD, + ROOFTOP_CORN_NAME: UPWIND_FACADE_FIELD, + } + + query = [ + safe("""{0}; DROP TABLE IF EXISTS {1} - """).format( DataUtil.createIndex(tableName=gridTable, - fieldName=GEOM_FIELD, - isSpatial=True), - ",".join(dicOfOutputTables.values()))] + """).format( + DataUtil.createIndex( + tableName=gridTable, fieldName=GEOM_FIELD, isSpatial=True + ), + ",".join(dicOfOutputTables.values()), + ) + ] # Construct a query to affect each point to a Rockle zone for i, t in enumerate(dicOfBuildRockleZoneTable): # The query differs depending on whether y value should be kept - queryKeepY = safe("b.{1}, b.{0}, b.{2},").format(ID_POINT_X, Y_POINT, ID_POINT_Y) - + queryKeepY = safe("b.{1}, b.{0}, b.{2},").format( + ID_POINT_X, Y_POINT, ID_POINT_Y + ) + # The columns to keep are different in case of street canyon zone # or rooftop corner zone columnsToKeepQuery = safe("""b.{0}, {1} a.{2}, a.{3} - """).format( ID_POINT, - queryKeepY, - idZone[t], - HEIGHT_FIELD) - if t==STREET_CANYON_NAME: - columnsToKeepQuery = safe("""b.{0}, {1} a.{2}, a.{3}, a.{4}, a.{5}, a.{6} - """).format( ID_POINT, - queryKeepY, - idZone[t], - UPSTREAM_HEIGHT_FIELD, - DOWNSTREAM_HEIGHT_FIELD, - UPWIND_FACADE_FIELD, - ID_UPSTREAM_STACKED_BLOCK) - elif t==ROOFTOP_CORN_NAME: - columnsToKeepQuery = safe("""b.{0}, a.{1}, a.{2}, b.{3}, a.{4}, a.{5}, a.{6}, a.{7}, + """).format( + ID_POINT, queryKeepY, idZone[t], HEIGHT_FIELD + ) + if t == STREET_CANYON_NAME: + columnsToKeepQuery = safe( + """b.{0}, {1} a.{2}, a.{3}, a.{4}, a.{5}, a.{6} + """ + ).format( + ID_POINT, + queryKeepY, + idZone[t], + UPSTREAM_HEIGHT_FIELD, + DOWNSTREAM_HEIGHT_FIELD, + UPWIND_FACADE_FIELD, + ID_UPSTREAM_STACKED_BLOCK, + ) + elif t == ROOFTOP_CORN_NAME: + columnsToKeepQuery = safe( + """b.{0}, a.{1}, a.{2}, b.{3}, a.{4}, a.{5}, a.{6}, a.{7}, a.GEOM_CORNER_POINT, b.{8} - """).format( ID_POINT, - idZone[t], - HEIGHT_FIELD, - GEOM_FIELD, - ROOFTOP_CORNER_FACADE_LENGTH, - ROOFTOP_CORNER_LENGTH, - UPWIND_FACADE_ANGLE_FIELD, - ROOFTOP_WIND_FACTOR, - ID_POINT_X) - - query.append(safe(""" + """ + ).format( + ID_POINT, + idZone[t], + HEIGHT_FIELD, + GEOM_FIELD, + ROOFTOP_CORNER_FACADE_LENGTH, + ROOFTOP_CORNER_LENGTH, + UPWIND_FACADE_ANGLE_FIELD, + ROOFTOP_WIND_FACTOR, + ID_POINT_X, + ) + + query.append( + safe(""" {5}; DROP TABLE IF EXISTS {2}; CREATE TABLE {2} @@ -232,22 +347,26 @@ def affectsPointToBuildZone(cursor, gridTable, dicOfBuildRockleZoneTable, FROM {0} AS a, {3} AS b WHERE a.{1} && b.{1} AND ST_INTERSECTS(a.{1}, b.{1}) - """).format( dicOfBuildRockleZoneTable[t], - GEOM_FIELD, - dicOfTempoOutput[t], - gridTable, - columnsToKeepQuery, - DataUtil.createIndex(tableName=dicOfBuildRockleZoneTable[t], - fieldName=GEOM_FIELD, - isSpatial=True))) - + """).format( + dicOfBuildRockleZoneTable[t], + GEOM_FIELD, + dicOfTempoOutput[t], + gridTable, + columnsToKeepQuery, + DataUtil.createIndex( + tableName=dicOfBuildRockleZoneTable[t], + fieldName=GEOM_FIELD, + isSpatial=True, + ), + ) + ) + # Get the ID of the lower grid point row cursor.execute(safe(""" SELECT MAX(DISTINCT {0}) AS {0} FROM {1}; - """).format( ID_POINT_Y, - gridTable)) + """).format(ID_POINT_Y, gridTable)) idLowerGridRow = cursor.fetchall()[0][0] - + # For Rockle zones that needs relative point distance, extra calculation is needed # First creates vertical lines endOfQuery = safe(""" @@ -262,26 +381,28 @@ def affectsPointToBuildZone(cursor, gridTable, dicOfBuildRockleZoneTable, FROM {3} AS a LEFT JOIN {3} AS b ON a.{1} = b.{1} WHERE a.{4} = 1 AND b.{4} = {5}; {8}; - """).format( verticalLineTable, - ID_POINT_X, - GEOM_FIELD, - gridTable, - ID_POINT_Y, - idLowerGridRow, - DataUtil.createIndex( tableName=gridTable, - fieldName=ID_POINT_X, - isSpatial=False), - DataUtil.createIndex( tableName=gridTable, - fieldName=ID_POINT_Y, - isSpatial=False), - DataUtil.createIndex( tableName=gridTable, - fieldName=GEOM_FIELD, - isSpatial=True), - X) - + """).format( + verticalLineTable, + ID_POINT_X, + GEOM_FIELD, + gridTable, + ID_POINT_Y, + idLowerGridRow, + DataUtil.createIndex( + tableName=gridTable, fieldName=ID_POINT_X, isSpatial=False + ), + DataUtil.createIndex( + tableName=gridTable, fieldName=ID_POINT_Y, isSpatial=False + ), + DataUtil.createIndex( + tableName=gridTable, fieldName=GEOM_FIELD, isSpatial=True + ), + X, + ) + # Fields to keep in the zone table (zone dependent) varToKeepZone = { - DISPLACEMENT_NAME : """b.{0}, + DISPLACEMENT_NAME: """b.{0}, b.{1}, a.{2}, b.{6}, @@ -289,13 +410,15 @@ def affectsPointToBuildZone(cursor, gridTable, dicOfBuildRockleZoneTable, ST_TOMULTILINE(b.{3})) ) AS {5}, ST_LENGTH(ST_INTERSECTION(a.{3}, b.{3})) AS {4} - """.format( idZone[DISPLACEMENT_NAME], - HEIGHT_FIELD, - ID_POINT_X, - GEOM_FIELD, - LENGTH_ZONE_FIELD+DISPLACEMENT_NAME[0], - Y_WALL, - UPWIND_FACADE_ANGLE_FIELD), + """.format( + idZone[DISPLACEMENT_NAME], + HEIGHT_FIELD, + ID_POINT_X, + GEOM_FIELD, + LENGTH_ZONE_FIELD + DISPLACEMENT_NAME[0], + Y_WALL, + UPWIND_FACADE_ANGLE_FIELD, + ), DISPLACEMENT_VORTEX_NAME: """b.{0}, b.{1}, a.{2}, @@ -304,14 +427,16 @@ def affectsPointToBuildZone(cursor, gridTable, dicOfBuildRockleZoneTable, ST_TOMULTILINE(b.{3})) ) AS {5}, ST_LENGTH(ST_INTERSECTION(a.{3}, b.{3})) AS {4} - """.format( idZone[DISPLACEMENT_VORTEX_NAME], - HEIGHT_FIELD, - ID_POINT_X, - GEOM_FIELD, - LENGTH_ZONE_FIELD+DISPLACEMENT_VORTEX_NAME[0], - Y_WALL, - UPWIND_FACADE_ANGLE_FIELD), - CAVITY_NAME : """b.{0}, + """.format( + idZone[DISPLACEMENT_VORTEX_NAME], + HEIGHT_FIELD, + ID_POINT_X, + GEOM_FIELD, + LENGTH_ZONE_FIELD + DISPLACEMENT_VORTEX_NAME[0], + Y_WALL, + UPWIND_FACADE_ANGLE_FIELD, + ), + CAVITY_NAME: """b.{0}, b.{1}, a.{2}, ST_YMAX(ST_INTERSECTION(a.{3}, @@ -337,26 +462,28 @@ def affectsPointToBuildZone(cursor, gridTable, dicOfBuildRockleZoneTable, ELSE b.{14} END AS {15}, POWER((b.{16}/2-ABS(a.{8}-b.{18}))/(b.{16}/2),0.5) AS {17} - """.format( idZone[CAVITY_NAME], - HEIGHT_FIELD, - ID_POINT_X, - GEOM_FIELD, - LENGTH_ZONE_FIELD+CAVITY_NAME[0], - Y_WALL, - ID_POINT_Y, - ID_FIELD_STACKED_BLOCK, - X, - STACKED_BLOCK_UPSTREAMEST_X, - COS_BLOCK_RIGHT_AZIMUTH, - COS_BLOCK_LEFT_AZIMUTH, - COS_BLOCK_AZIMUTH, - SIN_BLOCK_RIGHT_AZIMUTH, - SIN_BLOCK_LEFT_AZIMUTH, - SIN_BLOCK_AZIMUTH, - STACKED_BLOCK_WIDTH, - DOWNSTREAM_X_RELATIVE_POSITION, - STACKED_BLOCK_X_MED), - WAKE_NAME : """b.{0}, + """.format( + idZone[CAVITY_NAME], + HEIGHT_FIELD, + ID_POINT_X, + GEOM_FIELD, + LENGTH_ZONE_FIELD + CAVITY_NAME[0], + Y_WALL, + ID_POINT_Y, + ID_FIELD_STACKED_BLOCK, + X, + STACKED_BLOCK_UPSTREAMEST_X, + COS_BLOCK_RIGHT_AZIMUTH, + COS_BLOCK_LEFT_AZIMUTH, + COS_BLOCK_AZIMUTH, + SIN_BLOCK_RIGHT_AZIMUTH, + SIN_BLOCK_LEFT_AZIMUTH, + SIN_BLOCK_AZIMUTH, + STACKED_BLOCK_WIDTH, + DOWNSTREAM_X_RELATIVE_POSITION, + STACKED_BLOCK_X_MED, + ), + WAKE_NAME: """b.{0}, b.{1}, a.{2}, ST_YMAX(ST_INTERSECTION(a.{3}, @@ -372,14 +499,17 @@ def affectsPointToBuildZone(cursor, gridTable, dicOfBuildRockleZoneTable, ) END AS {4}, b.{6} - """.format( idZone[WAKE_NAME], - HEIGHT_FIELD, - ID_POINT_X, - GEOM_FIELD, - LENGTH_ZONE_FIELD+WAKE_NAME[0], - Y_WALL, - ID_FIELD_STACKED_BLOCK), - STREET_CANYON_NAME : f"""b.{idZone[STREET_CANYON_NAME]}, + """.format( + idZone[WAKE_NAME], + HEIGHT_FIELD, + ID_POINT_X, + GEOM_FIELD, + LENGTH_ZONE_FIELD + WAKE_NAME[0], + Y_WALL, + ID_FIELD_STACKED_BLOCK, + ), + STREET_CANYON_NAME: ( + f"""b.{idZone[STREET_CANYON_NAME]}, b.{UPSTREAM_HEIGHT_FIELD}, LEAST(b.{DOWNSTREAM_HEIGHT_FIELD}, b.{UPSTREAM_HEIGHT_FIELD}) AS {MAX_CANYON_HEIGHT_FIELD}, b.{DOWNSTREAM_HEIGHT_FIELD}-b.{UPSTREAM_HEIGHT_FIELD} AS {CANYON_DELTAH_FIELD}, @@ -397,8 +527,9 @@ def affectsPointToBuildZone(cursor, gridTable, dicOfBuildRockleZoneTable, b.{ID_UPSTREAM_STACKED_BLOCK}, a.{ID_POINT_Y}, b.{DOWNWIND_FACADE_FIELD} - """, - ROOFTOP_PERP_NAME : """b.{0}, + """ + ), + ROOFTOP_PERP_NAME: """b.{0}, b.{1}, a.{2}, b.{3}, @@ -406,17 +537,22 @@ def affectsPointToBuildZone(cursor, gridTable, dicOfBuildRockleZoneTable, ST_YMAX(ST_INTERSECTION(a.{5}, b.{5}) ) AS {6} - """.format( idZone[ROOFTOP_PERP_NAME], - HEIGHT_FIELD, - ID_POINT_X, - ROOFTOP_PERP_LENGTH, - ROOFTOP_PERP_HEIGHT, - GEOM_FIELD, - Y_WALL)} - - # Calculates the coordinate of the upper and lower part of each of the - # Röckle zones for each "north/south" line - endOfQuery += safe(";").join([safe(""" + """.format( + idZone[ROOFTOP_PERP_NAME], + HEIGHT_FIELD, + ID_POINT_X, + ROOFTOP_PERP_LENGTH, + ROOFTOP_PERP_HEIGHT, + GEOM_FIELD, + Y_WALL, + ), + } + + # Calculates the coordinate of the upper and lower part of each of the + # Röckle zones for each "north/south" line + endOfQuery += safe(";").join( + [ + safe(""" {6}; DROP TABLE IF EXISTS {0}, {4}; CREATE TABLE {0} @@ -424,23 +560,30 @@ def affectsPointToBuildZone(cursor, gridTable, dicOfBuildRockleZoneTable, FROM {3} AS a, {2} AS b WHERE a.{1} && b.{1} AND ST_INTERSECTS(a.{1}, b.{1}) AND ST_DIMENSION(ST_INTERSECTION(a.{1}, b.{1})) > 0; - """).format( dicOfPrefixZoneLim[t], - GEOM_FIELD, - dicOfBuildRockleZoneTable[t], - verticalLineTable, - dicOfOutputTables[t], - varToKeepZone[t], - DataUtil.createIndex( tableName=dicOfBuildRockleZoneTable[t], - fieldName=GEOM_FIELD, - isSpatial=True)) - for t in listTabYvalues]) + """).format( + dicOfPrefixZoneLim[t], + GEOM_FIELD, + dicOfBuildRockleZoneTable[t], + verticalLineTable, + dicOfOutputTables[t], + varToKeepZone[t], + DataUtil.createIndex( + tableName=dicOfBuildRockleZoneTable[t], + fieldName=GEOM_FIELD, + isSpatial=True, + ), + ) + for t in listTabYvalues + ] + ) query.append(endOfQuery) cursor.execute(";".join(query)) - + # The cavity zone length is needed for the wind speed calculation of # wake zone points and for the upper height limit of the street canyon # while the wake zone length is needed for cavity zone wind speed calculation - cursor.execute(safe(""" + cursor.execute( + safe(""" {idx1} {idx2} {idx3} @@ -485,32 +628,105 @@ def affectsPointToBuildZone(cursor, gridTable, dicOfBuildRockleZoneTable, DROP TABLE IF EXISTS {cavity_table}; ALTER TABLE TEMPO_CAV RENAME TO {cavity_table} """).format( - idx1=safe(DataUtil.createIndex(dicOfPrefixZoneLim[CAVITY_NAME], fieldName=DOWNWIND_FACADE_FIELD, isSpatial=False)), - idx2=safe(DataUtil.createIndex(dicOfPrefixZoneLim[CAVITY_NAME], fieldName=ID_POINT_X, isSpatial=False)), - idx3=safe(DataUtil.createIndex(dicOfPrefixZoneLim[WAKE_NAME], fieldName=DOWNWIND_FACADE_FIELD, isSpatial=False)), - idx4=safe(DataUtil.createIndex(dicOfPrefixZoneLim[WAKE_NAME], fieldName=ID_POINT_X, isSpatial=False)), - len_cav=safe(LENGTH_ZONE_FIELD+CAVITY_NAME[0]), - cos_az=safe(COS_BLOCK_AZIMUTH), - sin_az=safe(SIN_BLOCK_AZIMUTH), - rel_pos=safe(DOWNSTREAM_X_RELATIVE_POSITION), - wake_table=safe(dicOfPrefixZoneLim[WAKE_NAME]), - cavity_table=safe(dicOfPrefixZoneLim[CAVITY_NAME]), - downwind_f=safe(DOWNWIND_FACADE_FIELD), - id_px=safe(ID_POINT_X), - idx5=safe(DataUtil.createIndex(safe(dicOfPrefixZoneLim[CAVITY_NAME]), fieldName=safe(DOWNWIND_FACADE_FIELD), isSpatial=False)), - idx6=safe(DataUtil.createIndex(safe(dicOfPrefixZoneLim[CAVITY_NAME]), fieldName=safe(ID_POINT_X), isSpatial=False)), - idx7=safe(DataUtil.createIndex(safe(dicOfPrefixZoneLim[STREET_CANYON_NAME]), fieldName=safe(DOWNWIND_FACADE_FIELD), isSpatial=False)), - idx8=safe(DataUtil.createIndex(safe(dicOfPrefixZoneLim[STREET_CANYON_NAME]), fieldName=safe(ID_POINT_X), isSpatial=False)), - canyon_table=safe(dicOfPrefixZoneLim[STREET_CANYON_NAME]), - idx9=safe(DataUtil.createIndex(safe(dicOfPrefixZoneLim[CAVITY_NAME]), fieldName=safe(DOWNWIND_FACADE_FIELD), isSpatial=False)), - idx10=safe(DataUtil.createIndex(safe(dicOfPrefixZoneLim[CAVITY_NAME]), fieldName=safe(ID_POINT_X), isSpatial=False)), - idx11=safe(DataUtil.createIndex(safe(dicOfPrefixZoneLim[WAKE_NAME]), fieldName=safe(DOWNWIND_FACADE_FIELD), isSpatial=False)), - idx12=safe(DataUtil.createIndex(safe(dicOfPrefixZoneLim[WAKE_NAME]), fieldName=safe(ID_POINT_X), isSpatial=False)), - len_wake=safe(LENGTH_ZONE_FIELD+WAKE_NAME[0]) - )) + idx1=safe( + DataUtil.createIndex( + dicOfPrefixZoneLim[CAVITY_NAME], + fieldName=DOWNWIND_FACADE_FIELD, + isSpatial=False, + ) + ), + idx2=safe( + DataUtil.createIndex( + dicOfPrefixZoneLim[CAVITY_NAME], + fieldName=ID_POINT_X, + isSpatial=False, + ) + ), + idx3=safe( + DataUtil.createIndex( + dicOfPrefixZoneLim[WAKE_NAME], + fieldName=DOWNWIND_FACADE_FIELD, + isSpatial=False, + ) + ), + idx4=safe( + DataUtil.createIndex( + dicOfPrefixZoneLim[WAKE_NAME], + fieldName=ID_POINT_X, + isSpatial=False, + ) + ), + len_cav=safe(LENGTH_ZONE_FIELD + CAVITY_NAME[0]), + cos_az=safe(COS_BLOCK_AZIMUTH), + sin_az=safe(SIN_BLOCK_AZIMUTH), + rel_pos=safe(DOWNSTREAM_X_RELATIVE_POSITION), + wake_table=safe(dicOfPrefixZoneLim[WAKE_NAME]), + cavity_table=safe(dicOfPrefixZoneLim[CAVITY_NAME]), + downwind_f=safe(DOWNWIND_FACADE_FIELD), + id_px=safe(ID_POINT_X), + idx5=safe( + DataUtil.createIndex( + safe(dicOfPrefixZoneLim[CAVITY_NAME]), + fieldName=safe(DOWNWIND_FACADE_FIELD), + isSpatial=False, + ) + ), + idx6=safe( + DataUtil.createIndex( + safe(dicOfPrefixZoneLim[CAVITY_NAME]), + fieldName=safe(ID_POINT_X), + isSpatial=False, + ) + ), + idx7=safe( + DataUtil.createIndex( + safe(dicOfPrefixZoneLim[STREET_CANYON_NAME]), + fieldName=safe(DOWNWIND_FACADE_FIELD), + isSpatial=False, + ) + ), + idx8=safe( + DataUtil.createIndex( + safe(dicOfPrefixZoneLim[STREET_CANYON_NAME]), + fieldName=safe(ID_POINT_X), + isSpatial=False, + ) + ), + canyon_table=safe(dicOfPrefixZoneLim[STREET_CANYON_NAME]), + idx9=safe( + DataUtil.createIndex( + safe(dicOfPrefixZoneLim[CAVITY_NAME]), + fieldName=safe(DOWNWIND_FACADE_FIELD), + isSpatial=False, + ) + ), + idx10=safe( + DataUtil.createIndex( + safe(dicOfPrefixZoneLim[CAVITY_NAME]), + fieldName=safe(ID_POINT_X), + isSpatial=False, + ) + ), + idx11=safe( + DataUtil.createIndex( + safe(dicOfPrefixZoneLim[WAKE_NAME]), + fieldName=safe(DOWNWIND_FACADE_FIELD), + isSpatial=False, + ) + ), + idx12=safe( + DataUtil.createIndex( + safe(dicOfPrefixZoneLim[WAKE_NAME]), + fieldName=safe(ID_POINT_X), + isSpatial=False, + ) + ), + len_wake=safe(LENGTH_ZONE_FIELD + WAKE_NAME[0]), + ) + ) # Fields to keep in the point table (zone dependent) varToKeepPoint = { - DISPLACEMENT_NAME : """b.{0}, + DISPLACEMENT_NAME: """b.{0}, 0.6*a.{4}*SQRT(1-POWER((b.{10}-a.{6})/ a.{2}, 2)) AS {1}, (b.{10}-a.{6})/a.{2} AS {5}, @@ -518,32 +734,36 @@ def affectsPointToBuildZone(cursor, gridTable, dicOfBuildRockleZoneTable, a.{4}, 0 AS {8}, 1 AS {9}, - CAST(a.{6} AS INTEGER) AS {6}""".format(ID_POINT, - UPPER_VERTICAL_THRESHOLD, - LENGTH_ZONE_FIELD+DISPLACEMENT_NAME[0], - idZone[DISPLACEMENT_NAME], - HEIGHT_FIELD, - POINT_RELATIVE_POSITION_FIELD+DISPLACEMENT_NAME[0], - Y_WALL, - UPWIND_FACADE_ANGLE_FIELD, - U, - V, - Y_POINT), + CAST(a.{6} AS INTEGER) AS {6}""".format( + ID_POINT, + UPPER_VERTICAL_THRESHOLD, + LENGTH_ZONE_FIELD + DISPLACEMENT_NAME[0], + idZone[DISPLACEMENT_NAME], + HEIGHT_FIELD, + POINT_RELATIVE_POSITION_FIELD + DISPLACEMENT_NAME[0], + Y_WALL, + UPWIND_FACADE_ANGLE_FIELD, + U, + V, + Y_POINT, + ), DISPLACEMENT_VORTEX_NAME: """b.{0}, 0.5*a.{4}*SQRT(1-POWER((b.{7}-a.{6})/ a.{2}, 2)) AS {1}, (b.{7}-a.{6})/a.{2} AS {5}, b.{3}, a.{4}, - CAST(a.{6} AS INTEGER) AS {6}""".format(ID_POINT, - UPPER_VERTICAL_THRESHOLD, - LENGTH_ZONE_FIELD+DISPLACEMENT_VORTEX_NAME[0], - idZone[DISPLACEMENT_VORTEX_NAME], - HEIGHT_FIELD, - POINT_RELATIVE_POSITION_FIELD+DISPLACEMENT_VORTEX_NAME[0], - Y_WALL, - Y_POINT), - CAVITY_NAME : """a.{11}, + CAST(a.{6} AS INTEGER) AS {6}""".format( + ID_POINT, + UPPER_VERTICAL_THRESHOLD, + LENGTH_ZONE_FIELD + DISPLACEMENT_VORTEX_NAME[0], + idZone[DISPLACEMENT_VORTEX_NAME], + HEIGHT_FIELD, + POINT_RELATIVE_POSITION_FIELD + DISPLACEMENT_VORTEX_NAME[0], + Y_WALL, + Y_POINT, + ), + CAVITY_NAME: """a.{11}, b.{0}, a.{4}*SQRT(1-POWER((a.{7}-b.{9})/ a.{2}, 2)) AS {1}, @@ -559,24 +779,26 @@ def affectsPointToBuildZone(cursor, gridTable, dicOfBuildRockleZoneTable, a.{13}, a.{14}, a.{15}, - (a.{7}-b.{9})/a.{15} AS {16}""".format(ID_POINT, - UPPER_VERTICAL_THRESHOLD, - LENGTH_ZONE_FIELD+CAVITY_NAME[0], - idZone[CAVITY_NAME], - HEIGHT_FIELD, - POINT_RELATIVE_POSITION_FIELD+CAVITY_NAME[0], - ID_POINT_X, - Y_WALL, - DISTANCE_BUILD_TO_POINT_FIELD, - Y_POINT, - ID_POINT_Y, - ID_FIELD_STACKED_BLOCK, - COS_BLOCK_AZIMUTH, - SIN_BLOCK_AZIMUTH, - DOWNSTREAM_X_RELATIVE_POSITION, - LENGTH_ZONE_FIELD+WAKE_NAME[0], - POINT_RELATIVE_POSITION_FIELD+WAKE_NAME[0]), - WAKE_NAME : """a.{9}, + (a.{7}-b.{9})/a.{15} AS {16}""".format( + ID_POINT, + UPPER_VERTICAL_THRESHOLD, + LENGTH_ZONE_FIELD + CAVITY_NAME[0], + idZone[CAVITY_NAME], + HEIGHT_FIELD, + POINT_RELATIVE_POSITION_FIELD + CAVITY_NAME[0], + ID_POINT_X, + Y_WALL, + DISTANCE_BUILD_TO_POINT_FIELD, + Y_POINT, + ID_POINT_Y, + ID_FIELD_STACKED_BLOCK, + COS_BLOCK_AZIMUTH, + SIN_BLOCK_AZIMUTH, + DOWNSTREAM_X_RELATIVE_POSITION, + LENGTH_ZONE_FIELD + WAKE_NAME[0], + POINT_RELATIVE_POSITION_FIELD + WAKE_NAME[0], + ), + WAKE_NAME: """a.{9}, b.{0}, a.{4}*SQRT(1-POWER((a.{7}-b.{8})/ a.{2}, 2)) AS {1}, @@ -598,24 +820,26 @@ def affectsPointToBuildZone(cursor, gridTable, dicOfBuildRockleZoneTable, a.{14}, a.{15}, (a.{7}-b.{8})/a.{2} AS {16} - """.format(ID_POINT, - UPPER_VERTICAL_THRESHOLD, - LENGTH_ZONE_FIELD+WAKE_NAME[0], - idZone[WAKE_NAME], - HEIGHT_FIELD, - DISTANCE_BUILD_TO_POINT_FIELD, - ID_POINT_X, - Y_WALL, - Y_POINT, - ID_FIELD_STACKED_BLOCK, - LENGTH_ZONE_FIELD+CAVITY_NAME[0], - WAKE_RELATIVE_POSITION_FIELD, - UPPER_VERTICAL_THRESHOLD + CAVITY_NAME[0], - COS_BLOCK_AZIMUTH, - SIN_BLOCK_AZIMUTH, - DOWNSTREAM_X_RELATIVE_POSITION, - POINT_RELATIVE_POSITION_FIELD+WAKE_NAME[0]), - STREET_CANYON_NAME : """b.{0}, + """.format( + ID_POINT, + UPPER_VERTICAL_THRESHOLD, + LENGTH_ZONE_FIELD + WAKE_NAME[0], + idZone[WAKE_NAME], + HEIGHT_FIELD, + DISTANCE_BUILD_TO_POINT_FIELD, + ID_POINT_X, + Y_WALL, + Y_POINT, + ID_FIELD_STACKED_BLOCK, + LENGTH_ZONE_FIELD + CAVITY_NAME[0], + WAKE_RELATIVE_POSITION_FIELD, + UPPER_VERTICAL_THRESHOLD + CAVITY_NAME[0], + COS_BLOCK_AZIMUTH, + SIN_BLOCK_AZIMUTH, + DOWNSTREAM_X_RELATIVE_POSITION, + POINT_RELATIVE_POSITION_FIELD + WAKE_NAME[0], + ), + STREET_CANYON_NAME: """b.{0}, (SIN(2*(a.{1}-PI()/2))*(0.5+(a.{9}-b.{13})* (a.{2}-(a.{9}-b.{13}))/ (0.5*POWER(a.{2},2))))* @@ -642,45 +866,53 @@ def affectsPointToBuildZone(cursor, gridTable, dicOfBuildRockleZoneTable, b.{13}, a.{2}, a.{7}*SQRT(1-POWER((a.{9}-b.{13})/a.{17}, 2)) AS {18} - """.format( ID_POINT, - UPWIND_FACADE_ANGLE_FIELD, - LENGTH_ZONE_FIELD+STREET_CANYON_NAME[0], - U, - V, - W, - idZone[STREET_CANYON_NAME], - UPSTREAM_HEIGHT_FIELD, - BASE_HEIGHT_FIELD, - Y_WALL, - ID_POINT_X, - UPWIND_FACADE_FIELD, - MAX_CANYON_HEIGHT_FIELD, - Y_POINT, - ID_UPSTREAM_STACKED_BLOCK, - ID_POINT_Y, - DOWNWIND_FACADE_FIELD, - LENGTH_ZONE_FIELD+CAVITY_NAME[0], - UPPER_VERTICAL_THRESHOLD, - CANYON_DELTAH_FIELD), - ROOFTOP_PERP_NAME : """b.{0}, + """.format( + ID_POINT, + UPWIND_FACADE_ANGLE_FIELD, + LENGTH_ZONE_FIELD + STREET_CANYON_NAME[0], + U, + V, + W, + idZone[STREET_CANYON_NAME], + UPSTREAM_HEIGHT_FIELD, + BASE_HEIGHT_FIELD, + Y_WALL, + ID_POINT_X, + UPWIND_FACADE_FIELD, + MAX_CANYON_HEIGHT_FIELD, + Y_POINT, + ID_UPSTREAM_STACKED_BLOCK, + ID_POINT_Y, + DOWNWIND_FACADE_FIELD, + LENGTH_ZONE_FIELD + CAVITY_NAME[0], + UPPER_VERTICAL_THRESHOLD, + CANYON_DELTAH_FIELD, + ), + ROOFTOP_PERP_NAME: """b.{0}, a.{3}*SQRT(1-POWER(((a.{6}-b.{8})-a.{4}/2)/ a.{4}, 2)) AS {5}, b.{1}, a.{2}, CAST(a.{6} AS INTEGER) AS {6}, - a.{7}""".format(ID_POINT, - idZone[ROOFTOP_PERP_NAME], - HEIGHT_FIELD, - ROOFTOP_PERP_HEIGHT, - ROOFTOP_PERP_LENGTH, - ROOFTOP_PERP_VAR_HEIGHT, - Y_WALL, - ID_POINT_X, - Y_POINT)} - - # Last calculate the relative position of each point according + a.{7}""".format( + ID_POINT, + idZone[ROOFTOP_PERP_NAME], + HEIGHT_FIELD, + ROOFTOP_PERP_HEIGHT, + ROOFTOP_PERP_LENGTH, + ROOFTOP_PERP_VAR_HEIGHT, + Y_WALL, + ID_POINT_X, + Y_POINT, + ), + } + + # Last calculate the relative position of each point according # to the upper and lower part of the Rockle zones - cursor.execute(safe(";").join([safe(""" + cursor.execute( + safe(";").join( + [ + safe(""" {0}; {1}; {2}; @@ -690,25 +922,42 @@ def affectsPointToBuildZone(cursor, gridTable, dicOfBuildRockleZoneTable, AS SELECT {5} FROM {6} AS a RIGHT JOIN {7} AS b ON a.{8} = b.{8} AND a.{9} = b.{9} - """).format( DataUtil.createIndex( tableName=dicOfPrefixZoneLim[t], - fieldName=idZone[t], - isSpatial=False), - DataUtil.createIndex( tableName=dicOfTempoOutput[t], - fieldName=idZone[t], - isSpatial=False), - DataUtil.createIndex( tableName=dicOfPrefixZoneLim[t], - fieldName=ID_POINT_X, - isSpatial=False), - DataUtil.createIndex( tableName=dicOfTempoOutput[t], - fieldName=ID_POINT_X, - isSpatial=False), - dicOfOutputTables[t] , varToKeepPoint[t], - dicOfPrefixZoneLim[t] , dicOfTempoOutput[t], - idZone[t] , ID_POINT_X) - for t in listTabYvalues])) + """).format( + DataUtil.createIndex( + tableName=dicOfPrefixZoneLim[t], + fieldName=idZone[t], + isSpatial=False, + ), + DataUtil.createIndex( + tableName=dicOfTempoOutput[t], + fieldName=idZone[t], + isSpatial=False, + ), + DataUtil.createIndex( + tableName=dicOfPrefixZoneLim[t], + fieldName=ID_POINT_X, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=dicOfTempoOutput[t], + fieldName=ID_POINT_X, + isSpatial=False, + ), + dicOfOutputTables[t], + varToKeepPoint[t], + dicOfPrefixZoneLim[t], + dicOfTempoOutput[t], + idZone[t], + ID_POINT_X, + ) + for t in listTabYvalues + ] + ) + ) # Special treatment for rooftop corners which have not been calculated previously - cursor.execute(safe("""DROP TABLE IF EXISTS {rooftop_table}; + cursor.execute( + safe("""DROP TABLE IF EXISTS {rooftop_table}; CREATE TABLE {rooftop_table} AS SELECT {id_point}, ST_DISTANCE({geom}, GEOM_CORNER_POINT)/ @@ -725,41 +974,45 @@ def affectsPointToBuildZone(cursor, gridTable, dicOfBuildRockleZoneTable, CAST(ST_Y(GEOM_CORNER_POINT) AS INTEGER) AS {y_wall}, {id_point_x} FROM {tempo_table}""").format( - rooftop_table=dicOfOutputTables[ROOFTOP_CORN_NAME], - id_point=ID_POINT, - geom=GEOM_FIELD, - angle=UPWIND_FACADE_ANGLE_FIELD, - facade_len=ROOFTOP_CORNER_FACADE_LENGTH, - corner_len=ROOFTOP_CORNER_LENGTH, - var_height=ROOFTOP_CORNER_VAR_HEIGHT, - id_zone=idZone[ROOFTOP_PERP_NAME], - height=HEIGHT_FIELD, - wind_factor=ROOFTOP_WIND_FACTOR, - y_wall=Y_WALL, - id_point_x=ID_POINT_X, - tempo_table=dicOfTempoOutput[ROOFTOP_CORN_NAME] - )) - - + rooftop_table=dicOfOutputTables[ROOFTOP_CORN_NAME], + id_point=ID_POINT, + geom=GEOM_FIELD, + angle=UPWIND_FACADE_ANGLE_FIELD, + facade_len=ROOFTOP_CORNER_FACADE_LENGTH, + corner_len=ROOFTOP_CORNER_LENGTH, + var_height=ROOFTOP_CORNER_VAR_HEIGHT, + id_zone=idZone[ROOFTOP_PERP_NAME], + height=HEIGHT_FIELD, + wind_factor=ROOFTOP_WIND_FACTOR, + y_wall=Y_WALL, + id_point_x=ID_POINT_X, + tempo_table=dicOfTempoOutput[ROOFTOP_CORN_NAME], + ) + ) + if not DEBUG: # Remove intermediate tables - cursor.execute(""" + cursor.execute( + """ DROP TABLE IF EXISTS TEMPO_WAKE0, TEMPO_WAKE, {0},{1},{2} - """.format(",".join(list(dicOfTempoOutput.values())), - ",".join(list(dicOfPrefixZoneLim.values())), - tempoCavity)) - - + """.format( + ",".join(list(dicOfTempoOutput.values())), + ",".join(list(dicOfPrefixZoneLim.values())), + tempoCavity, + ) + ) + return dicOfOutputTables, verticalLineTable -def affectsPointToVegZone(cursor, gridTable, dicOfVegRockleZoneTable, - prefix = PREFIX_NAME): - """ Affects each point to a vegetation Rockle zone and calculates the +def affectsPointToVegZone( + cursor, gridTable, dicOfVegRockleZoneTable, prefix=PREFIX_NAME +): + """Affects each point to a vegetation Rockle zone and calculates the maximum vegetation height for each point. - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries @@ -770,27 +1023,37 @@ def affectsPointToVegZone(cursor, gridTable, dicOfVegRockleZoneTable, as value the corresponding vegetation table name prefix: String, default PREFIX_NAME Prefix to add to the output table name - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ dicOfOutputTables: dictionary of table name Dictionary having as key the type of vegetation Rockle zone and as value - the name of the table containing points corresponding to the vegetation zone""" - print("""Affects each grid point to a vegetation Rockle zone and calculates - needed variables for 3D wind speed""") - + the name of the table containing points corresponding to the vegetation zone + """ + print( + """Affects each grid point to a vegetation Rockle zone and calculates + needed variables for 3D wind speed""" + ) + # Name of the output tables - dicOfOutputTables = {t: DataUtil.postfix(tableName = DataUtil.prefix(tableName = t, - prefix = prefix), - suffix = "POINTS") for t in dicOfVegRockleZoneTable} - + dicOfOutputTables = { + t: DataUtil.postfix( + tableName=DataUtil.prefix(tableName=t, prefix=prefix), + suffix="POINTS", + ) + for t in dicOfVegRockleZoneTable + } + # Temporary tables (and prefix for temporary tables) maxHeightPointTable = "MAX_VEG_HEIGHT_POINT_" - + # Calculate the max of the canopy height for each point and then keep each # intersection between point and zone - cursor.execute(safe(";").join([safe(""" + cursor.execute( + safe(";").join( + [ + safe(""" {12}; {13}; DROP TABLE IF EXISTS {0}; @@ -805,53 +1068,63 @@ def affectsPointToVegZone(cursor, gridTable, dicOfVegRockleZoneTable, AS SELECT a.{1}, a.{2}, a.{7}, b.{4}, b.{8}, b.{9}, b.{10} FROM {0} AS a, {6} AS b WHERE a.{1} && b.{1} AND ST_INTERSECTS(a.{1}, b.{1}) - """).format( maxHeightPointTable+t, - GEOM_FIELD, - ID_POINT, - VEGETATION_CROWN_TOP_HEIGHT, - ID_VEGETATION, - gridTable, + """).format( + maxHeightPointTable + t, + GEOM_FIELD, + ID_POINT, + VEGETATION_CROWN_TOP_HEIGHT, + ID_VEGETATION, + gridTable, + dicOfVegRockleZoneTable[t], + TOP_CANOPY_HEIGHT_POINT, + VEGETATION_ATTENUATION_FACTOR, + VEGETATION_CROWN_BASE_HEIGHT, + VEGETATION_CROWN_TOP_HEIGHT, + dicOfOutputTables[t], + DataUtil.createIndex( + gridTable, fieldName=GEOM_FIELD, isSpatial=True + ), + DataUtil.createIndex( dicOfVegRockleZoneTable[t], - TOP_CANOPY_HEIGHT_POINT, - VEGETATION_ATTENUATION_FACTOR, - VEGETATION_CROWN_BASE_HEIGHT, - VEGETATION_CROWN_TOP_HEIGHT, - dicOfOutputTables[t], - DataUtil.createIndex(gridTable, - fieldName=GEOM_FIELD, - isSpatial=True), - DataUtil.createIndex(dicOfVegRockleZoneTable[t], - fieldName=GEOM_FIELD, - isSpatial=True), - DataUtil.createIndex(maxHeightPointTable+t, - fieldName=GEOM_FIELD, - isSpatial=True)) - for t in dicOfVegRockleZoneTable])) - + fieldName=GEOM_FIELD, + isSpatial=True, + ), + DataUtil.createIndex( + maxHeightPointTable + t, + fieldName=GEOM_FIELD, + isSpatial=True, + ), + ) + for t in dicOfVegRockleZoneTable + ] + ) + ) + if not DEBUG: # Remove intermediate tables cursor.execute(""" DROP TABLE IF EXISTS {0} - """.format(",".join([maxHeightPointTable]))) - + """.format(",".join([maxHeightPointTable]))) + return dicOfOutputTables -def removeBuildZonePoints(cursor, dicOfInitBuildZoneGridPoint, - prefix = PREFIX_NAME): - """ Remove some of the Röckle zone points when there are specific +def removeBuildZonePoints( + cursor, dicOfInitBuildZoneGridPoint, prefix=PREFIX_NAME +): + """Remove some of the Röckle zone points when there are specific zone overlapping. Currently, two major deletions are implemented: - 1. downwind building zone deletion: if a part of a building is entirely + 1. downwind building zone deletion: if a part of a building is entirely located within the vertical extend of an upward building cavity zone, its corresponding cavity, wake and street canyon zone are deleted - (only the cavity part corresponding to the part of building covered by + (only the cavity part corresponding to the part of building covered by an other cavity is deleted) 2. rooftop recirculation zone deletion: if the downward building of a street canyon is smaller or equal in size as the upward one, its rooftop recirculation zone is deleted. - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries @@ -860,21 +1133,25 @@ def removeBuildZonePoints(cursor, dicOfInitBuildZoneGridPoint, as value the corresponding points prefix: String, default PREFIX_NAME Prefix to add to the output table name - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ dicOfBuildZoneGridPoint: dictionary of table name Dictionary having as key the type of Rockle zone and as value - the name of the table containing updated points corresponding + the name of the table containing updated points corresponding to the zone""" print("""Remove some of the Röckle zone points""") - + # Name of the output tables - dicOfBuildZoneGridPoint = {t: DataUtil.postfix(tableName = DataUtil.prefix(tableName = t, - prefix = prefix), - suffix = "POINTS") for t in dicOfInitBuildZoneGridPoint} - + dicOfBuildZoneGridPoint = { + t: DataUtil.postfix( + tableName=DataUtil.prefix(tableName=t, prefix=prefix), + suffix="POINTS", + ) + for t in dicOfInitBuildZoneGridPoint + } + # Temporary tables (and prefix for temporary tables) cavityFirstPointCoord = DataUtil.postfix("CAVITY_FIRST_POINT_COORD") cavityFirstPoint = DataUtil.postfix("CAVITY_FIRST_POINT") @@ -885,63 +1162,93 @@ def removeBuildZonePoints(cursor, dicOfInitBuildZoneGridPoint, cavityWithoutDown = DataUtil.postfix("CAVITY_WITHOUT_DOWN") cavityPointsMinYwall = DataUtil.postfix("CAVITY_POINTS_MIN_YWALL") cavityFinalPoints = DataUtil.postfix("CAVITY_FINAL_POINTS") - dicPointsToRemoveStreetCanyon = {ROOFTOP_PERP_NAME: "TEMPO_POINTS_TO_REMOVE_"+ROOFTOP_PERP_NAME, - ROOFTOP_CORN_NAME: "TEMPO_POINTS_TO_REMOVE_"+ROOFTOP_CORN_NAME} - + dicPointsToRemoveStreetCanyon = { + ROOFTOP_PERP_NAME: "TEMPO_POINTS_TO_REMOVE_" + ROOFTOP_PERP_NAME, + ROOFTOP_CORN_NAME: "TEMPO_POINTS_TO_REMOVE_" + ROOFTOP_CORN_NAME, + } + # 1. IDENTIFY BUILDINGS PARTS (X POSITION) HAVING THEIR DOWNSTREAM FACADE INCLUDED WITHIN AN # UPSTREAM CAVITY ZONE # First identify the coordinate of the upstreamer point for each X coordinate to each cavity zone - cursor.execute(safe(""" + cursor.execute( + safe(""" {5}{6} DROP TABLE IF EXISTS {0}; CREATE TABLE {0} AS SELECT {1}, {2}, MAX({3}) AS {3} FROM {4} GROUP BY {1}, {2} - """).format(cavityFirstPointCoord , ID_POINT_X, - ID_FIELD_STACKED_BLOCK , ID_POINT_Y, - dicOfInitBuildZoneGridPoint[CAVITY_NAME], - DataUtil.createIndex(dicOfInitBuildZoneGridPoint[CAVITY_NAME], - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(dicOfInitBuildZoneGridPoint[CAVITY_NAME], - fieldName=ID_POINT_X, - isSpatial=False))) - + """).format( + cavityFirstPointCoord, + ID_POINT_X, + ID_FIELD_STACKED_BLOCK, + ID_POINT_Y, + dicOfInitBuildZoneGridPoint[CAVITY_NAME], + DataUtil.createIndex( + dicOfInitBuildZoneGridPoint[CAVITY_NAME], + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + dicOfInitBuildZoneGridPoint[CAVITY_NAME], + fieldName=ID_POINT_X, + isSpatial=False, + ), + ) + ) + # Then identify the maximum height of the cavity zone for each of these points - cursor.execute(safe(""" + cursor.execute( + safe(""" {9}{10}{11}{12}{13}{14} DROP TABLE IF EXISTS {0}; CREATE TABLE {0} AS SELECT b.{1}, b.{2}, b.{3}, b.{6}, b.{7} FROM {4} AS a LEFT JOIN {5} AS b ON a.{6} = b.{6} AND a.{8} = b.{8} - """).format(cavityFirstPoint , ID_POINT, - UPPER_VERTICAL_THRESHOLD , Y_WALL, - cavityFirstPointCoord , dicOfInitBuildZoneGridPoint[CAVITY_NAME], - ID_POINT_X , ID_FIELD_STACKED_BLOCK, - ID_POINT_Y , DataUtil.createIndex(dicOfInitBuildZoneGridPoint[CAVITY_NAME], - fieldName=ID_POINT_X, - isSpatial=False), - DataUtil.createIndex(dicOfInitBuildZoneGridPoint[CAVITY_NAME], - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(dicOfInitBuildZoneGridPoint[CAVITY_NAME], - fieldName=ID_POINT_Y, - isSpatial=False), - DataUtil.createIndex(cavityFirstPointCoord, - fieldName=ID_POINT_X, - isSpatial=False), - DataUtil.createIndex(cavityFirstPointCoord, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(cavityFirstPointCoord, - fieldName=ID_POINT_Y, - isSpatial=False))) - + """).format( + cavityFirstPoint, + ID_POINT, + UPPER_VERTICAL_THRESHOLD, + Y_WALL, + cavityFirstPointCoord, + dicOfInitBuildZoneGridPoint[CAVITY_NAME], + ID_POINT_X, + ID_FIELD_STACKED_BLOCK, + ID_POINT_Y, + DataUtil.createIndex( + dicOfInitBuildZoneGridPoint[CAVITY_NAME], + fieldName=ID_POINT_X, + isSpatial=False, + ), + DataUtil.createIndex( + dicOfInitBuildZoneGridPoint[CAVITY_NAME], + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + dicOfInitBuildZoneGridPoint[CAVITY_NAME], + fieldName=ID_POINT_Y, + isSpatial=False, + ), + DataUtil.createIndex( + cavityFirstPointCoord, fieldName=ID_POINT_X, isSpatial=False + ), + DataUtil.createIndex( + cavityFirstPointCoord, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + cavityFirstPointCoord, fieldName=ID_POINT_Y, isSpatial=False + ), + ) + ) + # Then identify potential relations between cavity zones (whether a cavity # zone is contained in an other) - cursor.execute(safe(""" + cursor.execute( + safe(""" {13}{14}{15}{16}{17} DROP TABLE IF EXISTS {0}; CREATE TABLE {0} @@ -950,30 +1257,49 @@ def removeBuildZonePoints(cursor, dicOfInitBuildZoneGridPoint, ON a.{7} = b.{7} WHERE a.{8} < b.{8} AND b.{9} > a.{9} + GREATEST({10}, {11}, {12}) GROUP BY b.{1}, b.{2}, a.{2} - """).format(cavityRelations , ID_POINT_X, - ID_FIELD_STACKED_BLOCK , ID_UPSTREAM_STACKED_BLOCK, - ID_DOWNSTREAM_STACKED_BLOCK , cavityFirstPoint, - dicOfInitBuildZoneGridPoint[CAVITY_NAME] , ID_POINT, - UPPER_VERTICAL_THRESHOLD , Y_WALL, - GEOMETRY_MERGE_TOLERANCE , SNAPPING_TOLERANCE, - GEOMETRY_SIMPLIFICATION_DISTANCE , DataUtil.createIndex(dicOfInitBuildZoneGridPoint[CAVITY_NAME], - fieldName=ID_POINT, - isSpatial=False), - DataUtil.createIndex(cavityFirstPoint, - fieldName=ID_POINT, - isSpatial=False), - DataUtil.createIndex(cavityFirstPoint, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(dicOfInitBuildZoneGridPoint[CAVITY_NAME], - fieldName=ID_POINT_X, - isSpatial=False), - DataUtil.createIndex(dicOfInitBuildZoneGridPoint[CAVITY_NAME], - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False))) + """).format( + cavityRelations, + ID_POINT_X, + ID_FIELD_STACKED_BLOCK, + ID_UPSTREAM_STACKED_BLOCK, + ID_DOWNSTREAM_STACKED_BLOCK, + cavityFirstPoint, + dicOfInitBuildZoneGridPoint[CAVITY_NAME], + ID_POINT, + UPPER_VERTICAL_THRESHOLD, + Y_WALL, + GEOMETRY_MERGE_TOLERANCE, + SNAPPING_TOLERANCE, + GEOMETRY_SIMPLIFICATION_DISTANCE, + DataUtil.createIndex( + dicOfInitBuildZoneGridPoint[CAVITY_NAME], + fieldName=ID_POINT, + isSpatial=False, + ), + DataUtil.createIndex( + cavityFirstPoint, fieldName=ID_POINT, isSpatial=False + ), + DataUtil.createIndex( + cavityFirstPoint, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + dicOfInitBuildZoneGridPoint[CAVITY_NAME], + fieldName=ID_POINT_X, + isSpatial=False, + ), + DataUtil.createIndex( + dicOfInitBuildZoneGridPoint[CAVITY_NAME], + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + ) + ) # Add all remaining cavity zones (having no other cavity zone contained in their cavity zone) - cursor.execute(safe(""" + cursor.execute( + safe(""" {7}{8}{9}{10} DROP TABLE IF EXISTS {0}; CREATE TABLE {0} @@ -984,25 +1310,37 @@ def removeBuildZonePoints(cursor, dicOfInitBuildZoneGridPoint, UNION ALL SELECT {1}, {3}, {4} FROM {6} - """).format(cavityRelationsAll , ID_POINT_X, - ID_FIELD_STACKED_BLOCK , ID_UPSTREAM_STACKED_BLOCK, - ID_DOWNSTREAM_STACKED_BLOCK , cavityFirstPoint, - cavityRelations , DataUtil.createIndex(cavityFirstPoint, - fieldName=ID_POINT_X, - isSpatial=False), - DataUtil.createIndex(cavityRelations, - fieldName=ID_POINT_X, - isSpatial=False), - DataUtil.createIndex(cavityFirstPoint, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(cavityRelations, - fieldName=ID_UPSTREAM_STACKED_BLOCK, - isSpatial=False))) - + """).format( + cavityRelationsAll, + ID_POINT_X, + ID_FIELD_STACKED_BLOCK, + ID_UPSTREAM_STACKED_BLOCK, + ID_DOWNSTREAM_STACKED_BLOCK, + cavityFirstPoint, + cavityRelations, + DataUtil.createIndex( + cavityFirstPoint, fieldName=ID_POINT_X, isSpatial=False + ), + DataUtil.createIndex( + cavityRelations, fieldName=ID_POINT_X, isSpatial=False + ), + DataUtil.createIndex( + cavityFirstPoint, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + cavityRelations, + fieldName=ID_UPSTREAM_STACKED_BLOCK, + isSpatial=False, + ), + ) + ) + # Identify cavity zones containing an other cavity BUT being also contained in an # upwind cavity - cursor.execute(safe(""" + cursor.execute( + safe(""" {5}{6}{7} DROP TABLE IF EXISTS {0}; CREATE TABLE {0} @@ -1011,22 +1349,32 @@ def removeBuildZonePoints(cursor, dicOfInitBuildZoneGridPoint, ON a.{1} = b.{4} WHERE b.{4} IS NOT NULL GROUP BY a.{1}, a.{2} - """).format(cavityUpAndDown , ID_DOWNSTREAM_STACKED_BLOCK, - ID_POINT_X , cavityRelations, - ID_UPSTREAM_STACKED_BLOCK , DataUtil.createIndex(cavityRelations, - fieldName=ID_POINT_X, - isSpatial=False), - DataUtil.createIndex(cavityRelations, - fieldName=ID_UPSTREAM_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(cavityRelations, - fieldName=ID_DOWNSTREAM_STACKED_BLOCK, - isSpatial=False))) - - - # Remove the previous identified cavity zones (ideally should be done with + """).format( + cavityUpAndDown, + ID_DOWNSTREAM_STACKED_BLOCK, + ID_POINT_X, + cavityRelations, + ID_UPSTREAM_STACKED_BLOCK, + DataUtil.createIndex( + cavityRelations, fieldName=ID_POINT_X, isSpatial=False + ), + DataUtil.createIndex( + cavityRelations, + fieldName=ID_UPSTREAM_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + cavityRelations, + fieldName=ID_DOWNSTREAM_STACKED_BLOCK, + isSpatial=False, + ), + ) + ) + + # Remove the previous identified cavity zones (ideally should be done with # a H2Network method since we do not take into account all cases...) - cursor.execute(safe(""" + cursor.execute( + safe(""" {6}{7}{8}{9} DROP TABLE IF EXISTS {0}; CREATE TABLE {0} @@ -1035,24 +1383,35 @@ def removeBuildZonePoints(cursor, dicOfInitBuildZoneGridPoint, ON a.{3} = b.{4} AND a.{5} = b.{5} WHERE b.{4} IS NULL GROUP BY a.{3}, a.{5}, a.{4} - """).format(cavityWithoutUpAndDown , cavityRelationsAll, - cavityUpAndDown , ID_UPSTREAM_STACKED_BLOCK, - ID_DOWNSTREAM_STACKED_BLOCK , ID_POINT_X, - DataUtil.createIndex(cavityUpAndDown, - fieldName=ID_DOWNSTREAM_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(cavityRelations, - fieldName=ID_UPSTREAM_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(cavityUpAndDown, - fieldName=ID_POINT_X, - isSpatial=False), - DataUtil.createIndex(cavityRelations, - fieldName=ID_POINT_X, - isSpatial=False))) + """).format( + cavityWithoutUpAndDown, + cavityRelationsAll, + cavityUpAndDown, + ID_UPSTREAM_STACKED_BLOCK, + ID_DOWNSTREAM_STACKED_BLOCK, + ID_POINT_X, + DataUtil.createIndex( + cavityUpAndDown, + fieldName=ID_DOWNSTREAM_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + cavityRelations, + fieldName=ID_UPSTREAM_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + cavityUpAndDown, fieldName=ID_POINT_X, isSpatial=False + ), + DataUtil.createIndex( + cavityRelations, fieldName=ID_POINT_X, isSpatial=False + ), + ) + ) # Remove the remaining cavity zones covered by an other cavity zone - cursor.execute(safe(""" + cursor.execute( + safe(""" {5}{6}{7} DROP TABLE IF EXISTS {0}; CREATE TABLE {0} @@ -1061,25 +1420,39 @@ def removeBuildZonePoints(cursor, dicOfInitBuildZoneGridPoint, ON a.{2} = b.{3} AND a.{4} = b.{4} WHERE b.{3} IS NULL GROUP BY a.{2}, a.{4} - """).format(cavityWithoutDown , cavityWithoutUpAndDown, - ID_UPSTREAM_STACKED_BLOCK , ID_DOWNSTREAM_STACKED_BLOCK, - ID_POINT_X , DataUtil.createIndex(cavityUpAndDown, - fieldName=ID_DOWNSTREAM_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(cavityWithoutUpAndDown, - fieldName=ID_UPSTREAM_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(cavityUpAndDown, - fieldName=ID_POINT_X, - isSpatial=False))) - - #2. REMOVE ANY ZONE CORRESPONDING TO THESE PARTS OF BUILDINGS - cavityJoinFields = {STREET_CANYON_NAME: [ID_UPSTREAM_STACKED_BLOCK, ID_POINT_X], - WAKE_NAME: [ID_FIELD_STACKED_BLOCK, ID_POINT_X], - CAVITY_NAME: [ID_UPSTREAM_STACKED_BLOCK, ID_POINT_X]} + """).format( + cavityWithoutDown, + cavityWithoutUpAndDown, + ID_UPSTREAM_STACKED_BLOCK, + ID_DOWNSTREAM_STACKED_BLOCK, + ID_POINT_X, + DataUtil.createIndex( + cavityUpAndDown, + fieldName=ID_DOWNSTREAM_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + cavityWithoutUpAndDown, + fieldName=ID_UPSTREAM_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + cavityUpAndDown, fieldName=ID_POINT_X, isSpatial=False + ), + ) + ) + + # 2. REMOVE ANY ZONE CORRESPONDING TO THESE PARTS OF BUILDINGS + cavityJoinFields = { + STREET_CANYON_NAME: [ID_UPSTREAM_STACKED_BLOCK, ID_POINT_X], + WAKE_NAME: [ID_FIELD_STACKED_BLOCK, ID_POINT_X], + CAVITY_NAME: [ID_UPSTREAM_STACKED_BLOCK, ID_POINT_X], + } # Take all points from a 't' zone which have not been deleted by a cavity zone - cursor.execute(safe(";").join( - [safe(""" + cursor.execute( + safe(";").join( + [ + safe(""" {6}{7}{8}{9} DROP TABLE IF EXISTS {0}; CREATE TABLE {0} @@ -1087,41 +1460,66 @@ def removeBuildZonePoints(cursor, dicOfInitBuildZoneGridPoint, FROM {1} AS a RIGHT JOIN {2} AS b ON a.{5} = b.{4} AND a.{3} = b.{3} WHERE a.{10} IS NOT NULL - """).format(dicOfBuildZoneGridPoint[t] , dicOfInitBuildZoneGridPoint[t], - cavityWithoutDown , ID_POINT_X, - ID_UPSTREAM_STACKED_BLOCK , cavityJoinFields[t][0], - DataUtil.createIndex(cavityWithoutDown, - fieldName=ID_UPSTREAM_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(dicOfInitBuildZoneGridPoint[t], - fieldName=cavityJoinFields[t][0], - isSpatial=False), - DataUtil.createIndex(cavityWithoutDown, - fieldName=ID_POINT_X, - isSpatial=False), - DataUtil.createIndex(dicOfInitBuildZoneGridPoint[t], - fieldName=cavityJoinFields[t][1], - isSpatial=False), - ID_POINT) - for t in cavityJoinFields.keys()])) + """).format( + dicOfBuildZoneGridPoint[t], + dicOfInitBuildZoneGridPoint[t], + cavityWithoutDown, + ID_POINT_X, + ID_UPSTREAM_STACKED_BLOCK, + cavityJoinFields[t][0], + DataUtil.createIndex( + cavityWithoutDown, + fieldName=ID_UPSTREAM_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + dicOfInitBuildZoneGridPoint[t], + fieldName=cavityJoinFields[t][0], + isSpatial=False, + ), + DataUtil.createIndex( + cavityWithoutDown, + fieldName=ID_POINT_X, + isSpatial=False, + ), + DataUtil.createIndex( + dicOfInitBuildZoneGridPoint[t], + fieldName=cavityJoinFields[t][1], + isSpatial=False, + ), + ID_POINT, + ) + for t in cavityJoinFields.keys() + ] + ) + ) # For each point, several zones may overlay, we need to identify those # coming from the more downstream one (y_wall min) - cursor.execute(safe(""" + cursor.execute( + safe(""" {4} DROP TABLE IF EXISTS {0}; CREATE TABLE {0} AS SELECT {1}, MIN({2}) AS {2} FROM {3} GROUP BY {1} - """).format(cavityPointsMinYwall , ID_POINT, - Y_WALL , dicOfBuildZoneGridPoint[CAVITY_NAME], - DataUtil.createIndex(dicOfBuildZoneGridPoint[CAVITY_NAME], - fieldName=ID_POINT, - isSpatial=False))) + """).format( + cavityPointsMinYwall, + ID_POINT, + Y_WALL, + dicOfBuildZoneGridPoint[CAVITY_NAME], + DataUtil.createIndex( + dicOfBuildZoneGridPoint[CAVITY_NAME], + fieldName=ID_POINT, + isSpatial=False, + ), + ) + ) # At the end only one point per position is conserved, the points from the more downstream zone - cursor.execute(safe(""" + cursor.execute( + safe(""" {5}{6}{7}{8} DROP TABLE IF EXISTS {0}; CREATE TABLE {0} @@ -1130,29 +1528,43 @@ def removeBuildZonePoints(cursor, dicOfInitBuildZoneGridPoint, ON a.{3} = b.{3} AND a.{4} = b.{4}; DROP TABLE IF EXISTS {1}; ALTER TABLE {0} RENAME TO {1}; - """).format(cavityFinalPoints , dicOfBuildZoneGridPoint[CAVITY_NAME], - cavityPointsMinYwall , ID_POINT, - Y_WALL , DataUtil.createIndex(dicOfBuildZoneGridPoint[CAVITY_NAME], - fieldName=ID_POINT, - isSpatial=False), - DataUtil.createIndex(cavityPointsMinYwall, - fieldName=ID_POINT, - isSpatial=False), - DataUtil.createIndex(dicOfBuildZoneGridPoint[CAVITY_NAME], - fieldName=Y_WALL, - isSpatial=False), - DataUtil.createIndex(cavityPointsMinYwall, - fieldName=Y_WALL, - isSpatial=False))) - + """).format( + cavityFinalPoints, + dicOfBuildZoneGridPoint[CAVITY_NAME], + cavityPointsMinYwall, + ID_POINT, + Y_WALL, + DataUtil.createIndex( + dicOfBuildZoneGridPoint[CAVITY_NAME], + fieldName=ID_POINT, + isSpatial=False, + ), + DataUtil.createIndex( + cavityPointsMinYwall, fieldName=ID_POINT, isSpatial=False + ), + DataUtil.createIndex( + dicOfBuildZoneGridPoint[CAVITY_NAME], + fieldName=Y_WALL, + isSpatial=False, + ), + DataUtil.createIndex( + cavityPointsMinYwall, fieldName=Y_WALL, isSpatial=False + ), + ) + ) + # 3. REMOVE ROOFTOP ZONES WHICH ARE DOWNSTREAM A CANYON IF THE CANYON - # IS AS HIGH AS THE BUILDING WHERE THE ROOFTOP ZONE SHOULD APPEAR + # IS AS HIGH AS THE BUILDING WHERE THE ROOFTOP ZONE SHOULD APPEAR # (note that street canyon have been previously updated) - streetCanyonJoinFields = {ROOFTOP_PERP_NAME: [UPWIND_FACADE_FIELD, ID_POINT_X], - ROOFTOP_CORN_NAME: [UPWIND_FACADE_FIELD, ID_POINT_X]} + streetCanyonJoinFields = { + ROOFTOP_PERP_NAME: [UPWIND_FACADE_FIELD, ID_POINT_X], + ROOFTOP_CORN_NAME: [UPWIND_FACADE_FIELD, ID_POINT_X], + } # Identify the rooftop zones to remove - cursor.execute(safe(";").join( - [safe(""" + cursor.execute( + safe(";").join( + [ + safe(""" {8}; {9}; {10}; @@ -1163,27 +1575,46 @@ def removeBuildZonePoints(cursor, dicOfInitBuildZoneGridPoint, FROM {4} AS a RIGHT JOIN {5} AS b ON a.{2} = b.{2} AND a.{3} = b.{3} WHERE b.{6} <= a.{7} GROUP BY b.{3}, b.{2} - """).format( dicPointsToRemoveStreetCanyon[t] , ID_POINT, - UPWIND_FACADE_FIELD , ID_POINT_X, - dicOfBuildZoneGridPoint[STREET_CANYON_NAME] , dicOfInitBuildZoneGridPoint[t], - HEIGHT_FIELD , MAX_CANYON_HEIGHT_FIELD, - DataUtil.createIndex(dicOfBuildZoneGridPoint[STREET_CANYON_NAME], - fieldName=UPWIND_FACADE_FIELD, - isSpatial=False), - DataUtil.createIndex(dicOfBuildZoneGridPoint[STREET_CANYON_NAME], - fieldName=ID_POINT_X, - isSpatial=False), - DataUtil.createIndex(dicOfInitBuildZoneGridPoint[t], - fieldName=UPWIND_FACADE_FIELD, - isSpatial=False), - DataUtil.createIndex(dicOfInitBuildZoneGridPoint[t], - fieldName=ID_POINT_X, - isSpatial=False)) - for t in streetCanyonJoinFields.keys()])) + """).format( + dicPointsToRemoveStreetCanyon[t], + ID_POINT, + UPWIND_FACADE_FIELD, + ID_POINT_X, + dicOfBuildZoneGridPoint[STREET_CANYON_NAME], + dicOfInitBuildZoneGridPoint[t], + HEIGHT_FIELD, + MAX_CANYON_HEIGHT_FIELD, + DataUtil.createIndex( + dicOfBuildZoneGridPoint[STREET_CANYON_NAME], + fieldName=UPWIND_FACADE_FIELD, + isSpatial=False, + ), + DataUtil.createIndex( + dicOfBuildZoneGridPoint[STREET_CANYON_NAME], + fieldName=ID_POINT_X, + isSpatial=False, + ), + DataUtil.createIndex( + dicOfInitBuildZoneGridPoint[t], + fieldName=UPWIND_FACADE_FIELD, + isSpatial=False, + ), + DataUtil.createIndex( + dicOfInitBuildZoneGridPoint[t], + fieldName=ID_POINT_X, + isSpatial=False, + ), + ) + for t in streetCanyonJoinFields.keys() + ] + ) + ) # Remove points in rooftop zones - cursor.execute(safe(";").join( - [safe(""" + cursor.execute( + safe(";").join( + [ + safe(""" {5}; {6}; {7}; @@ -1193,62 +1624,100 @@ def removeBuildZonePoints(cursor, dicOfInitBuildZoneGridPoint, AS SELECT a.* FROM {1} AS a LEFT JOIN {2} AS b ON a.{3} = b.{3} AND a.{4} = b.{4} WHERE b.{3} IS NULL AND b.{4} IS NULL - """).format( dicOfBuildZoneGridPoint[t] , dicOfInitBuildZoneGridPoint[t], - dicPointsToRemoveStreetCanyon[t] , streetCanyonJoinFields[t][0], - streetCanyonJoinFields[t][1] , DataUtil.createIndex( dicOfInitBuildZoneGridPoint[t], - fieldName=streetCanyonJoinFields[t][0], - isSpatial=False), - DataUtil.createIndex(dicOfInitBuildZoneGridPoint[t], - fieldName=streetCanyonJoinFields[t][1], - isSpatial=False), - DataUtil.createIndex(dicPointsToRemoveStreetCanyon[t], - fieldName=streetCanyonJoinFields[t][0], - isSpatial=False), - DataUtil.createIndex(dicPointsToRemoveStreetCanyon[t], - fieldName=streetCanyonJoinFields[t][1], - isSpatial=False)) - for t in streetCanyonJoinFields.keys()])) - - + """).format( + dicOfBuildZoneGridPoint[t], + dicOfInitBuildZoneGridPoint[t], + dicPointsToRemoveStreetCanyon[t], + streetCanyonJoinFields[t][0], + streetCanyonJoinFields[t][1], + DataUtil.createIndex( + dicOfInitBuildZoneGridPoint[t], + fieldName=streetCanyonJoinFields[t][0], + isSpatial=False, + ), + DataUtil.createIndex( + dicOfInitBuildZoneGridPoint[t], + fieldName=streetCanyonJoinFields[t][1], + isSpatial=False, + ), + DataUtil.createIndex( + dicPointsToRemoveStreetCanyon[t], + fieldName=streetCanyonJoinFields[t][0], + isSpatial=False, + ), + DataUtil.createIndex( + dicPointsToRemoveStreetCanyon[t], + fieldName=streetCanyonJoinFields[t][1], + isSpatial=False, + ), + ) + for t in streetCanyonJoinFields.keys() + ] + ) + ) + # Rename tables which has not been modified to the "updated" name - nonModifiedTables = set(dicOfInitBuildZoneGridPoint.keys())\ - -set(cavityJoinFields.keys())\ - -set(streetCanyonJoinFields.keys()) - cursor.execute(safe(";").join( - [safe(""" + nonModifiedTables = ( + set(dicOfInitBuildZoneGridPoint.keys()) + - set(cavityJoinFields.keys()) + - set(streetCanyonJoinFields.keys()) + ) + cursor.execute( + safe(";").join( + [ + safe(""" DROP TABLE IF EXISTS {0}; ALTER TABLE {1} RENAME TO {0} - """).format( dicOfBuildZoneGridPoint[t], - dicOfInitBuildZoneGridPoint[t]) - for t in nonModifiedTables])) + """).format( + dicOfBuildZoneGridPoint[t], dicOfInitBuildZoneGridPoint[t] + ) + for t in nonModifiedTables + ] + ) + ) if not DEBUG: # Remove intermediate tables - listToRemove = list(dicPointsToRemoveStreetCanyon.values())+\ - [cavityFirstPointCoord, cavityFirstPoint, cavityRelations, - cavityRelationsAll, cavityUpAndDown, cavityWithoutUpAndDown, - cavityWithoutDown, cavityPointsMinYwall, cavityFinalPoints] + listToRemove = list(dicPointsToRemoveStreetCanyon.values()) + [ + cavityFirstPointCoord, + cavityFirstPoint, + cavityRelations, + cavityRelationsAll, + cavityUpAndDown, + cavityWithoutUpAndDown, + cavityWithoutDown, + cavityPointsMinYwall, + cavityFinalPoints, + ] cursor.execute(""" DROP TABLE IF EXISTS {0} """.format(",".join(listToRemove))) - + return dicOfBuildZoneGridPoint -def manageBackwardZones(cursor, dicOfBuildZoneGridPoint, cavity2dInitPoints, - wake2dInitPoints, streetCanyonTable, gridTable, - prefix, meshSize = MESH_SIZE, dz = DZ): - """ A building having a horizontal piece its upwind facade entirely (vertically) +def manageBackwardZones( + cursor, + dicOfBuildZoneGridPoint, + cavity2dInitPoints, + wake2dInitPoints, + streetCanyonTable, + gridTable, + prefix, + meshSize=MESH_SIZE, + dz=DZ, +): + """A building having a horizontal piece its upwind facade entirely (vertically) located within the cavity zone of an upwind taller building will create: - -> a backward zone system within the 2 buildings (ie. the downwind building - cavity and wake zones will go + -> a backward zone system within the 2 buildings (ie. the downwind building + cavity and wake zones will go upstream - also downstream - if the building not entirely + if the building not entirely in the cavity zone of upstream building), -> the removal of the street canyon at these locations (upstream the facade within cavity) -> the removal of the rooftop zones at these locations (downstream the facade within cavity) - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries @@ -1266,47 +1735,54 @@ def manageBackwardZones(cursor, dicOfBuildZoneGridPoint, cavity2dInitPoints, prefix: String, default PREFIX_NAME Prefix to add to the output table name meshSize: float, default MESH_SIZE - Resolution (in meter) of the grid + Resolution (in meter) of the grid dz: float, default DZ Resolution (in meter) of the grid in the vertical direction - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ dicOfBuildZoneGridPoint: dictionary of table name Dictionary having as key the type of Rockle zone and as value - the name of the table containing updated street canyon zones and + the name of the table containing updated street canyon zones and new backward zone points facadeWithinCavity: string Name of the table containing the "facade" ID_POINT and height - which will be useful for assigning a weighting to backward zones""" + which will be useful for assigning a weighting to backward zones + """ print("""Creates backward zones""") - + # Name of the output tables tables2calculate = [CAVITY_BACKWARD_NAME, WAKE_BACKWARD_NAME] for t in tables2calculate: - dicOfBuildZoneGridPoint[t] = DataUtil.postfix(tableName = DataUtil.prefix(tableName = t, - prefix = prefix), - suffix = "POINTS") - facadeWithinCavity = DataUtil.prefix(tableName = "FACADE_WITHIN_CAVITY", - prefix = prefix) - + dicOfBuildZoneGridPoint[t] = DataUtil.postfix( + tableName=DataUtil.prefix(tableName=t, prefix=prefix), + suffix="POINTS", + ) + facadeWithinCavity = DataUtil.prefix( + tableName="FACADE_WITHIN_CAVITY", prefix=prefix + ) + # Temporary tables (and prefix for temporary tables) canyonLastPointCoord = DataUtil.postfix("CANYON_LAST_POINT_COORD") impactedStackedBlocs = DataUtil.postfix("IMPACTED_STACKED_BLOCKS") rooftop_tables = [ROOFTOP_PERP_NAME, ROOFTOP_CORN_NAME] tempoZoneTables = tables2calculate + rooftop_tables + [STREET_CANYON_NAME] - dicOfTempoBackPoints = {t: DataUtil.postfix(tableName = DataUtil.prefix(tableName = t, - prefix = "TEMPO"), - suffix = "POINTS") \ - for t in tempoZoneTables} - - # 1. IDENTIFY BUILDINGS PARTS (X POSITION) HAVING THEIR UPSTREAM FACADE + dicOfTempoBackPoints = { + t: DataUtil.postfix( + tableName=DataUtil.prefix(tableName=t, prefix="TEMPO"), + suffix="POINTS", + ) + for t in tempoZoneTables + } + + # 1. IDENTIFY BUILDINGS PARTS (X POSITION) HAVING THEIR UPSTREAM FACADE # INCLUDED WITHIN AN UPSTREAM CAVITY ZONE AND CREATE BACKWARD ZONES - # First identify the coordinate of the downstreamer point for each X + # First identify the coordinate of the downstreamer point for each X # coordinate of each street canyon zone and keep only those having the # cavity zone being higher than the downwind building height - cursor.execute(safe(""" + cursor.execute( + safe(""" {6}{7} DROP TABLE IF EXISTS {0}; CREATE TABLE {0} @@ -1321,51 +1797,84 @@ def manageBackwardZones(cursor, dicOfBuildZoneGridPoint, cavity2dInitPoints, FROM {0} AS a LEFT JOIN {5} AS b ON a.{1} = b.{1} AND a.{3} = b.{3} AND a.{4} = b.{4} WHERE b.{15} > b.{16} - """).format(canyonLastPointCoord , ID_POINT_X, - ID_FIELD_STACKED_BLOCK , ID_FIELD_CANYON, - ID_POINT_Y , dicOfBuildZoneGridPoint[STREET_CANYON_NAME], - DataUtil.createIndex(dicOfBuildZoneGridPoint[STREET_CANYON_NAME], - fieldName=ID_POINT_X, - isSpatial=False), - DataUtil.createIndex(dicOfBuildZoneGridPoint[STREET_CANYON_NAME], - fieldName=ID_FIELD_CANYON, - isSpatial=False), - DataUtil.createIndex(canyonLastPointCoord, - fieldName=ID_POINT_X, - isSpatial=False), - DataUtil.createIndex(canyonLastPointCoord, - fieldName=ID_POINT_Y, - isSpatial=False), - DataUtil.createIndex(canyonLastPointCoord, - fieldName=ID_FIELD_CANYON, - isSpatial=False), - DataUtil.createIndex(dicOfBuildZoneGridPoint[STREET_CANYON_NAME], - fieldName=ID_POINT_X, - isSpatial=False), - DataUtil.createIndex(dicOfBuildZoneGridPoint[STREET_CANYON_NAME], - fieldName=ID_POINT_Y, - isSpatial=False), - DataUtil.createIndex(dicOfBuildZoneGridPoint[STREET_CANYON_NAME], - fieldName=ID_FIELD_CANYON, - isSpatial=False), - facadeWithinCavity , UPPER_VERTICAL_THRESHOLD, - MAX_CANYON_HEIGHT_FIELD , Y_WALL, - LENGTH_ZONE_FIELD+STREET_CANYON_NAME[0], - UPWIND_FACADE_FIELD , ID_POINT, - dz , ID_POINT_Z)) - + """).format( + canyonLastPointCoord, + ID_POINT_X, + ID_FIELD_STACKED_BLOCK, + ID_FIELD_CANYON, + ID_POINT_Y, + dicOfBuildZoneGridPoint[STREET_CANYON_NAME], + DataUtil.createIndex( + dicOfBuildZoneGridPoint[STREET_CANYON_NAME], + fieldName=ID_POINT_X, + isSpatial=False, + ), + DataUtil.createIndex( + dicOfBuildZoneGridPoint[STREET_CANYON_NAME], + fieldName=ID_FIELD_CANYON, + isSpatial=False, + ), + DataUtil.createIndex( + canyonLastPointCoord, fieldName=ID_POINT_X, isSpatial=False + ), + DataUtil.createIndex( + canyonLastPointCoord, fieldName=ID_POINT_Y, isSpatial=False + ), + DataUtil.createIndex( + canyonLastPointCoord, + fieldName=ID_FIELD_CANYON, + isSpatial=False, + ), + DataUtil.createIndex( + dicOfBuildZoneGridPoint[STREET_CANYON_NAME], + fieldName=ID_POINT_X, + isSpatial=False, + ), + DataUtil.createIndex( + dicOfBuildZoneGridPoint[STREET_CANYON_NAME], + fieldName=ID_POINT_Y, + isSpatial=False, + ), + DataUtil.createIndex( + dicOfBuildZoneGridPoint[STREET_CANYON_NAME], + fieldName=ID_FIELD_CANYON, + isSpatial=False, + ), + facadeWithinCavity, + UPPER_VERTICAL_THRESHOLD, + MAX_CANYON_HEIGHT_FIELD, + Y_WALL, + LENGTH_ZONE_FIELD + STREET_CANYON_NAME[0], + UPWIND_FACADE_FIELD, + ID_POINT, + dz, + ID_POINT_Z, + ) + ) + # Then get the stacked blocks concerned by the backward cavity and wake zones # and revert the cavity and wake wind speed factors from downwind to upwind stacked block # and finally join the ID_POINT of each of the ID_X / ID_Y point - var2keepSpe = {CAVITY_BACKWARD_NAME: """a.{0}, a.{1} - """.format(LENGTH_ZONE_FIELD + CAVITY_NAME[0], - POINT_RELATIVE_POSITION_FIELD + CAVITY_NAME[0]), - WAKE_BACKWARD_NAME: """a.{0}, a.{1} - """.format(WAKE_RELATIVE_POSITION_FIELD, - UPPER_VERTICAL_THRESHOLD + CAVITY_NAME[0])} - tab2revert = {CAVITY_BACKWARD_NAME: cavity2dInitPoints, - WAKE_BACKWARD_NAME: wake2dInitPoints} - cursor.execute(safe(";").join([safe(""" + var2keepSpe = { + CAVITY_BACKWARD_NAME: """a.{0}, a.{1} + """.format( + LENGTH_ZONE_FIELD + CAVITY_NAME[0], + POINT_RELATIVE_POSITION_FIELD + CAVITY_NAME[0], + ), + WAKE_BACKWARD_NAME: """a.{0}, a.{1} + """.format( + WAKE_RELATIVE_POSITION_FIELD, + UPPER_VERTICAL_THRESHOLD + CAVITY_NAME[0], + ), + } + tab2revert = { + CAVITY_BACKWARD_NAME: cavity2dInitPoints, + WAKE_BACKWARD_NAME: wake2dInitPoints, + } + cursor.execute( + safe(";").join( + [ + safe(""" {0}{1} DROP TABLE IF EXISTS {2}; CREATE TABLE {2} @@ -1386,52 +1895,82 @@ def manageBackwardZones(cursor, dicOfBuildZoneGridPoint, cavity2dInitPoints, AS SELECT a.*, b.{28} FROM {16} AS a LEFT JOIN {29} AS b ON a.{3} = b.{3} AND a.{5} = b.{5} - """).format( DataUtil.createIndex(facadeWithinCavity, - fieldName=ID_FIELD_CANYON, - isSpatial=False), - DataUtil.createIndex(streetCanyonTable, - fieldName=ID_FIELD_CANYON, - isSpatial=False), - impactedStackedBlocs , ID_POINT_X, - ID_FIELD_STACKED_BLOCK , ID_POINT_Y, - Y_WALL , ID_DOWNSTREAM_STACKED_BLOCK, - LENGTH_ZONE_FIELD + STREET_CANYON_NAME[0] , facadeWithinCavity, - streetCanyonTable , ID_FIELD_CANYON, - DataUtil.createIndex(tab2revert[t], - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(tab2revert[t], - fieldName=ID_POINT_X, - isSpatial=False), - DataUtil.createIndex(impactedStackedBlocs, - fieldName=ID_DOWNSTREAM_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(impactedStackedBlocs, - fieldName=ID_POINT_X, - isSpatial=False), - dicOfTempoBackPoints[t] , UPPER_VERTICAL_THRESHOLD, - var2keepSpe[t] , DISTANCE_BUILD_TO_POINT_FIELD, - HEIGHT_FIELD , meshSize, - tab2revert[t], - DataUtil.createIndex(dicOfTempoBackPoints[t], - fieldName=ID_POINT_X, - isSpatial=False), - DataUtil.createIndex(dicOfTempoBackPoints[t], - fieldName=ID_POINT_Y, - isSpatial=False), - DataUtil.createIndex(gridTable, - fieldName=ID_POINT_X, - isSpatial=False), - DataUtil.createIndex(gridTable, - fieldName=ID_POINT_Y, - isSpatial=False), - dicOfBuildZoneGridPoint[t] , ID_POINT, - gridTable , UPWIND_FACADE_FIELD) - for t in tables2calculate])) - + """).format( + DataUtil.createIndex( + facadeWithinCavity, + fieldName=ID_FIELD_CANYON, + isSpatial=False, + ), + DataUtil.createIndex( + streetCanyonTable, + fieldName=ID_FIELD_CANYON, + isSpatial=False, + ), + impactedStackedBlocs, + ID_POINT_X, + ID_FIELD_STACKED_BLOCK, + ID_POINT_Y, + Y_WALL, + ID_DOWNSTREAM_STACKED_BLOCK, + LENGTH_ZONE_FIELD + STREET_CANYON_NAME[0], + facadeWithinCavity, + streetCanyonTable, + ID_FIELD_CANYON, + DataUtil.createIndex( + tab2revert[t], + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + tab2revert[t], fieldName=ID_POINT_X, isSpatial=False + ), + DataUtil.createIndex( + impactedStackedBlocs, + fieldName=ID_DOWNSTREAM_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + impactedStackedBlocs, + fieldName=ID_POINT_X, + isSpatial=False, + ), + dicOfTempoBackPoints[t], + UPPER_VERTICAL_THRESHOLD, + var2keepSpe[t], + DISTANCE_BUILD_TO_POINT_FIELD, + HEIGHT_FIELD, + meshSize, + tab2revert[t], + DataUtil.createIndex( + dicOfTempoBackPoints[t], + fieldName=ID_POINT_X, + isSpatial=False, + ), + DataUtil.createIndex( + dicOfTempoBackPoints[t], + fieldName=ID_POINT_Y, + isSpatial=False, + ), + DataUtil.createIndex( + gridTable, fieldName=ID_POINT_X, isSpatial=False + ), + DataUtil.createIndex( + gridTable, fieldName=ID_POINT_Y, isSpatial=False + ), + dicOfBuildZoneGridPoint[t], + ID_POINT, + gridTable, + UPWIND_FACADE_FIELD, + ) + for t in tables2calculate + ] + ) + ) + # 2. REMOVE STREET CANYON POINTS WHERE THE DOWNWIND FACADE OF THE CANYON IS # ENTIRELY IN THE CAVITY ZONE OF THE UPSTREAM BUILDING - cursor.execute(safe(""" + cursor.execute( + safe(""" {0}{1}{2}{3} DROP TABLE IF EXISTS {4}; CREATE TABLE {4} @@ -1441,25 +1980,37 @@ def manageBackwardZones(cursor, dicOfBuildZoneGridPoint, cavity2dInitPoints, WHERE b.{7} IS NULL AND b.{8} IS NULL; DROP TABLE IF EXISTS {5}; ALTER TABLE {4} RENAME TO {5}; - """).format( DataUtil.createIndex(facadeWithinCavity, - fieldName=ID_FIELD_CANYON, - isSpatial=False), - DataUtil.createIndex(facadeWithinCavity, - fieldName=ID_POINT_X, - isSpatial = False), - DataUtil.createIndex(dicOfBuildZoneGridPoint[STREET_CANYON_NAME], - fieldName=ID_FIELD_CANYON, - isSpatial = False), - DataUtil.createIndex(dicOfBuildZoneGridPoint[STREET_CANYON_NAME], - fieldName=ID_POINT_X, - isSpatial = False), - dicOfTempoBackPoints[STREET_CANYON_NAME] , dicOfBuildZoneGridPoint[STREET_CANYON_NAME], - facadeWithinCavity , ID_FIELD_CANYON, - ID_POINT_X)) - + """).format( + DataUtil.createIndex( + facadeWithinCavity, fieldName=ID_FIELD_CANYON, isSpatial=False + ), + DataUtil.createIndex( + facadeWithinCavity, fieldName=ID_POINT_X, isSpatial=False + ), + DataUtil.createIndex( + dicOfBuildZoneGridPoint[STREET_CANYON_NAME], + fieldName=ID_FIELD_CANYON, + isSpatial=False, + ), + DataUtil.createIndex( + dicOfBuildZoneGridPoint[STREET_CANYON_NAME], + fieldName=ID_POINT_X, + isSpatial=False, + ), + dicOfTempoBackPoints[STREET_CANYON_NAME], + dicOfBuildZoneGridPoint[STREET_CANYON_NAME], + facadeWithinCavity, + ID_FIELD_CANYON, + ID_POINT_X, + ) + ) + # 3. REMOVE ROOFTOP POINTS WHERE THE DOWNWIND FACADE OF THE CANYON IS # ENTIRELY IN THE CAVITY ZONE OF THE UPSTREAM BUILDING - cursor.execute(safe(";").join([safe(""" + cursor.execute( + safe(";").join( + [ + safe(""" {0}{1}{2}{3} DROP TABLE IF EXISTS {4}; CREATE TABLE {4} @@ -1469,124 +2020,171 @@ def manageBackwardZones(cursor, dicOfBuildZoneGridPoint, cavity2dInitPoints, WHERE b.{7} IS NULL AND b.{8} IS NULL; DROP TABLE IF EXISTS {5}; ALTER TABLE {4} RENAME TO {5}; - """).format( DataUtil.createIndex(facadeWithinCavity, - fieldName=UPWIND_FACADE_FIELD, - isSpatial=False), - DataUtil.createIndex(facadeWithinCavity, - fieldName=ID_POINT_X, - isSpatial = False), - DataUtil.createIndex(dicOfBuildZoneGridPoint[t], - fieldName=UPWIND_FACADE_FIELD, - isSpatial = False), - DataUtil.createIndex(dicOfBuildZoneGridPoint[t], - fieldName=ID_POINT_X, - isSpatial = False), - dicOfTempoBackPoints[t] , dicOfBuildZoneGridPoint[t], - facadeWithinCavity , UPWIND_FACADE_FIELD, - ID_POINT_X) for t in rooftop_tables])) - + """).format( + DataUtil.createIndex( + facadeWithinCavity, + fieldName=UPWIND_FACADE_FIELD, + isSpatial=False, + ), + DataUtil.createIndex( + facadeWithinCavity, + fieldName=ID_POINT_X, + isSpatial=False, + ), + DataUtil.createIndex( + dicOfBuildZoneGridPoint[t], + fieldName=UPWIND_FACADE_FIELD, + isSpatial=False, + ), + DataUtil.createIndex( + dicOfBuildZoneGridPoint[t], + fieldName=ID_POINT_X, + isSpatial=False, + ), + dicOfTempoBackPoints[t], + dicOfBuildZoneGridPoint[t], + facadeWithinCavity, + UPWIND_FACADE_FIELD, + ID_POINT_X, + ) + for t in rooftop_tables + ] + ) + ) + if not DEBUG: # Remove intermediate tables - listToRemove = list(dicOfTempoBackPoints.values())+\ - [canyonLastPointCoord, impactedStackedBlocs] + listToRemove = list(dicOfTempoBackPoints.values()) + [ + canyonLastPointCoord, + impactedStackedBlocs, + ] cursor.execute(safe(""" DROP TABLE IF EXISTS {0} """).format(",".join(listToRemove))) - + return dicOfBuildZoneGridPoint, facadeWithinCavity -def calculates3dBuildWindFactor(cursor, dicOfBuildZoneGridPoint, - dz = DZ, prefix = PREFIX_NAME): - """ Calculates the 3D wind speed factors for each building zone. +def calculates3dBuildWindFactor( + cursor, dicOfBuildZoneGridPoint, dz=DZ, prefix=PREFIX_NAME +): + """Calculates the 3D wind speed factors for each building zone. - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ - cursor: conn.cursor - A cursor object, used to perform spatial SQL queries - dicOfBuildZoneGridPoint: Dictionary of Rockle zone tables - Dictionary having as key the type of Rockle zone and as value - the name of the table containing points corresponding to the zone - dz: float, default DZ - Resolution (in meter) of the grid in the vertical direction - prefix: String, default PREFIX_NAME - Prefix to add to the output table name - - Returns - _ _ _ _ _ _ _ _ _ _ + cursor: conn.cursor + A cursor object, used to perform spatial SQL queries + dicOfBuildZoneGridPoint: Dictionary of Rockle zone tables + Dictionary having as key the type of Rockle zone and as value + the name of the table containing points corresponding to the zone + dz: float, default DZ + Resolution (in meter) of the grid in the vertical direction + prefix: String, default PREFIX_NAME + Prefix to add to the output table name + + Returns + _ _ _ _ _ _ _ _ _ _ + + dicOfOutputTables: dictionary of table name + Dictionary having as key the type of Rockle zone and as value + the name of the table containing points corresponding to the zone + and wind speed factor + maxHeight: float + Height of the highest Rôckle zone in the study area""" + print( + "Calculates the 3D wind speed factor value for each point of each BUILDING zone" + ) - dicOfOutputTables: dictionary of table name - Dictionary having as key the type of Rockle zone and as value - the name of the table containing points corresponding to the zone - and wind speed factor - maxHeight: float - Height of the highest Rôckle zone in the study area""" - print("Calculates the 3D wind speed factor value for each point of each BUILDING zone") - # Name of the output tables - dicOfOutputTables = {t: DataUtil.postfix(tableName = DataUtil.prefix(tableName = t, - prefix = prefix), - suffix = "POINTS_BUILD_3D") - for t in dicOfBuildZoneGridPoint} - + dicOfOutputTables = { + t: DataUtil.postfix( + tableName=DataUtil.prefix(tableName=t, prefix=prefix), + suffix="POINTS_BUILD_3D", + ) + for t in dicOfBuildZoneGridPoint + } + # Temporary tables (and prefix for temporary tables) zValueTable = DataUtil.postfix("Z_VALUES") - + # Identify the maximum height where wind speed may be affected by building obstacles - maxHeightQuery = \ - { DISPLACEMENT_NAME : "MAX({0}) AS MAX_HEIGHT".format(UPPER_VERTICAL_THRESHOLD), - DISPLACEMENT_VORTEX_NAME: "MAX({0}) AS MAX_HEIGHT".format(UPPER_VERTICAL_THRESHOLD), - CAVITY_NAME : "MAX({0}) AS MAX_HEIGHT".format(UPPER_VERTICAL_THRESHOLD), - WAKE_NAME : "MAX({0}) AS MAX_HEIGHT".format(UPPER_VERTICAL_THRESHOLD), - STREET_CANYON_NAME : "MAX({0}) AS MAX_HEIGHT".format(UPPER_VERTICAL_THRESHOLD), - ROOFTOP_PERP_NAME : "MAX({0}+{1}) AS MAX_HEIGHT".format(ROOFTOP_PERP_VAR_HEIGHT, - HEIGHT_FIELD), - ROOFTOP_CORN_NAME : "MAX({0}+{1}) AS MAX_HEIGHT".format(ROOFTOP_CORNER_VAR_HEIGHT, - HEIGHT_FIELD), - CAVITY_BACKWARD_NAME : "MAX({0}) AS MAX_HEIGHT".format(UPPER_VERTICAL_THRESHOLD), - WAKE_BACKWARD_NAME : "MAX({0}) AS MAX_HEIGHT".format(UPPER_VERTICAL_THRESHOLD)} - cursor.execute(safe(""" SELECT MAX(MAX_HEIGHT) AS MAX_HEIGHT + maxHeightQuery = { + DISPLACEMENT_NAME: "MAX({0}) AS MAX_HEIGHT".format( + UPPER_VERTICAL_THRESHOLD + ), + DISPLACEMENT_VORTEX_NAME: "MAX({0}) AS MAX_HEIGHT".format( + UPPER_VERTICAL_THRESHOLD + ), + CAVITY_NAME: "MAX({0}) AS MAX_HEIGHT".format(UPPER_VERTICAL_THRESHOLD), + WAKE_NAME: "MAX({0}) AS MAX_HEIGHT".format(UPPER_VERTICAL_THRESHOLD), + STREET_CANYON_NAME: "MAX({0}) AS MAX_HEIGHT".format( + UPPER_VERTICAL_THRESHOLD + ), + ROOFTOP_PERP_NAME: "MAX({0}+{1}) AS MAX_HEIGHT".format( + ROOFTOP_PERP_VAR_HEIGHT, HEIGHT_FIELD + ), + ROOFTOP_CORN_NAME: "MAX({0}+{1}) AS MAX_HEIGHT".format( + ROOFTOP_CORNER_VAR_HEIGHT, HEIGHT_FIELD + ), + CAVITY_BACKWARD_NAME: "MAX({0}) AS MAX_HEIGHT".format( + UPPER_VERTICAL_THRESHOLD + ), + WAKE_BACKWARD_NAME: "MAX({0}) AS MAX_HEIGHT".format( + UPPER_VERTICAL_THRESHOLD + ), + } + cursor.execute( + safe(""" SELECT MAX(MAX_HEIGHT) AS MAX_HEIGHT FROM (SELECT {0}) - """).format(" UNION ALL SELECT ".join([maxHeightQuery[t]+" FROM "+dicOfBuildZoneGridPoint[t] - for t in dicOfBuildZoneGridPoint]))) + """).format( + " UNION ALL SELECT ".join( + [ + maxHeightQuery[t] + " FROM " + dicOfBuildZoneGridPoint[t] + for t in dicOfBuildZoneGridPoint + ] + ) + ) + ) maxHeight = cursor.fetchall()[0][0] - + # Creates the table of z levels impacted by building obstacles (start at dz/2) if maxHeight: - if maxHeight > float(dz)/2: - listOfZ = [str(i) for i in np.arange(float(dz)/2, - 3*float(dz)/2+math.trunc(maxHeight/dz)*dz, - dz)] - - cursor.execute(safe(""" + if maxHeight > float(dz) / 2: + listOfZ = [ + str(i) + for i in np.arange( + float(dz) / 2, + 3 * float(dz) / 2 + math.trunc(maxHeight / dz) * dz, + dz, + ) + ] + + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0}({2} BIGINT AUTO_INCREMENT, {3} DOUBLE); INSERT INTO {0} VALUES (DEFAULT, {1}) - """).format( zValueTable, - "), (DEFAULT, ".join(listOfZ), - ID_POINT_Z, - Z)) - + """).format( + zValueTable, "), (DEFAULT, ".join(listOfZ), ID_POINT_Z, Z + ) + ) + else: cursor.execute(safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0}({1} BIGINT AUTO_INCREMENT, {2} DOUBLE); - """).format( zValueTable, - ID_POINT_Z, - Z)) - + """).format(zValueTable, ID_POINT_Z, Z)) + else: cursor.execute(safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0}({1} BIGINT AUTO_INCREMENT, {2} DOUBLE); - """).format( zValueTable, - ID_POINT_Z, - Z)) - + """).format(zValueTable, ID_POINT_Z, Z)) + # Defines the calculation and columns to keep for each zone - calcQuery = \ - { DISPLACEMENT_NAME : """ + calcQuery = { + DISPLACEMENT_NAME: """ b.{0}, {1}*POWER(b.{2}/a.{3},{4})*a.{7} AS {7}, {1}*POWER(b.{2}/a.{3},{4})*a.{5} AS {5}, @@ -1596,48 +2194,46 @@ def calculates3dBuildWindFactor(cursor, dicOfBuildZoneGridPoint, END AS {8}, a.{6}, a.{3} - """.format( ID_POINT_Z, - C_DZ, - Z, - HEIGHT_FIELD, - P_DZ, - V, - ID_POINT, - U, - W), - DISPLACEMENT_VORTEX_NAME : """ + """.format( + ID_POINT_Z, C_DZ, Z, HEIGHT_FIELD, P_DZ, V, ID_POINT, U, W + ), + DISPLACEMENT_VORTEX_NAME: """ b.{0}, -(0.6*COS(PI()*b.{1}/(0.5*a.{2}))+0.05)*0.6*SIN(PI()*a.{3}) AS {4}, -0.1*COS(PI()*a.{3})-0.05 AS {5}, a.{6}, a.{2} - """.format( ID_POINT_Z, - Z, - HEIGHT_FIELD, - POINT_RELATIVE_POSITION_FIELD+DISPLACEMENT_VORTEX_NAME[0], - V, - W, - ID_POINT), - CAVITY_NAME : """ + """.format( + ID_POINT_Z, + Z, + HEIGHT_FIELD, + POINT_RELATIVE_POSITION_FIELD + DISPLACEMENT_VORTEX_NAME[0], + V, + W, + ID_POINT, + ), + CAVITY_NAME: """ b.{0}, -POWER(1-a.{1}/POWER(1-POWER(b.{2}/a.{3},2),0.5),2)*POWER(a.{6},0)*POWER(a.{8},0.5) AS {4}, a.{5}, a.{3}, 0*POWER(a.{1}/POWER(1-POWER(b.{2}/a.{3},2),0.5),0.5) AS {9}, 0*POWER(1-a.{1}/POWER(1-POWER(b.{2}/a.{3},2),0.5),2) AS {11} - """.format( ID_POINT_Z, - POINT_RELATIVE_POSITION_FIELD+CAVITY_NAME[0], - Z, - HEIGHT_FIELD, - V, - ID_POINT, - COS_BLOCK_AZIMUTH, - SIN_BLOCK_AZIMUTH, - DOWNSTREAM_X_RELATIVE_POSITION, - U, - POINT_RELATIVE_POSITION_FIELD+WAKE_NAME[0], - W), - WAKE_NAME : """ + """.format( + ID_POINT_Z, + POINT_RELATIVE_POSITION_FIELD + CAVITY_NAME[0], + Z, + HEIGHT_FIELD, + V, + ID_POINT, + COS_BLOCK_AZIMUTH, + SIN_BLOCK_AZIMUTH, + DOWNSTREAM_X_RELATIVE_POSITION, + U, + POINT_RELATIVE_POSITION_FIELD + WAKE_NAME[0], + W, + ), + WAKE_NAME: """ b.{0}, 1-a.{1}*POWER(POWER(1-POWER(b.{2}/a.{3},2),0.5),1.5) AS {4}, 1-a.{1}*POWER(POWER(1-POWER(b.{2}/a.{3},2),0.5),1.5) AS {6}, @@ -1646,48 +2242,48 @@ def calculates3dBuildWindFactor(cursor, dicOfBuildZoneGridPoint, a.{3}, 1-a.{1}*POWER(POWER(1-POWER(b.{2}/a.{3},2),0.5),1.5)*POWER(a.{8},0)*POWER(a.{10},0) AS {11}, 0*a.{1}*POWER(POWER(1-POWER(b.{2}/a.{3},2),0.5),1.5)*a.{9}*a.{10} AS {12} - """.format( ID_POINT_Z, - WAKE_RELATIVE_POSITION_FIELD, - Z, - HEIGHT_FIELD, - V_WEIGHT, - ID_POINT, - U_WEIGHT, - W_WEIGHT, - COS_BLOCK_AZIMUTH, - SIN_BLOCK_AZIMUTH, - DOWNSTREAM_X_RELATIVE_POSITION, - V, - U, - POINT_RELATIVE_POSITION_FIELD+WAKE_NAME[0]), - STREET_CANYON_NAME : """ + """.format( + ID_POINT_Z, + WAKE_RELATIVE_POSITION_FIELD, + Z, + HEIGHT_FIELD, + V_WEIGHT, + ID_POINT, + U_WEIGHT, + W_WEIGHT, + COS_BLOCK_AZIMUTH, + SIN_BLOCK_AZIMUTH, + DOWNSTREAM_X_RELATIVE_POSITION, + V, + U, + POINT_RELATIVE_POSITION_FIELD + WAKE_NAME[0], + ), + STREET_CANYON_NAME: """ b.{0}, a.{1}, a.{2}, a.{3}, a.{4}, a.{5} AS {6} - """.format( ID_POINT_Z, - U, - V, - W, - ID_POINT, - UPSTREAM_HEIGHT_FIELD, - HEIGHT_FIELD), - ROOFTOP_PERP_NAME : """ + """.format( + ID_POINT_Z, U, V, W, ID_POINT, UPSTREAM_HEIGHT_FIELD, HEIGHT_FIELD + ), + ROOFTOP_PERP_NAME: """ b.{0}, -POWER((a.{1}+a.{2}-b.{3})/{4},{5})*ABS(a.{1}+a.{2}-b.{3})/a.{2} AS {6}, a.{7}, a.{1} - """.format( ID_POINT_Z, - HEIGHT_FIELD, - ROOFTOP_PERP_VAR_HEIGHT, - Z, - Z_REF, - P_RTP, - V, - ID_POINT), - ROOFTOP_CORN_NAME : """ + """.format( + ID_POINT_Z, + HEIGHT_FIELD, + ROOFTOP_PERP_VAR_HEIGHT, + Z, + Z_REF, + P_RTP, + V, + ID_POINT, + ), + ROOFTOP_CORN_NAME: """ b.{0}, -a.{8}*SIN(2*a.{9})*POWER((a.{1}+a.{2}-b.{3})/{4},{5}) *ABS(a.{1}+a.{2}-b.{3})/a.{2} AS {6}, @@ -1695,33 +2291,37 @@ def calculates3dBuildWindFactor(cursor, dicOfBuildZoneGridPoint, *ABS(a.{1}+a.{2}-b.{3})/a.{2} AS {10}, a.{7}, a.{1} - """.format( ID_POINT_Z, - HEIGHT_FIELD, - ROOFTOP_CORNER_VAR_HEIGHT, - Z, - Z_REF, - P_RTP, - U, - ID_POINT, - ROOFTOP_WIND_FACTOR, - UPWIND_FACADE_ANGLE_FIELD, - V), - CAVITY_BACKWARD_NAME: """ + """.format( + ID_POINT_Z, + HEIGHT_FIELD, + ROOFTOP_CORNER_VAR_HEIGHT, + Z, + Z_REF, + P_RTP, + U, + ID_POINT, + ROOFTOP_WIND_FACTOR, + UPWIND_FACADE_ANGLE_FIELD, + V, + ), + CAVITY_BACKWARD_NAME: """ b.{0}, POWER(1-a.{1}/POWER(1-POWER(b.{2}/a.{3},2),0.5),2) AS {4}, a.{5}, a.{3}, a.{6}, a.{7} - """.format( ID_POINT_Z, - POINT_RELATIVE_POSITION_FIELD+CAVITY_NAME[0], - Z, - HEIGHT_FIELD, - V, - ID_POINT, - ID_POINT_X, - UPWIND_FACADE_FIELD), - WAKE_BACKWARD_NAME: """ + """.format( + ID_POINT_Z, + POINT_RELATIVE_POSITION_FIELD + CAVITY_NAME[0], + Z, + HEIGHT_FIELD, + V, + ID_POINT, + ID_POINT_X, + UPWIND_FACADE_FIELD, + ), + WAKE_BACKWARD_NAME: """ b.{0}, -1+POWER(a.{1}*POWER(1-POWER(b.{2}/a.{3},2),0.5),1.5) AS {4}, -1+POWER(a.{1}*POWER(1-POWER(b.{2}/a.{3},2),0.5),1.5) AS {6}, @@ -1730,89 +2330,109 @@ def calculates3dBuildWindFactor(cursor, dicOfBuildZoneGridPoint, a.{3}, a.{8}, a.{9} - """.format( ID_POINT_Z, - WAKE_RELATIVE_POSITION_FIELD, - Z, - HEIGHT_FIELD, - V, - ID_POINT, - U, - W, - ID_POINT_X, - UPWIND_FACADE_FIELD) - } + """.format( + ID_POINT_Z, + WAKE_RELATIVE_POSITION_FIELD, + Z, + HEIGHT_FIELD, + V, + ID_POINT, + U, + W, + ID_POINT_X, + UPWIND_FACADE_FIELD, + ), + } # Defines the WHERE clause (on z-axis values) for each point of each zone - whereQuery = \ - { DISPLACEMENT_NAME : "b.{0} < a.{1}".format(Z, - UPPER_VERTICAL_THRESHOLD), - DISPLACEMENT_VORTEX_NAME: "b.{0} < a.{1}".format(Z, - UPPER_VERTICAL_THRESHOLD), - CAVITY_NAME : "b.{0} < a.{1}".format(Z, - UPPER_VERTICAL_THRESHOLD), - WAKE_NAME : "b.{0} < a.{1} AND b.{0} >= a.{2}".format(Z, - UPPER_VERTICAL_THRESHOLD, - UPPER_VERTICAL_THRESHOLD + CAVITY_NAME[0]), - STREET_CANYON_NAME : """b.{0} < a.{1} - AND b.{0} < a.{2}""".format( Z, - UPPER_VERTICAL_THRESHOLD, - MAX_CANYON_HEIGHT_FIELD), - ROOFTOP_PERP_NAME : """b.{0} < a.{1}+a.{2} - AND b.{0} > a.{1}""".format( Z, - HEIGHT_FIELD, - ROOFTOP_PERP_VAR_HEIGHT), - ROOFTOP_CORN_NAME : """b.{0} < a.{1}+a.{2} - AND b.{0} > a.{1}""".format( Z, - HEIGHT_FIELD, - ROOFTOP_CORNER_VAR_HEIGHT), - CAVITY_BACKWARD_NAME : "b.{0} < a.{1}".format(Z, - UPPER_VERTICAL_THRESHOLD), - WAKE_BACKWARD_NAME : "b.{0} < a.{1} AND b.{0} >= a.{2}".format(Z, - UPPER_VERTICAL_THRESHOLD, - UPPER_VERTICAL_THRESHOLD + CAVITY_NAME[0]), - } + whereQuery = { + DISPLACEMENT_NAME: "b.{0} < a.{1}".format(Z, UPPER_VERTICAL_THRESHOLD), + DISPLACEMENT_VORTEX_NAME: "b.{0} < a.{1}".format( + Z, UPPER_VERTICAL_THRESHOLD + ), + CAVITY_NAME: "b.{0} < a.{1}".format(Z, UPPER_VERTICAL_THRESHOLD), + WAKE_NAME: "b.{0} < a.{1} AND b.{0} >= a.{2}".format( + Z, + UPPER_VERTICAL_THRESHOLD, + UPPER_VERTICAL_THRESHOLD + CAVITY_NAME[0], + ), + STREET_CANYON_NAME: """b.{0} < a.{1} + AND b.{0} < a.{2}""".format( + Z, UPPER_VERTICAL_THRESHOLD, MAX_CANYON_HEIGHT_FIELD + ), + ROOFTOP_PERP_NAME: """b.{0} < a.{1}+a.{2} + AND b.{0} > a.{1}""".format( + Z, HEIGHT_FIELD, ROOFTOP_PERP_VAR_HEIGHT + ), + ROOFTOP_CORN_NAME: """b.{0} < a.{1}+a.{2} + AND b.{0} > a.{1}""".format( + Z, HEIGHT_FIELD, ROOFTOP_CORNER_VAR_HEIGHT + ), + CAVITY_BACKWARD_NAME: "b.{0} < a.{1}".format( + Z, UPPER_VERTICAL_THRESHOLD + ), + WAKE_BACKWARD_NAME: "b.{0} < a.{1} AND b.{0} >= a.{2}".format( + Z, + UPPER_VERTICAL_THRESHOLD, + UPPER_VERTICAL_THRESHOLD + CAVITY_NAME[0], + ), + } # Execute the calculation - cursor.execute(safe(";").join([ - safe(""" DROP TABLE IF EXISTS {0}; + cursor.execute( + safe(";").join( + [ + safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0} AS SELECT {1}, a.{5} FROM {2} AS a, {3} AS b WHERE {4} - """).format( dicOfOutputTables[t], - calcQuery[t], - dicOfBuildZoneGridPoint[t], - zValueTable, - whereQuery[t], - Y_WALL) - for t in dicOfBuildZoneGridPoint])) - + """).format( + dicOfOutputTables[t], + calcQuery[t], + dicOfBuildZoneGridPoint[t], + zValueTable, + whereQuery[t], + Y_WALL, + ) + for t in dicOfBuildZoneGridPoint + ] + ) + ) + if not DEBUG: # Remove intermediate tables cursor.execute(""" DROP TABLE IF EXISTS {0} """.format(zValueTable)) - + return dicOfOutputTables, maxHeight -def calculates3dVegWindFactor(cursor, dicOfVegZoneGridPoint, sketchHeight, - z0, d, dz = DZ, prefix = PREFIX_NAME): - """ Calculates the 3D wind speed factors for each zone according to - Nelson et al. (2009) method. Note that for vegetation located in +def calculates3dVegWindFactor( + cursor, + dicOfVegZoneGridPoint, + sketchHeight, + z0, + d, + dz=DZ, + prefix=PREFIX_NAME, +): + """Calculates the 3D wind speed factors for each zone according to + Nelson et al. (2009) method. Note that for vegetation located in open areas (Equations 8 and 9), the displacement height is defined by Equation 18a from Hanna and Britter (2002), considering that lambda_f=0.05 and Hr being the height of the vegetation at this specific location. - - References: - Hanna, SR, et RE Britter. « Wind flow and vapor cloud dispersion at + + References: + Hanna, SR, et RE Britter. « Wind flow and vapor cloud dispersion at industrial sites. Am. Inst ». Chem Eng, New York, 2002. - Nelson, Matthew, Michael Williams, Dragan Zajic, Michael Brown, et - Eric Pardyjak. Evaluation of an urban vegetative canopy scheme and + Nelson, Matthew, Michael Williams, Dragan Zajic, Michael Brown, et + Eric Pardyjak. Evaluation of an urban vegetative canopy scheme and impact on plume dispersion, 2009. - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries @@ -1829,47 +2449,56 @@ def calculates3dVegWindFactor(cursor, dicOfVegZoneGridPoint, sketchHeight, Resolution (in meter) of the grid in the vertical direction prefix: String, default PREFIX_NAME Prefix to add to the output table name - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ vegetationWeightFactorTable: String Name of the table containing the weighting factor for each 3D point located in a vegetation zone""" - print("Calculates the 3D wind speed factor value for each point of each VEGETATION zone") - + print( + "Calculates the 3D wind speed factor value for each point of each VEGETATION zone" + ) + # Output base name outputBaseName = "VEGETATION_WEIGHTING_FACTORS" - + # Name of the output table - vegetationWeightFactorTable = DataUtil.prefix(outputBaseName, - prefix = prefix) - + vegetationWeightFactorTable = DataUtil.prefix( + outputBaseName, prefix=prefix + ) + # Temporary tables (and prefix for temporary tables) zValueTable = DataUtil.postfix("Z_VALUES") - dicOfTempoTables = {t: DataUtil.postfix(tableName = t, - suffix = "TEMPO_3DPOINTS") - for t in dicOfVegZoneGridPoint} + dicOfTempoTables = { + t: DataUtil.postfix(tableName=t, suffix="TEMPO_3DPOINTS") + for t in dicOfVegZoneGridPoint + } tempoAllVeg = DataUtil.postfix("TEMPO_ALL_VEG") - + # Creates the table of z levels of the sketch - listOfZ = [str(i) for i in np.arange(float(dz)/2, - float(dz)/2+math.trunc(sketchHeight/dz)*dz, - dz)] - cursor.execute(safe(""" + listOfZ = [ + str(i) + for i in np.arange( + float(dz) / 2, + float(dz) / 2 + math.trunc(sketchHeight / dz) * dz, + dz, + ) + ] + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0}({2} BIGINT AUTO_INCREMENT, {3} DOUBLE); INSERT INTO {0} VALUES (DEFAULT, {1}) - """).format( zValueTable, - "), (DEFAULT, ".join(listOfZ), - ID_POINT_Z, - Z)) - + """).format( + zValueTable, "), (DEFAULT, ".join(listOfZ), ID_POINT_Z, Z + ) + ) + # Calculation of the wind speed depending on vegetation location (open or building zone) # d is actually calculated by Equation 18a from Hanna and Britter (2002) calcQuery = { - VEGETATION_OPEN_NAME: - """ CASE WHEN (b.{2} - 3 * 0.05 * b.{2} <= {1} OR b.{2} <= {1}) AND a.{0} < b.{4} + VEGETATION_OPEN_NAME: """ CASE WHEN (b.{2} - 3 * 0.05 * b.{2} <= {1} OR b.{2} <= {1}) AND a.{0} < b.{4} THEN EXP(0) WHEN (b.{2} - 3 * 0.05 * b.{2} <= {1} OR b.{2} <= {1}) AND a.{0} > b.{4} AND a.{0} < b.{3} THEN EXP(b.{5} * (a.{0} / b.{2} - 1)) @@ -1880,14 +2509,15 @@ def calculates3dVegWindFactor(cursor, dicOfVegZoneGridPoint, sketchHeight, ELSE LOG((b.{2} - 3 * 0.05 * b.{2}) / {1}) / LOG(b.{2} / {1}) * EXP(b.{5} * (a.{0} / b.{2} - 1)) END END - """.format( Z, - z0, - TOP_CANOPY_HEIGHT_POINT, - VEGETATION_CROWN_TOP_HEIGHT, - VEGETATION_CROWN_BASE_HEIGHT, - VEGETATION_ATTENUATION_FACTOR), - VEGETATION_BUILT_NAME: - """ CASE WHEN (b.{2} <= {1} OR a.{0} <= {1}) AND a.{0} < b.{4} + """.format( + Z, + z0, + TOP_CANOPY_HEIGHT_POINT, + VEGETATION_CROWN_TOP_HEIGHT, + VEGETATION_CROWN_BASE_HEIGHT, + VEGETATION_ATTENUATION_FACTOR, + ), + VEGETATION_BUILT_NAME: """ CASE WHEN (b.{2} <= {1} OR a.{0} <= {1}) AND a.{0} < b.{4} THEN EXP(0) WHEN (b.{2} <= {1} OR a.{0} <= {1}) AND a.{0} > b.{4} AND a.{0} < b.{3} THEN EXP(b.{5} * (a.{0} / b.{2} - 1)) @@ -1895,20 +2525,29 @@ def calculates3dVegWindFactor(cursor, dicOfVegZoneGridPoint, sketchHeight, THEN LOG(b.{2} / {1}) / LOG(a.{0} / {1}) * EXP(0) ELSE LOG(b.{2} / {1}) / LOG(a.{0} / {1}) * EXP(b.{5} * (a.{0} / b.{2} - 1)) END - """.format( Z, - z0, - TOP_CANOPY_HEIGHT_POINT, - VEGETATION_CROWN_TOP_HEIGHT, - VEGETATION_CROWN_BASE_HEIGHT, - VEGETATION_ATTENUATION_FACTOR)} - - whereQuery = {VEGETATION_OPEN_NAME: "WHERE a.{0} > 0".format(Z), - VEGETATION_BUILT_NAME: """ WHERE a.{0} < b.{1} AND a.{0} > 0 - """.format( Z, - TOP_CANOPY_HEIGHT_POINT)} - + """.format( + Z, + z0, + TOP_CANOPY_HEIGHT_POINT, + VEGETATION_CROWN_TOP_HEIGHT, + VEGETATION_CROWN_BASE_HEIGHT, + VEGETATION_ATTENUATION_FACTOR, + ), + } + + whereQuery = { + VEGETATION_OPEN_NAME: "WHERE a.{0} > 0".format(Z), + VEGETATION_BUILT_NAME: """ WHERE a.{0} < b.{1} AND a.{0} > 0 + """.format( + Z, TOP_CANOPY_HEIGHT_POINT + ), + } + # Initialize the wind speed field depending on vegetation type and height - cursor.execute(safe(";").join([safe(""" + cursor.execute( + safe(";").join( + [ + safe(""" {10}; {11}; DROP TABLE IF EXISTS {0}; @@ -1919,34 +2558,46 @@ def calculates3dVegWindFactor(cursor, dicOfVegZoneGridPoint, sketchHeight, {12}; UPDATE {0} SET {3} = 1 WHERE {3} > 1; UPDATE {0} SET {3} = 0 WHERE {3} < 0; - """).format( dicOfTempoTables[t], - ID_POINT_Z, - calcQuery[t], - VEGETATION_FACTOR, - zValueTable, - dicOfVegZoneGridPoint[t], - Z, - TOP_CANOPY_HEIGHT_POINT, - whereQuery[t], - ID_POINT, - DataUtil.createIndex( tableName=zValueTable, - fieldName=Z, - isSpatial=False), - DataUtil.createIndex( tableName=dicOfVegZoneGridPoint[t], - fieldName=TOP_CANOPY_HEIGHT_POINT, - isSpatial=False), - DataUtil.createIndex( tableName=dicOfTempoTables[t], - fieldName=VEGETATION_FACTOR, - isSpatial=False)) for t in dicOfTempoTables])) - - # Gather zone points in a single vegetation table and keep the minimum value + """).format( + dicOfTempoTables[t], + ID_POINT_Z, + calcQuery[t], + VEGETATION_FACTOR, + zValueTable, + dicOfVegZoneGridPoint[t], + Z, + TOP_CANOPY_HEIGHT_POINT, + whereQuery[t], + ID_POINT, + DataUtil.createIndex( + tableName=zValueTable, fieldName=Z, isSpatial=False + ), + DataUtil.createIndex( + tableName=dicOfVegZoneGridPoint[t], + fieldName=TOP_CANOPY_HEIGHT_POINT, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=dicOfTempoTables[t], + fieldName=VEGETATION_FACTOR, + isSpatial=False, + ), + ) + for t in dicOfTempoTables + ] + ) + ) + + # Gather zone points in a single vegetation table and keep the minimum value # in case there are several vegetation layers - unionAllQuery = [safe(" SELECT {0}, {1}, {2} FROM {3}").format(ID_POINT, - ID_POINT_Z, - VEGETATION_FACTOR, - dicOfTempoTables[t]) - for t in dicOfTempoTables] - cursor.execute(safe(""" + unionAllQuery = [ + safe(" SELECT {0}, {1}, {2} FROM {3}").format( + ID_POINT, ID_POINT_Z, VEGETATION_FACTOR, dicOfTempoTables[t] + ) + for t in dicOfTempoTables + ] + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0} AS {1}; @@ -1957,41 +2608,50 @@ def calculates3dVegWindFactor(cursor, dicOfVegZoneGridPoint, sketchHeight, AS SELECT {2}, {4}, MIN({5}) AS {5} FROM {0} GROUP BY {2}, {4} - """).format( tempoAllVeg, - " UNION ALL ".join(unionAllQuery), - ID_POINT, - vegetationWeightFactorTable, - ID_POINT_Z, - VEGETATION_FACTOR, - DataUtil.createIndex(tableName=tempoAllVeg, - fieldName=ID_POINT, - isSpatial=False), - DataUtil.createIndex(tableName=tempoAllVeg, - fieldName=ID_POINT_Z, - isSpatial=False))) - + """).format( + tempoAllVeg, + " UNION ALL ".join(unionAllQuery), + ID_POINT, + vegetationWeightFactorTable, + ID_POINT_Z, + VEGETATION_FACTOR, + DataUtil.createIndex( + tableName=tempoAllVeg, fieldName=ID_POINT, isSpatial=False + ), + DataUtil.createIndex( + tableName=tempoAllVeg, fieldName=ID_POINT_Z, isSpatial=False + ), + ) + ) + if not DEBUG: # Remove intermediate tables - cursor.execute(safe(""" + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0}, {1} - """).format(",".join(dicOfTempoTables.values()), - ",".join([zValueTable, tempoAllVeg]))) - + """).format( + ",".join(dicOfTempoTables.values()), + ",".join([zValueTable, tempoAllVeg]), + ) + ) + return vegetationWeightFactorTable -def manageSuperimposition(cursor, - dicAllWeightFactorsTables, - facadeWithinCavity, - upstreamPriorityTables = UPSTREAM_PRIORITY_TABLES, - upstreamWeightingTables = UPSTREAM_WEIGHTING_TABLES, - upstreamWeightingInterRules = UPSTREAM_WEIGHTING_INTER_RULES, - upstreamWeightingIntraRules = UPSTREAM_WEIGHTING_INTRA_RULES, - upstreamBackPriorityTables = UPSTREAM_BACKWARD_PRIORITY_TABLES, - downstreamWeightingTable = DOWNSTREAM_WEIGTHING_TABLE, - prefix = PREFIX_NAME, - feedback = None): - """ Keep only one value per 3D point, dealing with superimposition from +def manageSuperimposition( + cursor, + dicAllWeightFactorsTables, + facadeWithinCavity, + upstreamPriorityTables=UPSTREAM_PRIORITY_TABLES, + upstreamWeightingTables=UPSTREAM_WEIGHTING_TABLES, + upstreamWeightingInterRules=UPSTREAM_WEIGHTING_INTER_RULES, + upstreamWeightingIntraRules=UPSTREAM_WEIGHTING_INTRA_RULES, + upstreamBackPriorityTables=UPSTREAM_BACKWARD_PRIORITY_TABLES, + downstreamWeightingTable=DOWNSTREAM_WEIGTHING_TABLE, + prefix=PREFIX_NAME, + feedback=None, +): + """Keep only one value per 3D point, dealing with superimposition from different Röckle zones. It is performed in three steps: - if a point is covered by several zones, keep the value only from a single zone based on the following priorities: @@ -2002,9 +2662,9 @@ def manageSuperimposition(cursor, - add the backward zones weighted by cavity and wake zones - apply a weighting due to some downstream zones (such as vegetation) - Parameters - _ _ _ _ _ _ _ _ _ _ - + Parameters + _ _ _ _ _ _ _ _ _ _ + cursor: conn.cursor A cursor object, used to perform spatial SQL queries dicAllWeightFactorsTables: Dictionary of Rockle zone tables @@ -2015,20 +2675,20 @@ def manageSuperimposition(cursor, which will be useful for assigning a weighting to backward zones upstreamPriorityTables: pd.DataFrame, default UPSTREAM_PRIORITY_TABLES Defines which zones should be used in the priority algorithm and - set priorities (column "priority") when the zone comes from a same + set priorities (column "priority") when the zone comes from a same upstream obstacle of same height. Also contains a column "ref_height" to set by which wind speed height the weigthing factor should be multiplied. The following values are possible: - -> 1: "upstream building height", + -> 1: "upstream building height", -> 2: "Reference wind speed measurement height Z_REF", -> 3: "building height") upstreamBackPriorityTables: pd.DataFrame, default UPSTREAM_BACKWARD_PRIORITY_TABLES Defines which zones should be used in the priority algorithm and - set priorities (column "priority") when the backward zone comes from a same + set priorities (column "priority") when the backward zone comes from a same upstream obstacle of same height. Also contains a column "ref_height" to set by which wind speed height the weigthing factor should be multiplied. The following values are possible: - -> 1: "upstream building height", + -> 1: "upstream building height", -> 2: "Reference wind speed measurement height Z_REF", -> 3: "building height") upstreamWeightingTables: list, default UPSTREAM_WEIGHTING_TABLES @@ -2036,153 +2696,213 @@ def manageSuperimposition(cursor, upstreamWeightingInterRules: String, default UPSTREAM_WEIGHTING_INTER_RULES Defines how to deal with a point having several values from a same upstream weighting zone - -> "upstream": use values from the most upstream and upper + -> "upstream": use values from the most upstream and upper obstacles upstreamWeightingIntraRules: String, default UPSTREAM_WEIGHTING_INTRA_RULES Defines how to deal with a point having several values from several upstream weighting zones - -> "upstream": use values from the most upstream and upper + -> "upstream": use values from the most upstream and upper obstacles downstreamWeightingTable: String, default DOWNSTREAM_WEIGTHING_TABLES - Name of the zone having the non-duplicated points used to weight + Name of the zone having the non-duplicated points used to weight the wind speed factors at the end prefix: String, default PREFIX_NAME Prefix to add to the output table name feedback: Qgis.core class QgsProcessingFeedback Base class for providing feedback to QGIS from a processing algorithm (if not in standalone mode). - - Returns - _ _ _ _ _ _ _ _ _ _ - + + Returns + _ _ _ _ _ _ _ _ _ _ + initializedWindFactorTable: String Name of the table containing the weighting factor for each 3D point (one value per point, means superimposition have been used)""" print("Deals with superimposition (keeps only 1 value per 3D point)") - + # Output base name outputBaseName = "INITIALIZED_WIND_FACTOR_FIELD" - + # Name of the output table - initializedWindFactorTable = DataUtil.prefix(outputBaseName, - prefix = prefix) + initializedWindFactorTable = DataUtil.prefix(outputBaseName, prefix=prefix) # name of the bacward weight field backWeightField = "BACK_WEIGHT" # Backward zone names backwardZoneName = [CAVITY_BACKWARD_NAME, WAKE_BACKWARD_NAME] - + # Temporary tables (and prefix for temporary tables) tempoPrioritiesAll = DataUtil.postfix("TEMPO_PRIORITY_ALL") tempoPrioritiesWeighted = DataUtil.postfix("TEMPO_PRIORITY_WEIGHTED") - tempoPrioritiesWeightedAll = DataUtil.postfix("TEMPO_PRIORITY_WEIGHTED_ALL") + tempoPrioritiesWeightedAll = DataUtil.postfix( + "TEMPO_PRIORITY_WEIGHTED_ALL" + ) tempoBackwardWeights = DataUtil.postfix("TEMPO_BACWARD_WEIGHTS") - dicBackwardWeighted = {t: DataUtil.postfix(DataUtil.prefix(t, prefix = "TEMPO_WEIGHTED")) - for t in backwardZoneName} - tempoPrioritiesWeightedAllPlusBack = DataUtil.postfix("TEMPO_PRIORITY_WEIGHTED_ALL_PLUS_BACK") - tempoUpstreamAndDownstream = DataUtil.postfix("TEMPO_UPSTREAM_AND_DOWNSTREAM") - + dicBackwardWeighted = { + t: DataUtil.postfix(DataUtil.prefix(t, prefix="TEMPO_WEIGHTED")) + for t in backwardZoneName + } + tempoPrioritiesWeightedAllPlusBack = DataUtil.postfix( + "TEMPO_PRIORITY_WEIGHTED_ALL_PLUS_BACK" + ) + tempoUpstreamAndDownstream = DataUtil.postfix( + "TEMPO_UPSTREAM_AND_DOWNSTREAM" + ) + # Give feedback to user if feedback: - feedback.setProgressText('Deals with building zones superimposition') + feedback.setProgressText("Deals with building zones superimposition") if feedback.isCanceled(): cursor.close() feedback.setProgressText("Calculation cancelled by user") return {} - + # Deal with superimposition when duplicated points in non backward zones - tempoPrioritiesWeightedAll = \ - manageUpstreamSuperimposition(cursor, - dicAllWeightFactorsTables = dicAllWeightFactorsTables, - upstreamPriorityTables = upstreamPriorityTables, - upstreamWeightingTables = upstreamWeightingTables, - upstreamWeightingInterRules = UPSTREAM_WEIGHTING_INTER_RULES, - upstreamWeightingIntraRules = UPSTREAM_WEIGHTING_INTRA_RULES, - backward = False, - prefix = PREFIX_NAME) + tempoPrioritiesWeightedAll = manageUpstreamSuperimposition( + cursor, + dicAllWeightFactorsTables=dicAllWeightFactorsTables, + upstreamPriorityTables=upstreamPriorityTables, + upstreamWeightingTables=upstreamWeightingTables, + upstreamWeightingInterRules=UPSTREAM_WEIGHTING_INTER_RULES, + upstreamWeightingIntraRules=UPSTREAM_WEIGHTING_INTRA_RULES, + backward=False, + prefix=PREFIX_NAME, + ) # MANAGE THE BACKWARD ZONES # Get the weighting factor for the backward zones - cursor.execute(safe(""" + cursor.execute( + safe(""" {0}{1}{2}{3} DROP TABLE IF EXISTS {4}; CREATE TABLE {4} AS SELECT a.{5}, a.{6}, ABS(b.{7}) AS {8}, b.{13}, a.{11}, a.{12} FROM {9} AS a LEFT JOIN {10} AS b ON a.{11} = b.{11} AND a.{12} = b.{12} - """).format( DataUtil.createIndex(tableName=tempoPrioritiesWeightedAll, - fieldName=ID_POINT, - isSpatial=False), - DataUtil.createIndex(tableName=tempoPrioritiesWeightedAll, - fieldName=ID_POINT_Z, - isSpatial=False), - DataUtil.createIndex(tableName=facadeWithinCavity, - fieldName=ID_POINT, - isSpatial=False), - DataUtil.createIndex(tableName=facadeWithinCavity, - fieldName=ID_POINT_Z, - isSpatial=False), - tempoBackwardWeights , ID_POINT_X, - UPWIND_FACADE_FIELD , V, - backWeightField , facadeWithinCavity, - tempoPrioritiesWeightedAll , ID_POINT, - ID_POINT_Z , HEIGHT_FIELD)) - + """).format( + DataUtil.createIndex( + tableName=tempoPrioritiesWeightedAll, + fieldName=ID_POINT, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=tempoPrioritiesWeightedAll, + fieldName=ID_POINT_Z, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=facadeWithinCavity, + fieldName=ID_POINT, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=facadeWithinCavity, + fieldName=ID_POINT_Z, + isSpatial=False, + ), + tempoBackwardWeights, + ID_POINT_X, + UPWIND_FACADE_FIELD, + V, + backWeightField, + facadeWithinCavity, + tempoPrioritiesWeightedAll, + ID_POINT, + ID_POINT_Z, + HEIGHT_FIELD, + ) + ) + # Apply the weighting factor to the backward zones - backwardZoneQuery = {CAVITY_BACKWARD_NAME: - """ a.{0}, a.{1}, b.{2}, NULL AS {3}, a.{5}*b.{4} AS {5}, + backwardZoneQuery = { + CAVITY_BACKWARD_NAME: """ a.{0}, a.{1}, b.{2}, NULL AS {3}, a.{5}*b.{4} AS {5}, NULL AS {6}, {7} AS {8}, a.{9} - """.format(ID_POINT , ID_POINT_Z, - HEIGHT_FIELD , U, - backWeightField , V, - W , UPSTREAM_PRIORITY_TABLES.loc[CAVITY_NAME, REF_HEIGHT_FIELD], - REF_HEIGHT_FIELD , Y_WALL), - WAKE_BACKWARD_NAME: - """ a.{0}, a.{1}, b.{2}, NULL AS {3}, a.{5}*b.{4} AS {5}, + """.format( + ID_POINT, + ID_POINT_Z, + HEIGHT_FIELD, + U, + backWeightField, + V, + W, + UPSTREAM_PRIORITY_TABLES.loc[CAVITY_NAME, REF_HEIGHT_FIELD], + REF_HEIGHT_FIELD, + Y_WALL, + ), + WAKE_BACKWARD_NAME: """ a.{0}, a.{1}, b.{2}, NULL AS {3}, a.{5}*b.{4} AS {5}, NULL AS {6}, {7} AS {8}, a.{9} - """.format(ID_POINT , ID_POINT_Z, - HEIGHT_FIELD , U, - backWeightField , V, - W , UPSTREAM_PRIORITY_TABLES.loc[CAVITY_NAME, REF_HEIGHT_FIELD], - REF_HEIGHT_FIELD , Y_WALL)} - - cursor.execute(safe(";").join([safe(""" + """.format( + ID_POINT, + ID_POINT_Z, + HEIGHT_FIELD, + U, + backWeightField, + V, + W, + UPSTREAM_PRIORITY_TABLES.loc[CAVITY_NAME, REF_HEIGHT_FIELD], + REF_HEIGHT_FIELD, + Y_WALL, + ), + } + + cursor.execute( + safe(";").join( + [ + safe(""" {0}{1}{2}{3} DROP TABLE IF EXISTS {4}; CREATE TABLE {4} AS SELECT {5} FROM {6} AS a LEFT JOIN {7} AS b ON a.{8} = b.{8} AND a.{9} = b.{9} - """).format( DataUtil.createIndex(tableName=tempoBackwardWeights, - fieldName=ID_POINT_X, - isSpatial=False), - DataUtil.createIndex(tableName=tempoBackwardWeights, - fieldName=UPWIND_FACADE_FIELD, - isSpatial=False), - DataUtil.createIndex(tableName=dicAllWeightFactorsTables[t], - fieldName=ID_POINT_X, - isSpatial=False), - DataUtil.createIndex(tableName=dicAllWeightFactorsTables[t], - fieldName=UPWIND_FACADE_FIELD, - isSpatial=False), - dicBackwardWeighted[t] , backwardZoneQuery[t], - dicAllWeightFactorsTables[t] , tempoBackwardWeights, - ID_POINT_X , UPWIND_FACADE_FIELD) - for t in backwardZoneQuery])) - + """).format( + DataUtil.createIndex( + tableName=tempoBackwardWeights, + fieldName=ID_POINT_X, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=tempoBackwardWeights, + fieldName=UPWIND_FACADE_FIELD, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=dicAllWeightFactorsTables[t], + fieldName=ID_POINT_X, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=dicAllWeightFactorsTables[t], + fieldName=UPWIND_FACADE_FIELD, + isSpatial=False, + ), + dicBackwardWeighted[t], + backwardZoneQuery[t], + dicAllWeightFactorsTables[t], + tempoBackwardWeights, + ID_POINT_X, + UPWIND_FACADE_FIELD, + ) + for t in backwardZoneQuery + ] + ) + ) + # Deal with superimposition when duplicated points between backward cavity and wake zones - upstreamBackPrioritiesTempoTable = \ - manageUpstreamSuperimposition(cursor, - dicAllWeightFactorsTables = dicBackwardWeighted, - upstreamPriorityTables = upstreamBackPriorityTables, - upstreamWeightingTables = UPSTREAM_BACKWARD_WEIGHTING_TABLES, - upstreamWeightingInterRules = UPSTREAM_WEIGHTING_INTER_RULES, - upstreamWeightingIntraRules = UPSTREAM_WEIGHTING_INTRA_RULES, - backward = True, - prefix = PREFIX_NAME) - + upstreamBackPrioritiesTempoTable = manageUpstreamSuperimposition( + cursor, + dicAllWeightFactorsTables=dicBackwardWeighted, + upstreamPriorityTables=upstreamBackPriorityTables, + upstreamWeightingTables=UPSTREAM_BACKWARD_WEIGHTING_TABLES, + upstreamWeightingInterRules=UPSTREAM_WEIGHTING_INTER_RULES, + upstreamWeightingIntraRules=UPSTREAM_WEIGHTING_INTRA_RULES, + backward=True, + prefix=PREFIX_NAME, + ) + # Add backward zone points to the final table (replace points if exist) - cursor.execute(safe(""" + cursor.execute( + safe(""" {idx1} {idx2} DROP TABLE IF EXISTS {result_table}; @@ -2197,28 +2917,34 @@ def manageSuperimposition(cursor, ON a.{id_p} = b.{id_p} AND a.{id_z} = b.{id_z} WHERE b.{id_p} IS NULL AND b.{id_z} IS NULL """).format( - idx1=DataUtil.createIndex(tableName=upstreamBackPrioritiesTempoTable, - fieldName=[ID_POINT, ID_POINT_Z], - isSpatial=False), - idx2=DataUtil.createIndex(tableName=tempoPrioritiesWeightedAll, - fieldName=[ID_POINT, ID_POINT_Z], - isSpatial=False), - result_table=tempoPrioritiesWeightedAllPlusBack, - upstream_table=upstreamBackPrioritiesTempoTable, - weighted_table=tempoPrioritiesWeightedAll, - id_p=ID_POINT, - id_z=ID_POINT_Z - )) + idx1=DataUtil.createIndex( + tableName=upstreamBackPrioritiesTempoTable, + fieldName=[ID_POINT, ID_POINT_Z], + isSpatial=False, + ), + idx2=DataUtil.createIndex( + tableName=tempoPrioritiesWeightedAll, + fieldName=[ID_POINT, ID_POINT_Z], + isSpatial=False, + ), + result_table=tempoPrioritiesWeightedAllPlusBack, + upstream_table=upstreamBackPrioritiesTempoTable, + weighted_table=tempoPrioritiesWeightedAll, + id_p=ID_POINT, + id_z=ID_POINT_Z, + ) + ) if feedback: - feedback.setProgressText('Deals with vegetation zones superimposition') + feedback.setProgressText("Deals with vegetation zones superimposition") if feedback.isCanceled(): cursor.close() feedback.setProgressText("Calculation cancelled by user") return {} # MANAGE THE DOWNSTREAM WEIGHTING ZONES # Weight the wind speeds factors by the downstream weights (vegetation) - cursor.execute(safe(""" + cursor.execute( + safe(""" {12} DROP TABLE IF EXISTS {10}; CREATE TABLE {10} @@ -2229,19 +2955,30 @@ def manageSuperimposition(cursor, COALESCE(b.{9}, {11}) AS {9} FROM {0} AS a LEFT JOIN {1} AS b ON a.{2} = b.{2} AND a.{3} = b.{3} - """).format( dicAllWeightFactorsTables[downstreamWeightingTable], - tempoPrioritiesWeightedAllPlusBack, - ID_POINT , ID_POINT_Z, - HEIGHT_FIELD , VEGETATION_FACTOR, - U , V, - W , REF_HEIGHT_FIELD, - tempoUpstreamAndDownstream , REF_HEIGHT_DOWNSTREAM_WEIGHTING, - DataUtil.createIndex(tableName=tempoPrioritiesWeightedAllPlusBack, - fieldName=[ID_POINT, ID_POINT_Z], - isSpatial=False))) - + """).format( + dicAllWeightFactorsTables[downstreamWeightingTable], + tempoPrioritiesWeightedAllPlusBack, + ID_POINT, + ID_POINT_Z, + HEIGHT_FIELD, + VEGETATION_FACTOR, + U, + V, + W, + REF_HEIGHT_FIELD, + tempoUpstreamAndDownstream, + REF_HEIGHT_DOWNSTREAM_WEIGHTING, + DataUtil.createIndex( + tableName=tempoPrioritiesWeightedAllPlusBack, + fieldName=[ID_POINT, ID_POINT_Z], + isSpatial=False, + ), + ) + ) + # Join the downstream weigthted points to the non downstream weighted ones - cursor.execute(safe(""" + cursor.execute( + safe(""" {10}; DROP TABLE IF EXISTS {9}; CREATE TABLE {9} @@ -2252,39 +2989,60 @@ def manageSuperimposition(cursor, UNION ALL SELECT c.{2}, c.{3}, c.{4}, c.{5}, c.{6}, c.{7}, c.{8} FROM {1} AS c - """).format( tempoPrioritiesWeightedAllPlusBack , tempoUpstreamAndDownstream, - ID_POINT , ID_POINT_Z, - HEIGHT_FIELD , U, - V , W, - REF_HEIGHT_FIELD , initializedWindFactorTable, - DataUtil.createIndex(tableName=tempoUpstreamAndDownstream, - fieldName=[ID_POINT, ID_POINT_Z], - isSpatial=False))) + """).format( + tempoPrioritiesWeightedAllPlusBack, + tempoUpstreamAndDownstream, + ID_POINT, + ID_POINT_Z, + HEIGHT_FIELD, + U, + V, + W, + REF_HEIGHT_FIELD, + initializedWindFactorTable, + DataUtil.createIndex( + tableName=tempoUpstreamAndDownstream, + fieldName=[ID_POINT, ID_POINT_Z], + isSpatial=False, + ), + ) + ) if not DEBUG: # Remove intermediate tables - cursor.execute(""" + cursor.execute( + """ DROP TABLE IF EXISTS {0} - """.format(",".join([tempoUpstreamAndDownstream, - tempoPrioritiesWeighted, - tempoPrioritiesWeightedAll, - tempoPrioritiesAll, - tempoBackwardWeights, - upstreamBackPrioritiesTempoTable, - ",".join(list(dicBackwardWeighted.values())), - tempoPrioritiesWeightedAllPlusBack]))) - + """.format( + ",".join( + [ + tempoUpstreamAndDownstream, + tempoPrioritiesWeighted, + tempoPrioritiesWeightedAll, + tempoPrioritiesAll, + tempoBackwardWeights, + upstreamBackPrioritiesTempoTable, + ",".join(list(dicBackwardWeighted.values())), + tempoPrioritiesWeightedAllPlusBack, + ] + ) + ) + ) + return initializedWindFactorTable -def manageUpstreamSuperimposition(cursor, - dicAllWeightFactorsTables, - upstreamPriorityTables = UPSTREAM_PRIORITY_TABLES, - upstreamWeightingTables = UPSTREAM_WEIGHTING_TABLES, - upstreamWeightingInterRules = UPSTREAM_WEIGHTING_INTER_RULES, - upstreamWeightingIntraRules = UPSTREAM_WEIGHTING_INTRA_RULES, - backward = False, - prefix = PREFIX_NAME): - """ Keep only one value per 3D point, dealing with superimposition from + +def manageUpstreamSuperimposition( + cursor, + dicAllWeightFactorsTables, + upstreamPriorityTables=UPSTREAM_PRIORITY_TABLES, + upstreamWeightingTables=UPSTREAM_WEIGHTING_TABLES, + upstreamWeightingInterRules=UPSTREAM_WEIGHTING_INTER_RULES, + upstreamWeightingIntraRules=UPSTREAM_WEIGHTING_INTRA_RULES, + backward=False, + prefix=PREFIX_NAME, +): + """Keep only one value per 3D point, dealing with superimposition from different "all except downstream weighting tables". Can be applied for forward or backward wind zones. It is performed in three steps: @@ -2295,9 +3053,9 @@ def manageUpstreamSuperimposition(cursor, 3. a zone priority order (set in 'upstreamPriorityTables') - apply a weighting due to some upstream zones (such as wake zones) - Parameters - _ _ _ _ _ _ _ _ _ _ - + Parameters + _ _ _ _ _ _ _ _ _ _ + cursor: conn.cursor A cursor object, used to perform spatial SQL queries dicAllWeightFactorsTables: Dictionary of Rockle zone tables @@ -2305,11 +3063,11 @@ def manageUpstreamSuperimposition(cursor, the name of the table containing points corresponding to the zone upstreamPriorityTables: pd.DataFrame, default UPSTREAM_PRIORITY_TABLES Defines which zones should be used in the priority algorithm and - set priorities (column "priority") when the zone comes from a same + set priorities (column "priority") when the zone comes from a same upstream obstacle of same height. Also contains a column "ref_height" to set by which wind speed height the weigthing factor should be multiplied. The following values are possible: - -> 1: "upstream building height", + -> 1: "upstream building height", -> 2: "Reference wind speed measurement height Z_REF", -> 3: "building height") upstreamWeightingTables: list, default UPSTREAM_WEIGHTING_TABLES @@ -2317,84 +3075,88 @@ def manageUpstreamSuperimposition(cursor, upstreamWeightingInterRules: String, default UPSTREAM_WEIGHTING_INTER_RULES Defines how to deal with a point having several values from a same upstream weighting zone - -> "upstream": use values from the most upstream and upper + -> "upstream": use values from the most upstream and upper obstacles upstreamWeightingIntraRules: String, default UPSTREAM_WEIGHTING_INTRA_RULES Defines how to deal with a point having several values from several upstream weighting zones - -> "upstream": use values from the most upstream and upper + -> "upstream": use values from the most upstream and upper obstacles backward: boolean, default False Whether or not the zones to deal with are backward zones prefix: String, default PREFIX_NAME Prefix to add to the output table name - - Returns - _ _ _ _ _ _ _ _ _ _ - + + Returns + _ _ _ _ _ _ _ _ _ _ + upstreamWindFactorTable: String Name of the table containing the weighting factor for each 3D point (one value per point, means superimposition have been used)""" print("Deals with superimposition (keeps only 1 value per 3D point)") - + # Output base name outputBaseName = "UPSTREAM_WIND_FACTOR_TABLE" if backward: outputBaseName = "BACKWARD_" + outputBaseName - + # Name of the output table - upstreamWindFactorTable = DataUtil.prefix(outputBaseName, - prefix = prefix) - + upstreamWindFactorTable = DataUtil.prefix(outputBaseName, prefix=prefix) + # Temporary tables (and prefix for temporary tables) tempoPrioritiesAll = DataUtil.postfix("TEMPO_PRIORITY_ALL") tempoPrioritiesWeighted = DataUtil.postfix("TEMPO_PRIORITY_WEIGHTED") - - + # Identify the points to keep for duplicates in upstream weigthing if backward: upstreamWeight = False else: upstreamWeight = True - upstreamWeightingTempoTable = \ - identifyUpstreamer(cursor = cursor, - dicAllWeightFactorsTables = dicAllWeightFactorsTables, - tablesToConsider = upstreamWeightingTables, - prefix = "TEMPO_WEIGHTING", - upstream = upstreamWeight, - weightingZone = True) - + upstreamWeightingTempoTable = identifyUpstreamer( + cursor=cursor, + dicAllWeightFactorsTables=dicAllWeightFactorsTables, + tablesToConsider=upstreamWeightingTables, + prefix="TEMPO_WEIGHTING", + upstream=upstreamWeight, + weightingZone=True, + ) + # Identify the points to keep to solve duplicated points in upstream priorities # (except zones being in weighting) if backward: upstreamPriorities = True else: upstreamPriorities = True - upstreamPrioritiesTempoTable = \ - identifyUpstreamer(cursor = cursor, - dicAllWeightFactorsTables = dicAllWeightFactorsTables, - tablesToConsider = upstreamPriorityTables\ - .reindex(upstreamPriorityTables.index\ - .difference(pd.Index(upstreamWeightingTables))), - prefix = "TEMPO_PRIORITIES", - upstream = upstreamPriorities) - + upstreamPrioritiesTempoTable = identifyUpstreamer( + cursor=cursor, + dicAllWeightFactorsTables=dicAllWeightFactorsTables, + tablesToConsider=upstreamPriorityTables.reindex( + upstreamPriorityTables.index.difference( + pd.Index(upstreamWeightingTables) + ) + ), + prefix="TEMPO_PRIORITIES", + upstream=upstreamPriorities, + ) + # Identify the points to keep to solve duplicated points in upstream priorities # (only zones being in weighting - ie the most downstream wake zones...) if backward: upstreamPrioritiesWeight = True else: upstreamPrioritiesWeight = False - upstreamPrioritiesWeightTempoTable = \ - identifyUpstreamer(cursor = cursor, - dicAllWeightFactorsTables = dicAllWeightFactorsTables, - tablesToConsider = upstreamWeightingTables, - prefix = "TEMPO_PRIORITIES_WEIGHT", - upstream = upstreamPrioritiesWeight) + upstreamPrioritiesWeightTempoTable = identifyUpstreamer( + cursor=cursor, + dicAllWeightFactorsTables=dicAllWeightFactorsTables, + tablesToConsider=upstreamWeightingTables, + prefix="TEMPO_PRIORITIES_WEIGHT", + upstream=upstreamPrioritiesWeight, + ) # Join the points from the priority table to the downstreamest points of the # weighting table - cursor.execute(safe(""" + cursor.execute( + safe(""" {12}; {13}; {14}; @@ -2411,29 +3173,48 @@ def manageUpstreamSuperimposition(cursor, FROM {0} AS a LEFT JOIN {1} AS b ON a.{2} = b.{2} AND a.{3} = b.{3} WHERE b.{2} IS NULL AND b.{3} IS NULL - """).format( upstreamPrioritiesWeightTempoTable , upstreamPrioritiesTempoTable, - ID_POINT , ID_POINT_Z, - HEIGHT_FIELD , Y_WALL, - U , V, - W , REF_HEIGHT_FIELD, - tempoPrioritiesAll , REF_HEIGHT_UPSTREAM_WEIGHTING, - DataUtil.createIndex(tableName=upstreamPrioritiesWeightTempoTable, - fieldName=ID_POINT, - isSpatial=False), - DataUtil.createIndex(tableName=upstreamPrioritiesWeightTempoTable, - fieldName=ID_POINT_Z, - isSpatial=False), - DataUtil.createIndex(tableName=upstreamPrioritiesTempoTable, - fieldName=ID_POINT, - isSpatial=False), - DataUtil.createIndex(tableName=upstreamPrioritiesTempoTable, - fieldName=ID_POINT_Z, - isSpatial=False), - IS_UPSTREAM_FIELD , IS_UPSTREAM_UPSTREAM_WEIGHTING)) - + """).format( + upstreamPrioritiesWeightTempoTable, + upstreamPrioritiesTempoTable, + ID_POINT, + ID_POINT_Z, + HEIGHT_FIELD, + Y_WALL, + U, + V, + W, + REF_HEIGHT_FIELD, + tempoPrioritiesAll, + REF_HEIGHT_UPSTREAM_WEIGHTING, + DataUtil.createIndex( + tableName=upstreamPrioritiesWeightTempoTable, + fieldName=ID_POINT, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=upstreamPrioritiesWeightTempoTable, + fieldName=ID_POINT_Z, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=upstreamPrioritiesTempoTable, + fieldName=ID_POINT, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=upstreamPrioritiesTempoTable, + fieldName=ID_POINT_Z, + isSpatial=False, + ), + IS_UPSTREAM_FIELD, + IS_UPSTREAM_UPSTREAM_WEIGHTING, + ) + ) + # Weight the wind speeds factors of the upstream priorities when the # weighting factors comes from more upstream and a higher position - cursor.execute(safe(""" + cursor.execute( + safe(""" {12}; {13}; {14}; @@ -2456,40 +3237,64 @@ def manageUpstreamSuperimposition(cursor, FROM {0} AS a LEFT JOIN {1} AS b ON a.{2} = b.{2} AND a.{3} = b.{3} WHERE b.{2} IS NULL AND b.{3} IS NULL - """).format( upstreamWeightingTempoTable , tempoPrioritiesAll, - ID_POINT , ID_POINT_Z, - HEIGHT_FIELD , Y_WALL, - U , V, - W , REF_HEIGHT_FIELD, - tempoPrioritiesWeighted , REF_HEIGHT_UPSTREAM_WEIGHTING, - DataUtil.createIndex(tableName=upstreamWeightingTempoTable, - fieldName=ID_POINT, - isSpatial=False), - DataUtil.createIndex(tableName=upstreamWeightingTempoTable, - fieldName=ID_POINT_Z, - isSpatial=False), - DataUtil.createIndex(tableName=upstreamWeightingTempoTable, - fieldName=HEIGHT_FIELD, - isSpatial=False), - DataUtil.createIndex(tableName=upstreamWeightingTempoTable, - fieldName=Y_WALL, - isSpatial=False), - DataUtil.createIndex(tableName=tempoPrioritiesAll, - fieldName=ID_POINT, - isSpatial=False), - DataUtil.createIndex(tableName=tempoPrioritiesAll, - fieldName=ID_POINT_Z, - isSpatial=False), - DataUtil.createIndex(tableName=tempoPrioritiesAll, - fieldName=HEIGHT_FIELD, - isSpatial=False), - DataUtil.createIndex(tableName=tempoPrioritiesAll, - fieldName=Y_WALL, - isSpatial=False), - IS_UPSTREAM_FIELD)) - + """).format( + upstreamWeightingTempoTable, + tempoPrioritiesAll, + ID_POINT, + ID_POINT_Z, + HEIGHT_FIELD, + Y_WALL, + U, + V, + W, + REF_HEIGHT_FIELD, + tempoPrioritiesWeighted, + REF_HEIGHT_UPSTREAM_WEIGHTING, + DataUtil.createIndex( + tableName=upstreamWeightingTempoTable, + fieldName=ID_POINT, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=upstreamWeightingTempoTable, + fieldName=ID_POINT_Z, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=upstreamWeightingTempoTable, + fieldName=HEIGHT_FIELD, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=upstreamWeightingTempoTable, + fieldName=Y_WALL, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=tempoPrioritiesAll, + fieldName=ID_POINT, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=tempoPrioritiesAll, + fieldName=ID_POINT_Z, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=tempoPrioritiesAll, + fieldName=HEIGHT_FIELD, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=tempoPrioritiesAll, fieldName=Y_WALL, isSpatial=False + ), + IS_UPSTREAM_FIELD, + ) + ) + # Join the upstream priority weigthted points to the upstream priority non-weighted ones - cursor.execute(safe(""" + cursor.execute( + safe(""" {10}; {11}; {12}; @@ -2503,77 +3308,96 @@ def manageUpstreamSuperimposition(cursor, UNION ALL SELECT {2}, {3}, {4}, {5}, {6}, {7}, {8} FROM {1} - """).format( tempoPrioritiesAll , tempoPrioritiesWeighted, - ID_POINT , ID_POINT_Z, - HEIGHT_FIELD , U, - V , W, - REF_HEIGHT_FIELD , upstreamWindFactorTable, - DataUtil.createIndex(tableName=tempoPrioritiesAll, - fieldName=ID_POINT, - isSpatial=False), - DataUtil.createIndex(tableName=tempoPrioritiesAll, - fieldName=ID_POINT_Z, - isSpatial=False), - DataUtil.createIndex(tableName=tempoPrioritiesWeighted, - fieldName=ID_POINT, - isSpatial=False), - DataUtil.createIndex(tableName=tempoPrioritiesWeighted, - fieldName=ID_POINT_Z, - isSpatial=False))) + """).format( + tempoPrioritiesAll, + tempoPrioritiesWeighted, + ID_POINT, + ID_POINT_Z, + HEIGHT_FIELD, + U, + V, + W, + REF_HEIGHT_FIELD, + upstreamWindFactorTable, + DataUtil.createIndex( + tableName=tempoPrioritiesAll, + fieldName=ID_POINT, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=tempoPrioritiesAll, + fieldName=ID_POINT_Z, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=tempoPrioritiesWeighted, + fieldName=ID_POINT, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=tempoPrioritiesWeighted, + fieldName=ID_POINT_Z, + isSpatial=False, + ), + ) + ) return upstreamWindFactorTable - -def identifyUpstreamer( cursor, - dicAllWeightFactorsTables, - tablesToConsider, - prefix = PREFIX_NAME, - upstream = True, - weightingZone = False): - """ If a point is covered by several zones, keep the value only from - a single zone based on the following priorities: - 1. the most upstream zone (if equal, use the next priority) - 2. the upper obstacle (if equal, use the next priority) - 3. (optionnally) a zone priority order set in 'tablesToConsider' - Note that the most downstream zone and lower obstacles (contrary of - conditions 1 - and 2) - may be conserved if upstream is set to False. - - Parameters - _ _ _ _ _ _ _ _ _ _ - - cursor: conn.cursor - A cursor object, used to perform spatial SQL queries - dicAllWeightFactorsTables: Dictionary of vegetation Rockle zone tables - Dictionary having as key the type of vegetation Rockle zone and as value - the name of the table containing points corresponding to the zone - tablesToConsider: list (or pd.DataFrame if the 3rd step should be performed) - Defines which zones should be used in the upstreamer identification. - If priorities should be defined (in case the most upstream and the - upper obstacle are not sufficient), then a dataframe containing - the "priority" and "ref_height" columns should be passed. The - column "ref_height" refers to the wind speed height by which - the weigthing factor should be multiplied. - The following values are possible: - -> 1: "upstream building height", - -> 2: "Reference wind speed measurement height Z_REF", - -> 3: "building height" - prefix: String, default PREFIX_NAME - Prefix to add to the output table name - upstream: Boolean, default True - If False, the downstreamest zone coming from the lowest obstacles - are used for calculation - weightingZone: boolean, default False - If True, use wind factor used for weighting (U_WEIGHT, V_WEIGHT, W_WEIGHT) - instead of normal wind factors (U, V, W) - - Returns - _ _ _ _ _ _ _ _ _ _ - - uniqueValuePerPointTable: String - Name of the table containing one value per point (without duplicate)""" + + +def identifyUpstreamer( + cursor, + dicAllWeightFactorsTables, + tablesToConsider, + prefix=PREFIX_NAME, + upstream=True, + weightingZone=False, +): + """If a point is covered by several zones, keep the value only from + a single zone based on the following priorities: + 1. the most upstream zone (if equal, use the next priority) + 2. the upper obstacle (if equal, use the next priority) + 3. (optionnally) a zone priority order set in 'tablesToConsider' + Note that the most downstream zone and lower obstacles (contrary of + conditions 1 + and 2) + may be conserved if upstream is set to False. + + Parameters + _ _ _ _ _ _ _ _ _ _ + + cursor: conn.cursor + A cursor object, used to perform spatial SQL queries + dicAllWeightFactorsTables: Dictionary of vegetation Rockle zone tables + Dictionary having as key the type of vegetation Rockle zone and as value + the name of the table containing points corresponding to the zone + tablesToConsider: list (or pd.DataFrame if the 3rd step should be performed) + Defines which zones should be used in the upstreamer identification. + If priorities should be defined (in case the most upstream and the + upper obstacle are not sufficient), then a dataframe containing + the "priority" and "ref_height" columns should be passed. The + column "ref_height" refers to the wind speed height by which + the weigthing factor should be multiplied. + The following values are possible: + -> 1: "upstream building height", + -> 2: "Reference wind speed measurement height Z_REF", + -> 3: "building height" + prefix: String, default PREFIX_NAME + Prefix to add to the output table name + upstream: Boolean, default True + If False, the downstreamest zone coming from the lowest obstacles + are used for calculation + weightingZone: boolean, default False + If True, use wind factor used for weighting (U_WEIGHT, V_WEIGHT, W_WEIGHT) + instead of normal wind factors (U, V, W) + + Returns + _ _ _ _ _ _ _ _ _ _ + + uniqueValuePerPointTable: String + Name of the table containing one value per point (without duplicate)""" print("Identify upstreamer points in {0} table".format(prefix)) - + # Wind factors if weightingZone: u_factor = U_WEIGHT @@ -2584,85 +3408,107 @@ def identifyUpstreamer( cursor, v_factor = V w_factor = W wind_factor_names = {u_factor: U, v_factor: V, w_factor: W} - + # Output base name outputBaseName = "UNIQUE_3D" - + # Name of the output table - uniqueValuePerPointTable = DataUtil.prefix(outputBaseName, prefix = prefix) - + uniqueValuePerPointTable = DataUtil.prefix(outputBaseName, prefix=prefix) + # Temporary tables (and prefix for temporary tables) - tempoAllPointsTable = DataUtil.postfix("TEMPO_3D_ALL", suffix = prefix) - tempoUniquePointsTable = DataUtil.postfix("TEMPO_3D_UNIQUE", suffix = prefix) - + tempoAllPointsTable = DataUtil.postfix("TEMPO_3D_ALL", suffix=prefix) + tempoUniquePointsTable = DataUtil.postfix("TEMPO_3D_UNIQUE", suffix=prefix) + # If priorities should be used, recover list of tables and add columns to keep - if(type(tablesToConsider) == type(pd.DataFrame())): + if type(tablesToConsider) == type(pd.DataFrame()): listOfTables = tablesToConsider.index - defineCol2Add = "{0} INTEGER, {1} INTEGER, {2} INTEGER,".format(REF_HEIGHT_FIELD, - PRIORITY_FIELD, - IS_UPSTREAM_FIELD) + defineCol2Add = "{0} INTEGER, {1} INTEGER, {2} INTEGER,".format( + REF_HEIGHT_FIELD, PRIORITY_FIELD, IS_UPSTREAM_FIELD + ) else: listOfTables = tablesToConsider defineCol2Add = "" - + # Set columns to keep in the final table selectQueryDownstream = {} considerPrioritiesQuery = "" - + for t in listOfTables: selectQueryDownstream[t] = """ SELECT CAST((row_number() over()) as Integer) AS {0}, {1}, {2}, {3}, {4}, - """.format( ID_3D_POINT , ID_POINT, - ID_POINT_Z , HEIGHT_FIELD, - Y_WALL) - + """.format( + ID_3D_POINT, ID_POINT, ID_POINT_Z, HEIGHT_FIELD, Y_WALL + ) + # If priorities should be used, add columns to keep - if(type(tablesToConsider) == type(pd.DataFrame())): + if type(tablesToConsider) == type(pd.DataFrame()): selectQueryDownstream[t] += """ {0} AS {2}, {1} AS {3}, {4} AS {5}, - """.format( tablesToConsider.loc[t, REF_HEIGHT_FIELD], - tablesToConsider.loc[t, PRIORITY_FIELD], - REF_HEIGHT_FIELD, - PRIORITY_FIELD, - tablesToConsider.loc[t, IS_UPSTREAM_FIELD], - IS_UPSTREAM_FIELD) - - # Add the priority field as a decision criteria if two zones have + """.format( + tablesToConsider.loc[t, REF_HEIGHT_FIELD], + tablesToConsider.loc[t, PRIORITY_FIELD], + REF_HEIGHT_FIELD, + PRIORITY_FIELD, + tablesToConsider.loc[t, IS_UPSTREAM_FIELD], + IS_UPSTREAM_FIELD, + ) + + # Add the priority field as a decision criteria if two zones have # the same upstream obstacle considerPrioritiesQuery = ", b.{0} ASC".format(PRIORITY_FIELD) - + # Set to null wind speed factor for axis not set by upstream zones - columns = DataUtil.getColumns(cursor = cursor, tableName = dicAllWeightFactorsTables[t]) + columns = DataUtil.getColumns( + cursor=cursor, tableName=dicAllWeightFactorsTables[t] + ) for i in wind_factor_names: if i in columns: - selectQueryDownstream[t] += " {0} AS {1}, ".format(i, wind_factor_names[i]) + selectQueryDownstream[t] += " {0} AS {1}, ".format( + i, wind_factor_names[i] + ) else: - selectQueryDownstream[t] += " NULL AS {0}, ".format(wind_factor_names[i]) - selectQueryDownstream[t] = selectQueryDownstream[t][0:-2]+" FROM "+dicAllWeightFactorsTables[t] - + selectQueryDownstream[t] += " NULL AS {0}, ".format( + wind_factor_names[i] + ) + selectQueryDownstream[t] = ( + selectQueryDownstream[t][0:-2] + + " FROM " + + dicAllWeightFactorsTables[t] + ) + # Gather all data for the upstream weighting into a same table - cursor.execute(safe(""" + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0}({1} BIGINT AUTO_INCREMENT, {2} INTEGER, {3} INTEGER, {4} INTEGER, {5} INTEGER, {6} {7} DOUBLE, {8} DOUBLE, {9} DOUBLE) AS {10} - """).format( tempoAllPointsTable , ID_3D_POINT, - ID_POINT , ID_POINT_Z, - HEIGHT_FIELD , Y_WALL, - defineCol2Add , U, - V , W, - " UNION ALL ".join(selectQueryDownstream.values()))) - + """).format( + tempoAllPointsTable, + ID_3D_POINT, + ID_POINT, + ID_POINT_Z, + HEIGHT_FIELD, + Y_WALL, + defineCol2Add, + U, + V, + W, + " UNION ALL ".join(selectQueryDownstream.values()), + ) + ) + if upstream: order = "DESC" else: order = "ASC" # Identify which point should be conserved in the upstream weighting table - cursor.execute(safe(""" + cursor.execute( + safe(""" {7}; {8}; {9}; @@ -2675,26 +3521,42 @@ def identifyUpstreamer( cursor, WHERE a.{2} = b.{2} AND a.{3} = b.{3} ORDER BY (b.{5}, b.{4}) {11} {12} LIMIT 1) AS {1} FROM {0} AS a; - """).format( tempoAllPointsTable , ID_3D_POINT, - ID_POINT , ID_POINT_Z, - HEIGHT_FIELD , Y_WALL, - tempoUniquePointsTable, - DataUtil.createIndex(tableName=tempoAllPointsTable, - fieldName=ID_POINT, - isSpatial=False), - DataUtil.createIndex(tableName=tempoAllPointsTable, - fieldName=ID_POINT_Z, - isSpatial=False), - DataUtil.createIndex(tableName=tempoAllPointsTable, - fieldName=HEIGHT_FIELD, - isSpatial=False), - DataUtil.createIndex(tableName=tempoAllPointsTable, - fieldName=Y_WALL, - isSpatial=False), - order , considerPrioritiesQuery)) - + """).format( + tempoAllPointsTable, + ID_3D_POINT, + ID_POINT, + ID_POINT_Z, + HEIGHT_FIELD, + Y_WALL, + tempoUniquePointsTable, + DataUtil.createIndex( + tableName=tempoAllPointsTable, + fieldName=ID_POINT, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=tempoAllPointsTable, + fieldName=ID_POINT_Z, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=tempoAllPointsTable, + fieldName=HEIGHT_FIELD, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=tempoAllPointsTable, + fieldName=Y_WALL, + isSpatial=False, + ), + order, + considerPrioritiesQuery, + ) + ) + # Recover the useful informations from the unique points kept - cursor.execute(safe(""" + cursor.execute( + safe(""" {idx1}; {idx2}; DROP TABLE IF EXISTS {unique_table}; @@ -2704,74 +3566,84 @@ def identifyUpstreamer( cursor, ON a.{id_3d} = b.{id_3d} AND a.{id_p} = b.{id_p} """).format( - idx1=DataUtil.createIndex(tableName=tempoAllPointsTable, - fieldName=[ID_3D_POINT, ID_POINT], - isSpatial=False), - idx2=DataUtil.createIndex(tableName=tempoUniquePointsTable, - fieldName=[ID_3D_POINT, ID_POINT], - isSpatial=False), - unique_table=uniqueValuePerPointTable, - all_points_table=tempoAllPointsTable, - unique_points_table=tempoUniquePointsTable, - id_3d=ID_3D_POINT, - id_p=ID_POINT - )) + idx1=DataUtil.createIndex( + tableName=tempoAllPointsTable, + fieldName=[ID_3D_POINT, ID_POINT], + isSpatial=False, + ), + idx2=DataUtil.createIndex( + tableName=tempoUniquePointsTable, + fieldName=[ID_3D_POINT, ID_POINT], + isSpatial=False, + ), + unique_table=uniqueValuePerPointTable, + all_points_table=tempoAllPointsTable, + unique_points_table=tempoUniquePointsTable, + id_3d=ID_3D_POINT, + id_p=ID_POINT, + ) + ) if not DEBUG: # Remove intermediate tables - cursor.execute(""" + cursor.execute( + """ DROP TABLE IF EXISTS {0} - """.format(",".join([tempoAllPointsTable, - tempoUniquePointsTable]))) - + """.format( + ",".join([tempoAllPointsTable, tempoUniquePointsTable]) + ) + ) + return uniqueValuePerPointTable -def getVerticalProfile( cursor, - pointHeightList, - z0, - profileType = PROFILE_TYPE, - V_ref=V_REF, - z_ref=Z_REF, - prefix = PREFIX_NAME, - **kwargs): - """ Get the horizontal wind speed of a set of point heights. The +def getVerticalProfile( + cursor, + pointHeightList, + z0, + profileType=PROFILE_TYPE, + V_ref=V_REF, + z_ref=Z_REF, + prefix=PREFIX_NAME, + **kwargs, +): + """Get the horizontal wind speed of a set of point heights. The wind speed profile used to set wind speed value can be: - power-law: proposed by Kuttler (2000) and initially used in QUIC-URB (Pardyjak et Brown, 2003), - urban: exponential below mean building height and log otherwise (proposed by Cionco, 1972 with constant a defined by MacDonald, 2000 and used in Nelson et al., 2007) - - user: the profile is defined by the user (need to pass the + - user: the profile is defined by the user (need to pass the 'verticalProfileFile' parameter) - + Note that: - the exponent p of the power-law is calculated according to the formulae p = 0.12*z0+0.18 (Matzarakis et al. 2009), - - the attenuation coefficient for the exponential wind profile is calculated according to + - the attenuation coefficient for the exponential wind profile is calculated according to A = 9.6 * lambda_f (ratio of frontal and plot area - Hanna and Britter, 2002) - the stability is not taken into account yet - + References: - + Kuttler, Wilhelm. "Stadtklima." Umweltwissenschaften und Schadstoff-Forschung 16.3 (2004): 187-199. - Matzarakis, A. and Endler, C., 2009: Physiologically Equivalent - Temperature and Climate Change in Freiburg. Eighth Symposium on the - Urban Environment. American Meteorological Society, Phoenix/Arizona, + Matzarakis, A. and Endler, C., 2009: Physiologically Equivalent + Temperature and Climate Change in Freiburg. Eighth Symposium on the + Urban Environment. American Meteorological Society, Phoenix/Arizona, 10. to 15. January 2009 4(2), 1–8. - Nelson, M. A., B. Addepalli, D. Boswell, M. J. Brown, et Los + Nelson, M. A., B. Addepalli, D. Boswell, M. J. Brown, et Los Alamos National Laboratory. « QUIC Start Guide (V.45). », 2007. http://permalink.lanl.gov/object/tr?what=info:lanl-repo/lareport/LA-UR-07-2799. Pardyjak, Eric R, et Michael Brown. « QUIC-URB v. 1.1: Theory and User’s Guide ». Los Alamos National Laboratory, Los Alamos, NM, 2003. - - Parameters - _ _ _ _ _ _ _ _ _ _ - + + Parameters + _ _ _ _ _ _ _ _ _ _ + cursor: conn.cursor A cursor object, used to perform spatial SQL queries pointHeightList: list @@ -2798,67 +3670,113 @@ def getVerticalProfile( cursor, (optional) verticalProfileFile: string Path of the file where is stored the vertical wind profile (no header, comma separated, first column is the height, second column is the wind speed) - - Returns - _ _ _ _ _ _ _ _ _ _ - + + Returns + _ _ _ _ _ _ _ _ _ _ + verticalWindProfile: pd.DataFrame - Values of the wind speed and height from ground for each vertical level""" - # Get kwargs arguments - d = kwargs.get('d', None) - H = kwargs.get('H', None) - lambda_f = kwargs.get('lambda_f', None) - verticalProfileFile = kwargs.get('verticalProfileFile', None) + Values of the wind speed and height from ground for each vertical level + """ + # Get kwargs arguments + d = kwargs.get("d", None) + H = kwargs.get("H", None) + lambda_f = kwargs.get("lambda_f", None) + verticalProfileFile = kwargs.get("verticalProfileFile", None) if profileType == "power": - verticalWindProfile = pd.Series([V_ref * (z / z_ref) ** (0.12 * z0 + 0.18) - for z in pointHeightList], - index = pointHeightList) + verticalWindProfile = pd.Series( + [ + V_ref * (z / z_ref) ** (0.12 * z0 + 0.18) + for z in pointHeightList + ], + index=pointHeightList, + ) elif profileType == "urban": A = 9.6 * lambda_f pointHeightIndex = pd.Index(pointHeightList) pointHeighCanopy = pointHeightIndex[pointHeightIndex < H] pointHeighAbove = pointHeightIndex[pointHeightIndex >= H] speedAtCanopyHeight = V_ref * np.log((H - d) / z0) / np.log(z_ref / z0) - verticalProfileWithin = pd.Series([speedAtCanopyHeight * np.exp(A * (z / H - 1)) - for z in pointHeighCanopy], - index = pointHeighCanopy) - verticalProfileAbove = pd.Series([V_ref * np.log((z - d) / z0) / np.log(z_ref / z0) - for z in pointHeighAbove], - index = pointHeighAbove) - verticalWindProfile = pd.concat( - [verticalProfileWithin, verticalProfileAbove[verticalProfileAbove > verticalProfileWithin.max()]], - ignore_index=True).reindex(pointHeightIndex).interpolate(method="index") - + verticalProfileWithin = pd.Series( + [ + speedAtCanopyHeight * np.exp(A * (z / H - 1)) + for z in pointHeighCanopy + ], + index=pointHeighCanopy, + ) + verticalProfileAbove = pd.Series( + [ + V_ref * np.log((z - d) / z0) / np.log(z_ref / z0) + for z in pointHeighAbove + ], + index=pointHeighAbove, + ) + verticalWindProfile = ( + pd.concat( + [ + verticalProfileWithin, + verticalProfileAbove[ + verticalProfileAbove > verticalProfileWithin.max() + ], + ], + ignore_index=True, + ) + .reindex(pointHeightIndex) + .interpolate(method="index") + ) + elif profileType == "user": pointHeightIndex = pd.Index(pointHeightList) - verticalWindProfile = pd.read_csv(verticalProfileFile, header = None, - index_col = 0, names = ["z", "v"], - dtype = float)["v"] - verticalWindProfile = verticalWindProfile.reindex(pointHeightIndex\ - .union(verticalWindProfile.index)) + verticalWindProfile = pd.read_csv( + verticalProfileFile, + header=None, + index_col=0, + names=["z", "v"], + dtype=float, + )["v"] + verticalWindProfile = verticalWindProfile.reindex( + pointHeightIndex.union(verticalWindProfile.index) + ) verticalWindProfile.loc[0] = 0 - verticalWindProfile = verticalWindProfile.sort_values().interpolate(method = "polynomial", - order = 1).reindex(pointHeightIndex) - + verticalWindProfile = ( + verticalWindProfile.sort_values() + .interpolate(method="polynomial", order=1) + .reindex(pointHeightIndex) + ) + # Add the height from ground as column instead of index - verticalWindProfile.sort_index(inplace = True) - verticalWindProfile = pd.DataFrame({HORIZ_WIND_SPEED : verticalWindProfile.values, - Z: verticalWindProfile.index}, - index = range(1, verticalWindProfile.size + 1)) - + verticalWindProfile.sort_index(inplace=True) + verticalWindProfile = pd.DataFrame( + { + HORIZ_WIND_SPEED: verticalWindProfile.values, + Z: verticalWindProfile.index, + }, + index=range(1, verticalWindProfile.size + 1), + ) + return verticalWindProfile -def setInitialWindField(cursor, initializedWindFactorTable, gridPoint, - df_gridBuil, z0, sketchHeight, profileType = PROFILE_TYPE, - meshSize = MESH_SIZE, dz = DZ, z_ref = Z_REF, - V_ref = V_REF, tempoDirectory = TEMPO_DIRECTORY, - **kwargs): - """ Set the initial 3D wind speed according to the wind speed factor in + +def setInitialWindField( + cursor, + initializedWindFactorTable, + gridPoint, + df_gridBuil, + z0, + sketchHeight, + profileType=PROFILE_TYPE, + meshSize=MESH_SIZE, + dz=DZ, + z_ref=Z_REF, + V_ref=V_REF, + tempoDirectory=TEMPO_DIRECTORY, + **kwargs, +): + """Set the initial 3D wind speed according to the wind speed factor in the Röckle zones and to the initial vertical wind speed profile. - - Parameters - _ _ _ _ _ _ _ _ _ _ - + + Parameters + _ _ _ _ _ _ _ _ _ _ + cursor: conn.cursor A cursor object, used to perform spatial SQL queries initializedWindFactorTable: String @@ -2898,14 +3816,14 @@ def setInitialWindField(cursor, initializedWindFactorTable, gridPoint, Value of the study area frontal density (only if profileType = "urban") (optional) verticalProfileFile: string Path of the file where is stored the vertical wind profile - (no header, comma separated, first column is the height, + (no header, comma separated, first column is the height, second column is the wind speed) - - - - Returns - _ _ _ _ _ _ _ _ _ _ - + + + + Returns + _ _ _ _ _ _ _ _ _ _ + initial3dWindSpeed: pd.DataFrame 3D wind speed value used as "first guess" in the wind solver nPoints: dictionary @@ -2913,46 +3831,65 @@ def setInitialWindField(cursor, initializedWindFactorTable, gridPoint, number of grid point in the corresponding axis as value verticalWindSpeedProfile: pd.Series Initial wind speed profile along a vertical axis z""" - + print("Set the initial 3D wind speed field") # Get kwargs arguments - d = kwargs.get('d', None) - H = kwargs.get('H', None) - lambda_f = kwargs.get('lambda_f', None) - verticalProfileFile = kwargs.get('verticalProfileFile', None) - + d = kwargs.get("d", None) + H = kwargs.get("H", None) + lambda_f = kwargs.get("lambda_f", None) + verticalProfileFile = kwargs.get("verticalProfileFile", None) + # File name of the intermediate data saved on disk initRockleFilename = "INIT_WIND_ROCKLE_ZONES.csv" - + # Temporary tables (and prefix for temporary tables) tempoVerticalProfileTable = DataUtil.postfix("TEMPO_VERTICAL_PROFILE_WIND") - tempoBuildingHeightWindTable = DataUtil.postfix("TEMPO_BUILDING_HEIGHT_WIND") - tempoZoneWindSpeedFactorTable = DataUtil.postfix("TEMPO_ZONE_WIND_SPEED_FACTOR") - + tempoBuildingHeightWindTable = DataUtil.postfix( + "TEMPO_BUILDING_HEIGHT_WIND" + ) + tempoZoneWindSpeedFactorTable = DataUtil.postfix( + "TEMPO_ZONE_WIND_SPEED_FACTOR" + ) + # Set a list of the level height and get their horizontal wind speed - levelHeightList = [i for i in np.arange(float(dz)/2, - float(dz)/2+math.trunc(sketchHeight/dz)*dz, - dz)] - verticalWindSpeedProfile = \ - getVerticalProfile( cursor = cursor, - pointHeightList = levelHeightList, - z0 = z0, - V_ref=V_ref, - z_ref=z_ref, - profileType = profileType, - d = d, - H = H, - lambda_f = lambda_f, - verticalProfileFile = verticalProfileFile) - + levelHeightList = [ + i + for i in np.arange( + float(dz) / 2, + float(dz) / 2 + math.trunc(sketchHeight / dz) * dz, + dz, + ) + ] + verticalWindSpeedProfile = getVerticalProfile( + cursor=cursor, + pointHeightList=levelHeightList, + z0=z0, + V_ref=V_ref, + z_ref=z_ref, + profileType=profileType, + d=d, + H=H, + lambda_f=lambda_f, + verticalProfileFile=verticalProfileFile, + ) + # Insert the initial vertical wind profile values into a table - valuesForEachRowProfile = [str(i)+","+str(j) for i, j in verticalWindSpeedProfile[HORIZ_WIND_SPEED].items()] - cursor.execute(safe(""" + valuesForEachRowProfile = [ + str(i) + "," + str(j) + for i, j in verticalWindSpeedProfile[HORIZ_WIND_SPEED].items() + ] + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0}({1} INTEGER, {2} DOUBLE); INSERT INTO {0} VALUES ({3}); - """).format( tempoVerticalProfileTable , ID_POINT_Z, - V ,"), (".join(valuesForEachRowProfile))) + """).format( + tempoVerticalProfileTable, + ID_POINT_Z, + V, + "), (".join(valuesForEachRowProfile), + ) + ) # Get the wind speed at each building height value... cursor.execute(safe(""" SELECT DISTINCT({0}) AS {0} @@ -2961,41 +3898,59 @@ def setInitialWindField(cursor, initializedWindFactorTable, gridPoint, """).format(HEIGHT_FIELD, initializedWindFactorTable)) buildingHeightList = cursor.fetchall() if len(buildingHeightList) > 0: - df_buildingHeightList = pd.Series(pd.DataFrame(buildingHeightList)[0].values) - buildingHeightWindSpeed = \ - getVerticalProfile( cursor = cursor, - pointHeightList = df_buildingHeightList, - z0 = z0, - V_ref=V_ref, - z_ref=z_ref, - profileType = profileType, - d = d, - H = H, - lambda_f = lambda_f, - verticalProfileFile = verticalProfileFile) - + df_buildingHeightList = pd.Series( + pd.DataFrame(buildingHeightList)[0].values + ) + buildingHeightWindSpeed = getVerticalProfile( + cursor=cursor, + pointHeightList=df_buildingHeightList, + z0=z0, + V_ref=V_ref, + z_ref=z_ref, + profileType=profileType, + d=d, + H=H, + lambda_f=lambda_f, + verticalProfileFile=verticalProfileFile, + ) + # ... and insert it into a table - valuesForEachRowBuilding = [str(i)+","+str(j) for i, j in buildingHeightWindSpeed.set_index(Z)[HORIZ_WIND_SPEED].items()] - cursor.execute(safe(""" + valuesForEachRowBuilding = [ + str(i) + "," + str(j) + for i, j in buildingHeightWindSpeed.set_index(Z)[ + HORIZ_WIND_SPEED + ].items() + ] + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0}({1} INTEGER, {2} DOUBLE); INSERT INTO {0} VALUES ({3}); - """).format( tempoBuildingHeightWindTable , HEIGHT_FIELD, - V ,"), (".join(valuesForEachRowBuilding))) + """).format( + tempoBuildingHeightWindTable, + HEIGHT_FIELD, + V, + "), (".join(valuesForEachRowBuilding), + ) + ) else: cursor.execute(safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0}({1} INTEGER, {2} DOUBLE); - """).format( tempoBuildingHeightWindTable , HEIGHT_FIELD, - V)) - + """).format(tempoBuildingHeightWindTable, HEIGHT_FIELD, V)) + if V_ref is None or z_ref is None: - V_ref = verticalWindSpeedProfile.loc[verticalWindSpeedProfile.index[-1], HORIZ_WIND_SPEED] - z_ref = verticalWindSpeedProfile.loc[verticalWindSpeedProfile.index[-1], Z] - + V_ref = verticalWindSpeedProfile.loc[ + verticalWindSpeedProfile.index[-1], HORIZ_WIND_SPEED + ] + z_ref = verticalWindSpeedProfile.loc[ + verticalWindSpeedProfile.index[-1], Z + ] + # Calculates the initial wind speed field according to each point rule # and join to the table x and y coordinates - cursor.execute(safe(""" + cursor.execute( + safe(""" {16}; {17}; {18}; @@ -3036,36 +3991,58 @@ def setInitialWindField(cursor, initializedWindFactorTable, gridPoint, FROM {4} AS a LEFT JOIN {12} AS b ON a.{5} = b.{5}', 'charset=UTF-8 fieldSeparator=,') - """).format( initializedWindFactorTable , tempoVerticalProfileTable, - ID_POINT_Z , REF_HEIGHT_FIELD, - tempoZoneWindSpeedFactorTable, ID_POINT, - V , V_ref, - U , W, - tempoBuildingHeightWindTable , HEIGHT_FIELD, - gridPoint , os.path.join(tempoDirectory, - initRockleFilename), - ID_POINT_X , ID_POINT_Y, - DataUtil.createIndex(tableName=initializedWindFactorTable, - fieldName=HEIGHT_FIELD, - isSpatial=False), - DataUtil.createIndex(tableName=initializedWindFactorTable, - fieldName=ID_POINT_Z, - isSpatial=False), - DataUtil.createIndex(tableName=initializedWindFactorTable, - fieldName=REF_HEIGHT_FIELD, - isSpatial=False), - DataUtil.createIndex(tableName=tempoVerticalProfileTable, - fieldName=ID_POINT_Z, - isSpatial=False), - DataUtil.createIndex(tableName=tempoBuildingHeightWindTable, - fieldName=HEIGHT_FIELD, - isSpatial=False), - DataUtil.createIndex(tableName=tempoZoneWindSpeedFactorTable, - fieldName=ID_POINT, - isSpatial=False), - DataUtil.createIndex(tableName=gridPoint, - fieldName=ID_POINT, - isSpatial=False))) + """).format( + initializedWindFactorTable, + tempoVerticalProfileTable, + ID_POINT_Z, + REF_HEIGHT_FIELD, + tempoZoneWindSpeedFactorTable, + ID_POINT, + V, + V_ref, + U, + W, + tempoBuildingHeightWindTable, + HEIGHT_FIELD, + gridPoint, + os.path.join(tempoDirectory, initRockleFilename), + ID_POINT_X, + ID_POINT_Y, + DataUtil.createIndex( + tableName=initializedWindFactorTable, + fieldName=HEIGHT_FIELD, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=initializedWindFactorTable, + fieldName=ID_POINT_Z, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=initializedWindFactorTable, + fieldName=REF_HEIGHT_FIELD, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=tempoVerticalProfileTable, + fieldName=ID_POINT_Z, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=tempoBuildingHeightWindTable, + fieldName=HEIGHT_FIELD, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=tempoZoneWindSpeedFactorTable, + fieldName=ID_POINT, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=gridPoint, fieldName=ID_POINT, isSpatial=False + ), + ) + ) # Get the number of grid point for each axis x, y and z cursor.execute(safe("""SELECT MAX({0}) AS ID_POINT_X, @@ -3073,94 +4050,131 @@ def setInitialWindField(cursor, initializedWindFactorTable, gridPoint, FROM {2} """).format(ID_POINT_X, ID_POINT_Y, gridPoint)) nPointsResults = cursor.fetchall() - nPoints = {X: nPointsResults[0][0] , Y: nPointsResults[0][1], - Z: verticalWindSpeedProfile.index.max()+1} - + nPoints = { + X: nPointsResults[0][0], + Y: nPointsResults[0][1], + Z: verticalWindSpeedProfile.index.max() + 1, + } + # Initialize the 3D wind speed field considering no obstacles verticalWindSpeedProfile.loc[0] = [0, 0] - verticalWindSpeedProfile.sort_index(inplace = True) - df_wind0 = pd.DataFrame({U: np.zeros(nPoints[X]*nPoints[Y]*nPoints[Z]), - V: [val for j in range(nPoints[Y]) - for i in range(nPoints[X]) - for val in verticalWindSpeedProfile[HORIZ_WIND_SPEED]], - W: np.zeros(nPoints[X] * nPoints[Y] * nPoints[Z])}, - index=pd.MultiIndex.from_product([[i for i in range(0, nPoints[X])], - [j for j in range(0, nPoints[Y])], - [k for k in range(0, nPoints[Z])]])) + verticalWindSpeedProfile.sort_index(inplace=True) + df_wind0 = pd.DataFrame( + { + U: np.zeros(nPoints[X] * nPoints[Y] * nPoints[Z]), + V: [ + val + for j in range(nPoints[Y]) + for i in range(nPoints[X]) + for val in verticalWindSpeedProfile[HORIZ_WIND_SPEED] + ], + W: np.zeros(nPoints[X] * nPoints[Y] * nPoints[Z]), + }, + index=pd.MultiIndex.from_product( + [ + [i for i in range(0, nPoints[X])], + [j for j in range(0, nPoints[Y])], + [k for k in range(0, nPoints[Z])], + ] + ), + ) # Read the wind speed near obstacles (data coming from H2GIS database) - df_wind0_rockle = pd.read_csv(os.path.join(tempoDirectory, - initRockleFilename), - header = 0, - index_col = [0, 1, 2]) - + df_wind0_rockle = pd.read_csv( + os.path.join(tempoDirectory, initRockleFilename), + header=0, + index_col=[0, 1, 2], + ) + # Update the 3D wind speed field with the initial guess near obstacles for c in df_wind0_rockle.columns: - df_wind0.loc[df_wind0_rockle[c].sort_index().dropna().index,c] = df_wind0_rockle[c].sort_index().dropna() - + df_wind0.loc[df_wind0_rockle[c].sort_index().dropna().index, c] = ( + df_wind0_rockle[c].sort_index().dropna() + ) + # Renormalize wind speed at each height to make sure there is no offset of # wind speed between the wind profile and the initialization before the balance of wind if REMOVE_INITIALIZATION_OFFSET: max_zi = df_wind0_rockle.index.get_level_values("ID_Z").max() - for z_i in verticalWindSpeedProfile.index[1:max_zi + 1]: - df_wind0.loc[idx[:,:,z_i],:] = df_wind0.loc[idx[:,:,z_i],:] \ - * verticalWindSpeedProfile.loc[z_i, HORIZ_WIND_SPEED] \ - / df_wind0.loc[idx[:,:,z_i],:].pow(2).sum(axis=1).pow(0.5).mean() - + for z_i in verticalWindSpeedProfile.index[1 : max_zi + 1]: + df_wind0.loc[idx[:, :, z_i], :] = ( + df_wind0.loc[idx[:, :, z_i], :] + * verticalWindSpeedProfile.loc[z_i, HORIZ_WIND_SPEED] + / df_wind0.loc[idx[:, :, z_i], :] + .pow(2) + .sum(axis=1) + .pow(0.5) + .mean() + ) + # Set to 0 wind speed within buildings... df_wind0.loc[df_gridBuil.index] = 0 - + if not DEBUG: # Remove intermediate tables - cursor.execute(safe(""" + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0} - """).format(",".join([tempoVerticalProfileTable, - tempoBuildingHeightWindTable, - tempoZoneWindSpeedFactorTable]))) - + """).format( + ",".join( + [ + tempoVerticalProfileTable, + tempoBuildingHeightWindTable, + tempoZoneWindSpeedFactorTable, + ] + ) + ) + ) + return df_wind0, nPoints, verticalWindSpeedProfile -def identifyBuildPoints(cursor, gridPoint, stackedBlocksWithBaseHeight, - meshSize = MESH_SIZE, dz = DZ, - tempoDirectory = TEMPO_DIRECTORY): - """ Identify grid cells intersecting buildings. - - Parameters - _ _ _ _ _ _ _ _ _ _ - - cursor: conn.cursor - A cursor object, used to perform spatial SQL queries - gridPoint: String - Name of the grid point table - stackedBlocksWithBaseHeight: String - Name of the table containing stacked blocks with block base - height - dz: float, default DZ - Resolution (in meter) of the grid in the vertical direction - tempoDirectory: String, default = TEMPO_DIRECTORY - Path of the directory where will be stored the grid points - intersecting with buildings (in order to exchange - data between H2 to Python) - - - Returns - _ _ _ _ _ _ _ _ _ _ - - df_gridBuil: pd.DataFrame - 3D multiindex corresponding to grid points intersecting buildings""" +def identifyBuildPoints( + cursor, + gridPoint, + stackedBlocksWithBaseHeight, + meshSize=MESH_SIZE, + dz=DZ, + tempoDirectory=TEMPO_DIRECTORY, +): + """Identify grid cells intersecting buildings. + + Parameters + _ _ _ _ _ _ _ _ _ _ + + cursor: conn.cursor + A cursor object, used to perform spatial SQL queries + gridPoint: String + Name of the grid point table + stackedBlocksWithBaseHeight: String + Name of the table containing stacked blocks with block base + height + dz: float, default DZ + Resolution (in meter) of the grid in the vertical direction + tempoDirectory: String, default = TEMPO_DIRECTORY + Path of the directory where will be stored the grid points + intersecting with buildings (in order to exchange + data between H2 to Python) + + + Returns + _ _ _ _ _ _ _ _ _ _ + + df_gridBuil: pd.DataFrame + 3D multiindex corresponding to grid points intersecting buildings""" print("Identify grid points intersecting buildings") - + # File name of the intermediate data saved on disk buildPointsFilename = "BUILDING_POINTS.csv" - + # Temporary tables (and prefix for temporary tables) tempoBuildPointsTable = DataUtil.postfix("BUILDING_POINTS") tempoLevelHeightPointTable = DataUtil.postfix("LEVEL_POINTS") - - # Identify 2D coordinates of points intersecting buildings - cursor.execute(safe(""" + + # Identify 2D coordinates of points intersecting buildings + cursor.execute( + safe(""" {9}; {10}; DROP TABLE IF EXISTS {0}; @@ -3168,47 +4182,68 @@ def identifyBuildPoints(cursor, gridPoint, stackedBlocksWithBaseHeight, AS SELECT a.{1}, a.{8}, b.{2}, b.{3}, b.{4} FROM {5} AS a, {6} AS b WHERE a.{7} && b.{7} AND ST_INTERSECTS(a.{7}, b.{7}) - """).format( tempoBuildPointsTable , ID_POINT_X, - ID_FIELD_STACKED_BLOCK , HEIGHT_FIELD , - BASE_HEIGHT_FIELD , gridPoint, - stackedBlocksWithBaseHeight , GEOM_FIELD, - ID_POINT_Y, - DataUtil.createIndex(tableName=gridPoint, - fieldName=GEOM_FIELD, - isSpatial=True), - DataUtil.createIndex(tableName=stackedBlocksWithBaseHeight, - fieldName=GEOM_FIELD, - isSpatial=True))) + """).format( + tempoBuildPointsTable, + ID_POINT_X, + ID_FIELD_STACKED_BLOCK, + HEIGHT_FIELD, + BASE_HEIGHT_FIELD, + gridPoint, + stackedBlocksWithBaseHeight, + GEOM_FIELD, + ID_POINT_Y, + DataUtil.createIndex( + tableName=gridPoint, fieldName=GEOM_FIELD, isSpatial=True + ), + DataUtil.createIndex( + tableName=stackedBlocksWithBaseHeight, + fieldName=GEOM_FIELD, + isSpatial=True, + ), + ) + ) # Get the maximum building height cursor.execute(safe(""" SELECT MAX({0}) AS {0} FROM {1}; """).format(HEIGHT_FIELD, stackedBlocksWithBaseHeight)) buildMaxHeight = cursor.fetchall()[0][0] - + # Set a list of the level height (and indice) which can intersect with buildings if buildMaxHeight: - levelHeightList = [str(j+1)+","+str(i) - for j, i in enumerate(np.arange(float(dz)/2, - float(dz)/2+math.trunc(buildMaxHeight/dz)*dz, - dz))] + levelHeightList = [ + str(j + 1) + "," + str(i) + for j, i in enumerate( + np.arange( + float(dz) / 2, + float(dz) / 2 + math.trunc(buildMaxHeight / dz) * dz, + dz, + ) + ) + ] # ...and insert them into a table - cursor.execute(safe(""" + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0}({1} INTEGER, {2} DOUBLE); INSERT INTO {0} VALUES ({3}); - """).format( tempoLevelHeightPointTable , ID_POINT_Z, - Z ,"), (".join(levelHeightList))) + """).format( + tempoLevelHeightPointTable, + ID_POINT_Z, + Z, + "), (".join(levelHeightList), + ) + ) else: cursor.execute(safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0}({1} INTEGER, {2} DOUBLE); - """).format( tempoLevelHeightPointTable , ID_POINT_Z, - Z)) - + """).format(tempoLevelHeightPointTable, ID_POINT_Z, Z)) + # Identify the third dimension of points intersecting buildings and save it... - cursor.execute(safe(""" + cursor.execute( + safe(""" {9}; {10}; {11}; @@ -3217,50 +4252,87 @@ def identifyBuildPoints(cursor, gridPoint, stackedBlocksWithBaseHeight, FROM {3} AS a, {4} AS b WHERE b.{5} <= a.{6} AND b.{5} > a.{7}', 'charset=UTF-8 fieldSeparator=,') - """).format( os.path.join(tempoDirectory, - buildPointsFilename) , ID_POINT_X, - ID_POINT_Z , tempoBuildPointsTable, - tempoLevelHeightPointTable , Z, - HEIGHT_FIELD , BASE_HEIGHT_FIELD, - ID_POINT_Y, - DataUtil.createIndex(tableName=tempoBuildPointsTable, - fieldName=HEIGHT_FIELD, - isSpatial=False), - DataUtil.createIndex(tableName=tempoBuildPointsTable, - fieldName=BASE_HEIGHT_FIELD, - isSpatial=False), - DataUtil.createIndex(tableName=tempoLevelHeightPointTable, - fieldName=Z, - isSpatial=False))) - + """).format( + os.path.join(tempoDirectory, buildPointsFilename), + ID_POINT_X, + ID_POINT_Z, + tempoBuildPointsTable, + tempoLevelHeightPointTable, + Z, + HEIGHT_FIELD, + BASE_HEIGHT_FIELD, + ID_POINT_Y, + DataUtil.createIndex( + tableName=tempoBuildPointsTable, + fieldName=HEIGHT_FIELD, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=tempoBuildPointsTable, + fieldName=BASE_HEIGHT_FIELD, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=tempoLevelHeightPointTable, + fieldName=Z, + isSpatial=False, + ), + ) + ) + # ...in order to load it back into Python - df_gridBuil = pd.read_csv(os.path.join(tempoDirectory, - buildPointsFilename), - header = 0, - index_col = [0, 1, 2]) - + df_gridBuil = pd.read_csv( + os.path.join(tempoDirectory, buildPointsFilename), + header=0, + index_col=[0, 1, 2], + ) + # Remove potential duplicated indexes - df_gridBuil = pd.DataFrame(index = df_gridBuil.index.drop_duplicates()) + df_gridBuil = pd.DataFrame(index=df_gridBuil.index.drop_duplicates()) # Identify the cells located near buildings - df_wall_left = df_gridBuil.index.set_levels(df_gridBuil.index.levels[0] + 1, level=0) - df_wall_right = df_gridBuil.index.set_levels(df_gridBuil.index.levels[0] - 1, level=0) - df_wall_behind = df_gridBuil.index.set_levels(df_gridBuil.index.levels[1] + 1, level=1) - df_wall_face = df_gridBuil.index.set_levels(df_gridBuil.index.levels[1] - 1, level=1) - + df_wall_left = df_gridBuil.index.set_levels( + df_gridBuil.index.levels[0] + 1, level=0 + ) + df_wall_right = df_gridBuil.index.set_levels( + df_gridBuil.index.levels[0] - 1, level=0 + ) + df_wall_behind = df_gridBuil.index.set_levels( + df_gridBuil.index.levels[1] + 1, level=1 + ) + df_wall_face = df_gridBuil.index.set_levels( + df_gridBuil.index.levels[1] - 1, level=1 + ) + # Consider as buildings points which are surrounded by 3 vertical walls (leads to numerical issues... See issue #) - ind2remove = df_wall_left.intersection(df_wall_right).intersection(df_wall_behind)\ - .union(df_wall_left.intersection(df_wall_right).intersection(df_wall_face))\ - .union(df_wall_left.intersection(df_wall_behind).intersection(df_wall_face))\ - .union(df_wall_right.intersection(df_wall_behind).intersection(df_wall_face))\ - .difference(df_gridBuil.index) + ind2remove = ( + df_wall_left.intersection(df_wall_right) + .intersection(df_wall_behind) + .union( + df_wall_left.intersection(df_wall_right).intersection(df_wall_face) + ) + .union( + df_wall_left.intersection(df_wall_behind).intersection( + df_wall_face + ) + ) + .union( + df_wall_right.intersection(df_wall_behind).intersection( + df_wall_face + ) + ) + .difference(df_gridBuil.index) + ) df_gridBuil.index = df_gridBuil.index.append(ind2remove) if not DEBUG: # Remove intermediate tables - cursor.execute(""" + cursor.execute( + """ DROP TABLE IF EXISTS {0} - """.format(",".join([tempoBuildPointsTable, - tempoLevelHeightPointTable]))) - + """.format( + ",".join([tempoBuildPointsTable, tempoLevelHeightPointTable]) + ) + ) + return df_gridBuil diff --git a/functions/URock/MainCalculation.py b/functions/URock/MainCalculation.py index 23d426a..95f6547 100644 --- a/functions/URock/MainCalculation.py +++ b/functions/URock/MainCalculation.py @@ -6,7 +6,7 @@ @author: Jérémy Bernard, University of Gothenburg """ -from .GlobalVariables import * +from .GlobalVariables import * from .DataUtil import safe @@ -25,107 +25,152 @@ from qgis.core import QgsProcessingException from shutil import rmtree -try : +try: from numba import jit except ImportError: exit("'numba' Python package is missing") -#import copy as cp +# import copy as cp from pathlib import Path import uuid import os -def main(javaEnvironmentPath, - pluginDirectory, - outputFilePath, - buildingFilePath, - srid, - outputFilename = OUTPUT_FILENAME, - vegetationFilePath = "", - z_ref = Z_REF, - v_ref = V_REF, - windDirection = WIND_DIRECTION, - prefix = PREFIX_NAME, - meshSize = MESH_SIZE, - dz = DZ, - alongWindZoneExtend = ALONG_WIND_ZONE_EXTEND, - crossWindZoneExtend = CROSS_WIND_ZONE_EXTEND, - verticalExtend = VERTICAL_EXTEND, - tempoDirectory = TEMPO_DIRECTORY, - inputDirectory = INPUT_DIRECTORY, - outputDirectory = OUTPUT_DIRECTORY, - cadTriangles = CAD_TRIANGLE_FILENAME, - cadTreesIntersection = CAD_VEG_INTERSECTION_FILENAME, - onlyInitialization = ONLY_INITIALIZATION, - maxIterations = MAX_ITERATIONS, - thresholdIterations = THRESHOLD_ITERATIONS, - idFieldBuild = ID_FIELD_BUILD, - buildingHeightField = HEIGHT_FIELD, - vegetationBaseHeight = VEGETATION_CROWN_BASE_HEIGHT, - vegetationTopHeight = VEGETATION_CROWN_TOP_HEIGHT, - idVegetation = ID_VEGETATION, - vegetationAttenuationFactor = VEGETATION_ATTENUATION_FACTOR, - saveRockleZones = SAVE_ROCKLE_ZONES, - z_out = Z_OUT, - outputRaster = None, - feedback = None, - saveRaster = True, - saveVector = True, - saveNetcdf = True, - debug = DEBUG, - profileType = PROFILE_TYPE, - verticalProfileFile = None): + +def main( + javaEnvironmentPath, + pluginDirectory, + outputFilePath, + buildingFilePath, + srid, + outputFilename=OUTPUT_FILENAME, + vegetationFilePath="", + z_ref=Z_REF, + v_ref=V_REF, + windDirection=WIND_DIRECTION, + prefix=PREFIX_NAME, + meshSize=MESH_SIZE, + dz=DZ, + alongWindZoneExtend=ALONG_WIND_ZONE_EXTEND, + crossWindZoneExtend=CROSS_WIND_ZONE_EXTEND, + verticalExtend=VERTICAL_EXTEND, + tempoDirectory=TEMPO_DIRECTORY, + inputDirectory=INPUT_DIRECTORY, + outputDirectory=OUTPUT_DIRECTORY, + cadTriangles=CAD_TRIANGLE_FILENAME, + cadTreesIntersection=CAD_VEG_INTERSECTION_FILENAME, + onlyInitialization=ONLY_INITIALIZATION, + maxIterations=MAX_ITERATIONS, + thresholdIterations=THRESHOLD_ITERATIONS, + idFieldBuild=ID_FIELD_BUILD, + buildingHeightField=HEIGHT_FIELD, + vegetationBaseHeight=VEGETATION_CROWN_BASE_HEIGHT, + vegetationTopHeight=VEGETATION_CROWN_TOP_HEIGHT, + idVegetation=ID_VEGETATION, + vegetationAttenuationFactor=VEGETATION_ATTENUATION_FACTOR, + saveRockleZones=SAVE_ROCKLE_ZONES, + z_out=Z_OUT, + outputRaster=None, + feedback=None, + saveRaster=True, + saveVector=True, + saveNetcdf=True, + debug=DEBUG, + profileType=PROFILE_TYPE, + verticalProfileFile=None, +): # If the function is called within QGIS, a feedback is sent into the QGIS interface if feedback: - feedback.setProgressText('Initiating algorithm') - + feedback.setProgressText("Initiating algorithm") + ################################ INIT OUTPUT VARIABLES ############################ # Create the temporary directory if not exists - tmp_dir_unique = os.path.join(TEMPO_DIRECTORY, - "URock_" \ - + datetime.datetime.now().isoformat().split(".")[0].replace(':','')\ - + str(uuid.uuid4())) - + tmp_dir_unique = os.path.join( + TEMPO_DIRECTORY, + "URock_" + + datetime.datetime.now().isoformat().split(".")[0].replace(":", "") + + str(uuid.uuid4()), + ) + if os.path.exists(tmp_dir_unique): os.remove(tmp_dir_unique) - os.mkdir(tmp_dir_unique) - + os.mkdir(tmp_dir_unique) + # Define dictionaries of input and output relative directories outputDataRel = {} # Blocks and stacked blocks - outputDataRel["blocks"] = os.path.join(tmp_dir_unique, f"blocks.{OUTPUT_VECTOR_EXTENSION}") - outputDataRel["stacked_blocks"] = os.path.join(tmp_dir_unique, f"stackedBlocks.{OUTPUT_VECTOR_EXTENSION}") - outputDataRel["vegetation"] = os.path.join(tmp_dir_unique, f"vegetation.{OUTPUT_VECTOR_EXTENSION}") + outputDataRel["blocks"] = os.path.join( + tmp_dir_unique, f"blocks.{OUTPUT_VECTOR_EXTENSION}" + ) + outputDataRel["stacked_blocks"] = os.path.join( + tmp_dir_unique, f"stackedBlocks.{OUTPUT_VECTOR_EXTENSION}" + ) + outputDataRel["vegetation"] = os.path.join( + tmp_dir_unique, f"vegetation.{OUTPUT_VECTOR_EXTENSION}" + ) # Rotated geometries - outputDataRel["rotated_stacked_blocks"] = os.path.join(tmp_dir_unique, f"rotated_stacked_blocks.{OUTPUT_VECTOR_EXTENSION}") - outputDataRel["rotated_vegetation"] = os.path.join(tmp_dir_unique, f"vegetationRotated.{OUTPUT_VECTOR_EXTENSION}") - outputDataRel["upwind_facades"] = os.path.join(tmp_dir_unique, f"upwind_facades.{OUTPUT_VECTOR_EXTENSION}") - outputDataRel["downwind_facades"] = os.path.join(tmp_dir_unique, f"downwind_facades.{OUTPUT_VECTOR_EXTENSION}") - + outputDataRel["rotated_stacked_blocks"] = os.path.join( + tmp_dir_unique, f"rotated_stacked_blocks.{OUTPUT_VECTOR_EXTENSION}" + ) + outputDataRel["rotated_vegetation"] = os.path.join( + tmp_dir_unique, f"vegetationRotated.{OUTPUT_VECTOR_EXTENSION}" + ) + outputDataRel["upwind_facades"] = os.path.join( + tmp_dir_unique, f"upwind_facades.{OUTPUT_VECTOR_EXTENSION}" + ) + outputDataRel["downwind_facades"] = os.path.join( + tmp_dir_unique, f"downwind_facades.{OUTPUT_VECTOR_EXTENSION}" + ) + # Created zones - outputDataRel["displacement"] = os.path.join(tmp_dir_unique, f"displacementZones.{OUTPUT_VECTOR_EXTENSION}") - outputDataRel["displacement_vortex"] = os.path.join(tmp_dir_unique, f"displacementVortexZones.{OUTPUT_VECTOR_EXTENSION}") - outputDataRel["cavity"] = os.path.join(tmp_dir_unique, f"cavity.{OUTPUT_VECTOR_EXTENSION}") - outputDataRel["wake"] = os.path.join(tmp_dir_unique, f"wake.{OUTPUT_VECTOR_EXTENSION}") - outputDataRel["street_canyon"] = os.path.join(tmp_dir_unique, f"streetCanyon.{OUTPUT_VECTOR_EXTENSION}") - outputDataRel["rooftop_perpendicular"] = os.path.join(tmp_dir_unique, f"rooftopPerp.{OUTPUT_VECTOR_EXTENSION}") - outputDataRel["rooftop_corner"] = os.path.join(tmp_dir_unique, f"rooftopCorner.{OUTPUT_VECTOR_EXTENSION}") - outputDataRel["vegetation_built"] = os.path.join(tmp_dir_unique, f"vegetationBuilt.{OUTPUT_VECTOR_EXTENSION}") - outputDataRel["vegetation_open"] = os.path.join(tmp_dir_unique, f"vegetationOpen.{OUTPUT_VECTOR_EXTENSION}") - + outputDataRel["displacement"] = os.path.join( + tmp_dir_unique, f"displacementZones.{OUTPUT_VECTOR_EXTENSION}" + ) + outputDataRel["displacement_vortex"] = os.path.join( + tmp_dir_unique, f"displacementVortexZones.{OUTPUT_VECTOR_EXTENSION}" + ) + outputDataRel["cavity"] = os.path.join( + tmp_dir_unique, f"cavity.{OUTPUT_VECTOR_EXTENSION}" + ) + outputDataRel["wake"] = os.path.join( + tmp_dir_unique, f"wake.{OUTPUT_VECTOR_EXTENSION}" + ) + outputDataRel["street_canyon"] = os.path.join( + tmp_dir_unique, f"streetCanyon.{OUTPUT_VECTOR_EXTENSION}" + ) + outputDataRel["rooftop_perpendicular"] = os.path.join( + tmp_dir_unique, f"rooftopPerp.{OUTPUT_VECTOR_EXTENSION}" + ) + outputDataRel["rooftop_corner"] = os.path.join( + tmp_dir_unique, f"rooftopCorner.{OUTPUT_VECTOR_EXTENSION}" + ) + outputDataRel["vegetation_built"] = os.path.join( + tmp_dir_unique, f"vegetationBuilt.{OUTPUT_VECTOR_EXTENSION}" + ) + outputDataRel["vegetation_open"] = os.path.join( + tmp_dir_unique, f"vegetationOpen.{OUTPUT_VECTOR_EXTENSION}" + ) + # Grid points - outputDataRel["point3D_BuildZone"] = os.path.join(tmp_dir_unique, "point3D_BuildZone") - outputDataRel["point3D_VegZone"] = os.path.join(tmp_dir_unique, "point3D_VegZone") + outputDataRel["point3D_BuildZone"] = os.path.join( + tmp_dir_unique, "point3D_BuildZone" + ) + outputDataRel["point3D_VegZone"] = os.path.join( + tmp_dir_unique, "point3D_VegZone" + ) outputDataRel["point3D_All"] = os.path.join(tmp_dir_unique, "point3D_All") - + # Put 2D grid points in the output directory - outputDataRel["point_2DRockleZone"] = os.path.join(outputFilePath, "Rockle_zones") - + outputDataRel["point_2DRockleZone"] = os.path.join( + outputFilePath, "Rockle_zones" + ) + # Convert relative to absolute paths - outputDataAbs = {i : os.path.abspath(outputDataRel[i]) for i in outputDataRel} - + outputDataAbs = { + i: os.path.abspath(outputDataRel[i]) for i in outputDataRel + } + ############################################################################ ################################ SCRIPT #################################### ############################################################################ @@ -133,128 +178,141 @@ def main(javaEnvironmentPath, # 1. SET H2GIS DATABASE ENVIRONMENT AND LOAD DATA # ---------------------------------------------------------------------- if feedback: - feedback.setProgressText('Creates an H2GIS Instance and load data') + feedback.setProgressText("Creates an H2GIS Instance and load data") if feedback.isCanceled(): cursor.close() feedback.setProgressText("Calculation cancelled by user") return {} - #Download H2GIS - #H2gisConnection.downloadH2gis(dbDirectory = pluginDirectory) - #Initialize a H2GIS database connection - dBDir = os.path.join(Path(pluginDirectory).parent, 'functions','URock') - #print(dBDir) + # Download H2GIS + # H2gisConnection.downloadH2gis(dbDirectory = pluginDirectory) + # Initialize a H2GIS database connection + dBDir = os.path.join(Path(pluginDirectory).parent, "functions", "URock") + # print(dBDir) if DEBUG: db_suffix = "" else: db_suffix = str(time.time()).replace(".", "_") - cursor, conn, localH2InstanceDir = \ - H2gisConnection.startH2gisInstance(dbDirectory = dBDir, - dbInstanceDir = tmp_dir_unique, - suffix = db_suffix) - + cursor, conn, localH2InstanceDir = H2gisConnection.startH2gisInstance( + dbDirectory=dBDir, dbInstanceDir=tmp_dir_unique, suffix=db_suffix + ) + # Load data - loadData.loadData(fromCad = False, - prefix = prefix, - idFieldBuild = idFieldBuild, - buildingHeightField = buildingHeightField, - vegetationBaseHeight = vegetationBaseHeight, - vegetationTopHeight = vegetationTopHeight, - idVegetation = idVegetation, - vegetationAttenuationFactor = vegetationAttenuationFactor, - cursor = cursor, - buildingFilePath = buildingFilePath, - vegetationFilePath = vegetationFilePath, - srid = srid) - + loadData.loadData( + fromCad=False, + prefix=prefix, + idFieldBuild=idFieldBuild, + buildingHeightField=buildingHeightField, + vegetationBaseHeight=vegetationBaseHeight, + vegetationTopHeight=vegetationTopHeight, + idVegetation=idVegetation, + vegetationAttenuationFactor=vegetationAttenuationFactor, + cursor=cursor, + buildingFilePath=buildingFilePath, + vegetationFilePath=vegetationFilePath, + srid=srid, + ) + timeStartCalculation = time.time() - + # ----------------------------------------------------------------------------------- # 2. CREATES OBSTACLE GEOMETRIES ---------------------------------------------------- # ----------------------------------------------------------------------------------- if feedback: - feedback.setProgressText('Creates the stacked blocks used as obstacles') + feedback.setProgressText( + "Creates the stacked blocks used as obstacles" + ) if feedback.isCanceled(): cursor.close() feedback.setProgressText("Calculation cancelled by user") return {} # Create the stacked blocks - blockTable, stackedBlockTable = \ - Obstacles.createsBlocks(cursor = cursor, - inputBuildings = BUILDING_TABLE_NAME, - prefix = prefix) - + blockTable, stackedBlockTable = Obstacles.createsBlocks( + cursor=cursor, inputBuildings=BUILDING_TABLE_NAME, prefix=prefix + ) + # Save the blocks, stacked blocks and vegetation as fgb if debug or saveRockleZones: - saveData.saveTable(cursor = cursor , tableName = blockTable, - filedir = outputDataAbs["blocks"] , delete = True) - saveData.saveTable(cursor = cursor , tableName = VEGETATION_TABLE_NAME, - filedir = outputDataAbs["vegetation"] , delete = True) - + saveData.saveTable( + cursor=cursor, + tableName=blockTable, + filedir=outputDataAbs["blocks"], + delete=True, + ) + saveData.saveTable( + cursor=cursor, + tableName=VEGETATION_TABLE_NAME, + filedir=outputDataAbs["vegetation"], + delete=True, + ) + # ----------------------------------------------------------------------------------- # 3. ROTATES OBSTACLES TO THE RIGHT DIRECTION AND CALCULATES GEOMETRY PROPERTIES ---- # ----------------------------------------------------------------------------------- if feedback: - feedback.setProgressText('Rotates obstacles to the right direction and calculates geometry properties') + feedback.setProgressText( + "Rotates obstacles to the right direction and calculates geometry properties" + ) if feedback.isCanceled(): cursor.close() feedback.setProgressText("Calculation cancelled by user") return {} # Define a set of obstacles in a dictionary before the rotation - dicOfObstacles = {BUILDING_TABLE_NAME : stackedBlockTable, - VEGETATION_TABLE_NAME : VEGETATION_TABLE_NAME} - + dicOfObstacles = { + BUILDING_TABLE_NAME: stackedBlockTable, + VEGETATION_TABLE_NAME: VEGETATION_TABLE_NAME, + } + # Rotate obstacles - dicRotatedTables, rotationCenterCoordinates = \ - Obstacles.windRotation(cursor = cursor, - dicOfInputTables = dicOfObstacles, - rotateAngle = windDirection, - rotationCenterCoordinates = None, - prefix = prefix) - + dicRotatedTables, rotationCenterCoordinates = Obstacles.windRotation( + cursor=cursor, + dicOfInputTables=dicOfObstacles, + rotateAngle=windDirection, + rotationCenterCoordinates=None, + prefix=prefix, + ) + # Get the rotated block and vegetation table names rotatedStackedBlocks = dicRotatedTables[BUILDING_TABLE_NAME] rotatedVegetation = dicRotatedTables[VEGETATION_TABLE_NAME] - + # Calculates base block height and base of block cavity zone - rotatedPropStackedBlocks = \ - Obstacles.identifyBlockAndCavityBase(cursor, rotatedStackedBlocks, - prefix = prefix) - + rotatedPropStackedBlocks = Obstacles.identifyBlockAndCavityBase( + cursor, rotatedStackedBlocks, prefix=prefix + ) + # Calculates obstacles properties - obstaclePropertiesTable = \ - CalculatesIndicators.obstacleProperties(cursor = cursor, - obstaclesTable = rotatedPropStackedBlocks, - prefix = prefix) - + obstaclePropertiesTable = CalculatesIndicators.obstacleProperties( + cursor=cursor, obstaclesTable=rotatedPropStackedBlocks, prefix=prefix + ) + # Calculates obstacle zone properties - zonePropertiesTable = \ - CalculatesIndicators.zoneProperties(cursor = cursor, - obstaclePropertiesTable = obstaclePropertiesTable, - prefix = prefix) - + zonePropertiesTable = CalculatesIndicators.zoneProperties( + cursor=cursor, + obstaclePropertiesTable=obstaclePropertiesTable, + prefix=prefix, + ) + # Init the upwind facades - upwindInitedTable = \ - Obstacles.initUpwindFacades(cursor = cursor, - obstaclesTable = zonePropertiesTable, - prefix = prefix) + upwindInitedTable = Obstacles.initUpwindFacades( + cursor=cursor, obstaclesTable=zonePropertiesTable, prefix=prefix + ) # Update base height of upwind facades (if shared with the building below) - upwindTable = \ - Obstacles.updateUpwindFacadeBase(cursor = cursor, - upwindTable = upwindInitedTable, - prefix = prefix) - - # Calculates downwind facades - downwindTable = \ - Obstacles.initDownwindFacades(cursor = cursor, - obstaclesTable = zonePropertiesTable, - prefix = prefix) - + upwindTable = Obstacles.updateUpwindFacadeBase( + cursor=cursor, upwindTable=upwindInitedTable, prefix=prefix + ) + + # Calculates downwind facades + downwindTable = Obstacles.initDownwindFacades( + cursor=cursor, obstaclesTable=zonePropertiesTable, prefix=prefix + ) + # Calculates roughness properties of the study area - z0, d, Hr, H_ob_max, lambda_f = \ - CalculatesIndicators.studyAreaProperties(cursor = cursor, - upwindTable = upwindInitedTable, - stackedBlockTable = rotatedStackedBlocks, - vegetationTable = rotatedVegetation) + z0, d, Hr, H_ob_max, lambda_f = CalculatesIndicators.studyAreaProperties( + cursor=cursor, + upwindTable=upwindInitedTable, + stackedBlockTable=rotatedStackedBlocks, + vegetationTable=rotatedVegetation, + ) # Print some of the roughness properties for the zone if feedback: @@ -277,30 +335,49 @@ def main(javaEnvironmentPath, # Save the rotated obstacles and facades as fgb if debug or saveRockleZones: - saveData.saveTable(cursor = cursor , tableName = rotatedPropStackedBlocks, - filedir = outputDataAbs["rotated_stacked_blocks"], delete = True) - saveData.saveTable(cursor = cursor , tableName = rotatedVegetation, - filedir = outputDataAbs["rotated_vegetation"] , delete = True) - saveData.saveTable(cursor = cursor , tableName = upwindTable, - filedir = outputDataAbs["upwind_facades"] , delete = True, - rotationCenterCoordinates = rotationCenterCoordinates, - rotateAngle = - windDirection) - saveData.saveTable(cursor = cursor , tableName = downwindTable, - filedir = outputDataAbs["downwind_facades"] , delete = True, - rotationCenterCoordinates = rotationCenterCoordinates, - rotateAngle = - windDirection) + saveData.saveTable( + cursor=cursor, + tableName=rotatedPropStackedBlocks, + filedir=outputDataAbs["rotated_stacked_blocks"], + delete=True, + ) + saveData.saveTable( + cursor=cursor, + tableName=rotatedVegetation, + filedir=outputDataAbs["rotated_vegetation"], + delete=True, + ) + saveData.saveTable( + cursor=cursor, + tableName=upwindTable, + filedir=outputDataAbs["upwind_facades"], + delete=True, + rotationCenterCoordinates=rotationCenterCoordinates, + rotateAngle=-windDirection, + ) + saveData.saveTable( + cursor=cursor, + tableName=downwindTable, + filedir=outputDataAbs["downwind_facades"], + delete=True, + rotationCenterCoordinates=rotationCenterCoordinates, + rotateAngle=-windDirection, + ) # Used later to save results - saveData.saveTable(cursor = cursor , tableName = rotatedPropStackedBlocks, - rotationCenterCoordinates = rotationCenterCoordinates, - rotateAngle = - windDirection, - filedir = outputDataAbs["stacked_blocks"], delete = True) - - + saveData.saveTable( + cursor=cursor, + tableName=rotatedPropStackedBlocks, + rotationCenterCoordinates=rotationCenterCoordinates, + rotateAngle=-windDirection, + filedir=outputDataAbs["stacked_blocks"], + delete=True, + ) + # ----------------------------------------------------------------------------------- # 4. CREATES THE 2D ROCKLE ZONES ---------------------------------------------------- # ----------------------------------------------------------------------------------- if feedback: - feedback.setProgressText('Creates the 2D Röckle zones') + feedback.setProgressText("Creates the 2D Röckle zones") if feedback.isCanceled(): cursor.close() feedback.setProgressText("Calculation cancelled by user") @@ -312,11 +389,14 @@ def main(javaEnvironmentPath, # zonePropertiesTable = zonePropertiesTable, # srid = srid, # prefix = prefix) - displacementZonesTable, displacementVortexZonesTable = \ - Zones.displacementZones2(cursor = cursor, - upwindWithPropTable = upwindTable, - srid = srid, - prefix = prefix) + displacementZonesTable, displacementVortexZonesTable = ( + Zones.displacementZones2( + cursor=cursor, + upwindWithPropTable=upwindTable, + srid=srid, + prefix=prefix, + ) + ) # # Creates the displacement zone (upwind) # displacementZonesTable = \ # Zones.displacementZones(cursor = cursor, @@ -329,105 +409,148 @@ def main(javaEnvironmentPath, # upwindWithPropTable = upwindTable, # srid = srid, # prefix = prefix)[0] - - + # Save the resulting displacement zones as fgb if debug or saveRockleZones: - saveData.saveTable(cursor = cursor , tableName = displacementZonesTable, - filedir = outputDataAbs["displacement"] , delete = True, - rotationCenterCoordinates = rotationCenterCoordinates, - rotateAngle = - windDirection) - saveData.saveTable(cursor = cursor , tableName = displacementVortexZonesTable, - filedir = outputDataAbs["displacement_vortex"] , delete = True, - rotationCenterCoordinates = rotationCenterCoordinates, - rotateAngle = - windDirection) - + saveData.saveTable( + cursor=cursor, + tableName=displacementZonesTable, + filedir=outputDataAbs["displacement"], + delete=True, + rotationCenterCoordinates=rotationCenterCoordinates, + rotateAngle=-windDirection, + ) + saveData.saveTable( + cursor=cursor, + tableName=displacementVortexZonesTable, + filedir=outputDataAbs["displacement_vortex"], + delete=True, + rotationCenterCoordinates=rotationCenterCoordinates, + rotateAngle=-windDirection, + ) + # Creates the cavity and wake zones - cavityZonesTable, wakeZonesTable = \ - Zones.cavityAndWakeZones(cursor = cursor, - downwindWithPropTable = downwindTable, - srid = srid, - ellipseResolution = meshSize/3, - prefix = prefix).values() - + cavityZonesTable, wakeZonesTable = Zones.cavityAndWakeZones( + cursor=cursor, + downwindWithPropTable=downwindTable, + srid=srid, + ellipseResolution=meshSize / 3, + prefix=prefix, + ).values() + # Save the resulting displacement zones as fgb if debug or saveRockleZones: - saveData.saveTable(cursor = cursor , tableName = cavityZonesTable, - filedir = outputDataAbs["cavity"] , delete = True, - rotationCenterCoordinates = rotationCenterCoordinates, - rotateAngle = - windDirection) - saveData.saveTable(cursor = cursor , tableName = wakeZonesTable, - filedir = outputDataAbs["wake"] , delete = True, - rotationCenterCoordinates = rotationCenterCoordinates, - rotateAngle = - windDirection) - - + saveData.saveTable( + cursor=cursor, + tableName=cavityZonesTable, + filedir=outputDataAbs["cavity"], + delete=True, + rotationCenterCoordinates=rotationCenterCoordinates, + rotateAngle=-windDirection, + ) + saveData.saveTable( + cursor=cursor, + tableName=wakeZonesTable, + filedir=outputDataAbs["wake"], + delete=True, + rotationCenterCoordinates=rotationCenterCoordinates, + rotateAngle=-windDirection, + ) + # Creates the street canyon zones - streetCanyonTable = \ - Zones.streetCanyonZones(cursor = cursor, - cavityZonesTable = cavityZonesTable, - zonePropertiesTable = zonePropertiesTable, - upwindTable = upwindTable, - downwindTable = downwindTable, - srid = srid, - prefix = prefix) - + streetCanyonTable = Zones.streetCanyonZones( + cursor=cursor, + cavityZonesTable=cavityZonesTable, + zonePropertiesTable=zonePropertiesTable, + upwindTable=upwindTable, + downwindTable=downwindTable, + srid=srid, + prefix=prefix, + ) + # Save the resulting street canyon zones as fgb if debug or saveRockleZones: - saveData.saveTable(cursor = cursor , tableName = streetCanyonTable, - filedir = outputDataAbs["street_canyon"] , delete = True, - rotationCenterCoordinates = rotationCenterCoordinates, - rotateAngle = - windDirection) - + saveData.saveTable( + cursor=cursor, + tableName=streetCanyonTable, + filedir=outputDataAbs["street_canyon"], + delete=True, + rotationCenterCoordinates=rotationCenterCoordinates, + rotateAngle=-windDirection, + ) + # Creates the rooftop zones - rooftopPerpendicularZoneTable, rooftopCornerZoneTable = \ - Zones.rooftopZones(cursor = cursor, - upwindTable = upwindTable, - zonePropertiesTable = zonePropertiesTable, - prefix = prefix) + rooftopPerpendicularZoneTable, rooftopCornerZoneTable = Zones.rooftopZones( + cursor=cursor, + upwindTable=upwindTable, + zonePropertiesTable=zonePropertiesTable, + prefix=prefix, + ) # Save the resulting rooftop zones as fgb if debug or saveRockleZones: - saveData.saveTable(cursor = cursor , tableName = rooftopPerpendicularZoneTable, - filedir = outputDataAbs["rooftop_perpendicular"] , delete = True, - rotationCenterCoordinates = rotationCenterCoordinates, - rotateAngle = - windDirection) - saveData.saveTable(cursor = cursor , tableName = rooftopCornerZoneTable, - filedir = outputDataAbs["rooftop_corner"] , delete = True, - rotationCenterCoordinates = rotationCenterCoordinates, - rotateAngle = - windDirection) - + saveData.saveTable( + cursor=cursor, + tableName=rooftopPerpendicularZoneTable, + filedir=outputDataAbs["rooftop_perpendicular"], + delete=True, + rotationCenterCoordinates=rotationCenterCoordinates, + rotateAngle=-windDirection, + ) + saveData.saveTable( + cursor=cursor, + tableName=rooftopCornerZoneTable, + filedir=outputDataAbs["rooftop_corner"], + delete=True, + rotationCenterCoordinates=rotationCenterCoordinates, + rotateAngle=-windDirection, + ) + # Creates the vegetation zones - vegetationBuiltZoneTable, vegetationOpenZoneTable = \ - Zones.vegetationZones(cursor = cursor, - vegetationTable = rotatedVegetation, - wakeZonesTable = wakeZonesTable, - prefix = prefix) + vegetationBuiltZoneTable, vegetationOpenZoneTable = Zones.vegetationZones( + cursor=cursor, + vegetationTable=rotatedVegetation, + wakeZonesTable=wakeZonesTable, + prefix=prefix, + ) if debug or saveRockleZones: - saveData.saveTable(cursor = cursor , tableName = vegetationBuiltZoneTable, - filedir = outputDataAbs["vegetation_built"] , delete = True, - rotationCenterCoordinates = rotationCenterCoordinates, - rotateAngle = - windDirection) - saveData.saveTable(cursor = cursor , tableName = vegetationOpenZoneTable, - filedir = outputDataAbs["vegetation_open"] , delete = True, - rotationCenterCoordinates = rotationCenterCoordinates, - rotateAngle = - windDirection) - + saveData.saveTable( + cursor=cursor, + tableName=vegetationBuiltZoneTable, + filedir=outputDataAbs["vegetation_built"], + delete=True, + rotationCenterCoordinates=rotationCenterCoordinates, + rotateAngle=-windDirection, + ) + saveData.saveTable( + cursor=cursor, + tableName=vegetationOpenZoneTable, + filedir=outputDataAbs["vegetation_open"], + delete=True, + rotationCenterCoordinates=rotationCenterCoordinates, + rotateAngle=-windDirection, + ) + # Define a dictionary of all building Rockle zones and same for veg - dicOfBuildRockleZoneTable = {DISPLACEMENT_NAME : displacementZonesTable, - DISPLACEMENT_VORTEX_NAME: displacementVortexZonesTable, - CAVITY_NAME : cavityZonesTable, - WAKE_NAME : wakeZonesTable, - STREET_CANYON_NAME : streetCanyonTable, - ROOFTOP_PERP_NAME : rooftopPerpendicularZoneTable, - ROOFTOP_CORN_NAME : rooftopCornerZoneTable} - dicOfVegRockleZoneTable = {VEGETATION_BUILT_NAME : vegetationBuiltZoneTable, - VEGETATION_OPEN_NAME : vegetationOpenZoneTable} - + dicOfBuildRockleZoneTable = { + DISPLACEMENT_NAME: displacementZonesTable, + DISPLACEMENT_VORTEX_NAME: displacementVortexZonesTable, + CAVITY_NAME: cavityZonesTable, + WAKE_NAME: wakeZonesTable, + STREET_CANYON_NAME: streetCanyonTable, + ROOFTOP_PERP_NAME: rooftopPerpendicularZoneTable, + ROOFTOP_CORN_NAME: rooftopCornerZoneTable, + } + dicOfVegRockleZoneTable = { + VEGETATION_BUILT_NAME: vegetationBuiltZoneTable, + VEGETATION_OPEN_NAME: vegetationOpenZoneTable, + } + if outputRaster: # Creates a table with a polygon covering the raster zone envelope smallStudyZone = "SMALL_STUDY_ZONE" outputRasterExtent = outputRaster.extent() - cursor.execute(safe(""" + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0}({5} GEOMETRY) AS SELECT ST_SETSRID(ST_ROTATE(ST_ENVELOPE('MULTIPOINT({1} {2}, @@ -435,99 +558,121 @@ def main(javaEnvironmentPath, {6}, {7}, {8}), {9}) - """).format(smallStudyZone, - outputRasterExtent.xMinimum(), - outputRasterExtent.yMinimum(), - outputRasterExtent.xMaximum(), - outputRasterExtent.yMaximum(), - GEOM_FIELD, - DataUtil.degToRad(windDirection), - rotationCenterCoordinates[0], - rotationCenterCoordinates[1], - srid)) + """).format( + smallStudyZone, + outputRasterExtent.xMinimum(), + outputRasterExtent.yMinimum(), + outputRasterExtent.xMaximum(), + outputRasterExtent.yMaximum(), + GEOM_FIELD, + DataUtil.degToRad(windDirection), + rotationCenterCoordinates[0], + rotationCenterCoordinates[1], + srid, + ) + ) # Identify the stacked blocks, blocks potentially impacting the - # impacted zone and their corresponding Röckle zones - dicOfBuildRockleZoneTable, dicOfVegRockleZoneTable, rotatedPropStackedBlocks,\ - rotatedVegetation = \ - Zones.identifyImpactingStackedBlocks(cursor = cursor, - dicOfBuildRockleZoneTable = dicOfBuildRockleZoneTable, - dicOfVegRockleZoneTable = dicOfVegRockleZoneTable, - impactedZone = smallStudyZone, - stackedBlocksTable = rotatedPropStackedBlocks, - vegetationTable = rotatedVegetation, - crossWindExtend = crossWindZoneExtend, - prefix = prefix) + # impacted zone and their corresponding Röckle zones + ( + dicOfBuildRockleZoneTable, + dicOfVegRockleZoneTable, + rotatedPropStackedBlocks, + rotatedVegetation, + ) = Zones.identifyImpactingStackedBlocks( + cursor=cursor, + dicOfBuildRockleZoneTable=dicOfBuildRockleZoneTable, + dicOfVegRockleZoneTable=dicOfVegRockleZoneTable, + impactedZone=smallStudyZone, + stackedBlocksTable=rotatedPropStackedBlocks, + vegetationTable=rotatedVegetation, + crossWindExtend=crossWindZoneExtend, + prefix=prefix, + ) # ---------------------------------------------------------------------- # 5. SET THE 2D GRID IN THE ROCKLE ZONES ------------------------------- # ---------------------------------------------------------------------- if feedback: - feedback.setProgressText('Creates the 2D grid') + feedback.setProgressText("Creates the 2D grid") if feedback.isCanceled(): cursor.close() feedback.setProgressText("Calculation cancelled by user") return {} - + # Creates the grid of points - gridPoint = InitWindField.createGrid(cursor = cursor, - dicOfInputTables = dict(dicOfBuildRockleZoneTable, - **dicOfVegRockleZoneTable), - srid = srid, - alongWindZoneExtend = alongWindZoneExtend, - crossWindZoneExtend = crossWindZoneExtend, - meshSize = meshSize, - prefix = prefix) - + gridPoint = InitWindField.createGrid( + cursor=cursor, + dicOfInputTables=dict( + dicOfBuildRockleZoneTable, **dicOfVegRockleZoneTable + ), + srid=srid, + alongWindZoneExtend=alongWindZoneExtend, + crossWindZoneExtend=crossWindZoneExtend, + meshSize=meshSize, + prefix=prefix, + ) + # Affects each 2D point to a build Rockle zone and calculates needed variables for 3D wind speed factors - dicOfInitBuildZoneGridPoint, verticalLineTable = \ - InitWindField.affectsPointToBuildZone( cursor = cursor, - gridTable = gridPoint, - dicOfBuildRockleZoneTable = dicOfBuildRockleZoneTable, - prefix = prefix) - + dicOfInitBuildZoneGridPoint, verticalLineTable = ( + InitWindField.affectsPointToBuildZone( + cursor=cursor, + gridTable=gridPoint, + dicOfBuildRockleZoneTable=dicOfBuildRockleZoneTable, + prefix=prefix, + ) + ) + # Same for vegetation Röckle zones - dicOfVegZoneGridPoint = \ - InitWindField.affectsPointToVegZone(cursor = cursor, - gridTable = gridPoint, - dicOfVegRockleZoneTable = dicOfVegRockleZoneTable, - prefix = prefix) - + dicOfVegZoneGridPoint = InitWindField.affectsPointToVegZone( + cursor=cursor, + gridTable=gridPoint, + dicOfVegRockleZoneTable=dicOfVegRockleZoneTable, + prefix=prefix, + ) + # Remove some of the Röckle points where building Röckle zones overlap - dicOfBuildZoneGridPoint = \ - InitWindField.removeBuildZonePoints(cursor = cursor, - dicOfInitBuildZoneGridPoint = dicOfInitBuildZoneGridPoint, - prefix = prefix) - + dicOfBuildZoneGridPoint = InitWindField.removeBuildZonePoints( + cursor=cursor, + dicOfInitBuildZoneGridPoint=dicOfInitBuildZoneGridPoint, + prefix=prefix, + ) + # Manage backward cavity and wake zones in the leeward zone of tall buildings - dicOfBuildZoneGridPoint, facadeWithinCavity =\ - InitWindField.manageBackwardZones(cursor = cursor, - dicOfBuildZoneGridPoint = dicOfBuildZoneGridPoint, - cavity2dInitPoints = dicOfInitBuildZoneGridPoint[CAVITY_NAME], - wake2dInitPoints = dicOfInitBuildZoneGridPoint[WAKE_NAME], - streetCanyonTable = streetCanyonTable, - gridTable = gridPoint, - meshSize = meshSize, - dz = dz, - prefix = prefix) - - + dicOfBuildZoneGridPoint, facadeWithinCavity = ( + InitWindField.manageBackwardZones( + cursor=cursor, + dicOfBuildZoneGridPoint=dicOfBuildZoneGridPoint, + cavity2dInitPoints=dicOfInitBuildZoneGridPoint[CAVITY_NAME], + wake2dInitPoints=dicOfInitBuildZoneGridPoint[WAKE_NAME], + streetCanyonTable=streetCanyonTable, + gridTable=gridPoint, + meshSize=meshSize, + dz=dz, + prefix=prefix, + ) + ) + # ----------------------------------------------------------------------------------- # 6. INITIALIZE THE 3D WIND FACTORS IN THE ROCKLE ZONES ------------------------------- - # ----------------------------------------------------------------------------------- + # ----------------------------------------------------------------------------------- if feedback: - feedback.setProgressText('Initializes the 3D grid within Röckle zones') + feedback.setProgressText("Initializes the 3D grid within Röckle zones") if feedback.isCanceled(): cursor.close() feedback.setProgressText("Calculation cancelled by user") return {} # Calculates the 3D wind speed factors for each building Röckle zone - dicOfBuildZone3DWindFactor, maxBuildZoneHeight = \ - InitWindField.calculates3dBuildWindFactor(cursor = cursor, - dicOfBuildZoneGridPoint = dicOfBuildZoneGridPoint, - dz = dz, - prefix = prefix) + dicOfBuildZone3DWindFactor, maxBuildZoneHeight = ( + InitWindField.calculates3dBuildWindFactor( + cursor=cursor, + dicOfBuildZoneGridPoint=dicOfBuildZoneGridPoint, + dz=dz, + prefix=prefix, + ) + ) if debug or saveRockleZones: for t in dicOfBuildZone3DWindFactor: - cursor.execute(safe(""" + cursor.execute( + safe(""" DROP TABLE IF EXISTS point3D_Buildzone_{0}; {5}; {6}; @@ -536,38 +681,54 @@ def main(javaEnvironmentPath, FROM {3} AS a RIGHT JOIN {4} AS b ON a.{1} = b.{1} WHERE b.{1} IS NOT NULL - """).format( t , ID_POINT, - GEOM_FIELD , gridPoint, - dicOfBuildZone3DWindFactor[t], DataUtil.createIndex(tableName=gridPoint, - fieldName=ID_POINT, - isSpatial=False), - DataUtil.createIndex(tableName=dicOfBuildZone3DWindFactor[t], - fieldName=ID_POINT, - isSpatial=False))) - saveData.saveTable(cursor = cursor, - tableName = "point3D_Buildzone_"+t, - filedir = outputDataAbs["point3D_BuildZone"] + t + OUTPUT_VECTOR_EXTENSION, - delete = True, - rotationCenterCoordinates = rotationCenterCoordinates, - rotateAngle = - windDirection) - + """).format( + t, + ID_POINT, + GEOM_FIELD, + gridPoint, + dicOfBuildZone3DWindFactor[t], + DataUtil.createIndex( + tableName=gridPoint, + fieldName=ID_POINT, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=dicOfBuildZone3DWindFactor[t], + fieldName=ID_POINT, + isSpatial=False, + ), + ) + ) + saveData.saveTable( + cursor=cursor, + tableName="point3D_Buildzone_" + t, + filedir=outputDataAbs["point3D_BuildZone"] + + t + + OUTPUT_VECTOR_EXTENSION, + delete=True, + rotationCenterCoordinates=rotationCenterCoordinates, + rotateAngle=-windDirection, + ) + # Calculates the 3D wind speed factors of the vegetation (considering all zone types) # after calculation of the top of the "sketch" maxHeight = H_ob_max - if maxBuildZoneHeight: + if maxBuildZoneHeight: if maxBuildZoneHeight > H_ob_max: maxHeight = maxBuildZoneHeight sketchHeight = maxHeight + verticalExtend - vegetationWeightFactorTable = \ - InitWindField.calculates3dVegWindFactor(cursor = cursor, - dicOfVegZoneGridPoint = dicOfVegZoneGridPoint, - sketchHeight = sketchHeight, - z0 = z0, - d = d, - dz = dz, - prefix = prefix) + vegetationWeightFactorTable = InitWindField.calculates3dVegWindFactor( + cursor=cursor, + dicOfVegZoneGridPoint=dicOfVegZoneGridPoint, + sketchHeight=sketchHeight, + z0=z0, + d=d, + dz=dz, + prefix=prefix, + ) if debug or saveRockleZones: - cursor.execute(safe(""" + cursor.execute( + safe(""" DROP TABLE IF EXISTS point3D_AllVegZone; {4}; {5}; @@ -576,41 +737,53 @@ def main(javaEnvironmentPath, FROM {2} AS a RIGHT JOIN {3} AS b ON a.{0} = b.{0} WHERE b.{0} IS NOT NULL - """).format( ID_POINT , GEOM_FIELD, - gridPoint , vegetationWeightFactorTable, - DataUtil.createIndex(tableName=gridPoint, - fieldName=ID_POINT, - isSpatial=False), - DataUtil.createIndex(tableName=vegetationWeightFactorTable, - fieldName=ID_POINT, - isSpatial=False))) - saveData.saveTable(cursor = cursor, - tableName = "point3D_AllVegZone", - filedir = outputDataAbs["point3D_VegZone"] + OUTPUT_VECTOR_EXTENSION, - delete = True, - rotationCenterCoordinates = rotationCenterCoordinates, - rotateAngle = - windDirection) - - + """).format( + ID_POINT, + GEOM_FIELD, + gridPoint, + vegetationWeightFactorTable, + DataUtil.createIndex( + tableName=gridPoint, fieldName=ID_POINT, isSpatial=False + ), + DataUtil.createIndex( + tableName=vegetationWeightFactorTable, + fieldName=ID_POINT, + isSpatial=False, + ), + ) + ) + saveData.saveTable( + cursor=cursor, + tableName="point3D_AllVegZone", + filedir=outputDataAbs["point3D_VegZone"] + OUTPUT_VECTOR_EXTENSION, + delete=True, + rotationCenterCoordinates=rotationCenterCoordinates, + rotateAngle=-windDirection, + ) + # ---------------------------------------------------------------- # 7. DEALS WITH SUPERIMPOSED ZONES ------------------------------- # ---------------------------------------------------------------- # Calculates the final weighting factor for each point, dealing with duplicates (superimposition) dicAllWeightFactorsTables = dicOfBuildZone3DWindFactor.copy() - dicAllWeightFactorsTables[ALL_VEGETATION_NAME] = vegetationWeightFactorTable - allZonesPointFactor = \ - InitWindField.manageSuperimposition(cursor = cursor, - dicAllWeightFactorsTables = dicAllWeightFactorsTables, - facadeWithinCavity = facadeWithinCavity, - upstreamPriorityTables = UPSTREAM_PRIORITY_TABLES, - upstreamWeightingTables = UPSTREAM_WEIGHTING_TABLES, - upstreamWeightingInterRules = UPSTREAM_WEIGHTING_INTER_RULES, - upstreamWeightingIntraRules = UPSTREAM_WEIGHTING_INTRA_RULES, - downstreamWeightingTable = DOWNSTREAM_WEIGTHING_TABLE, - prefix = prefix, - feedback = feedback) + dicAllWeightFactorsTables[ALL_VEGETATION_NAME] = ( + vegetationWeightFactorTable + ) + allZonesPointFactor = InitWindField.manageSuperimposition( + cursor=cursor, + dicAllWeightFactorsTables=dicAllWeightFactorsTables, + facadeWithinCavity=facadeWithinCavity, + upstreamPriorityTables=UPSTREAM_PRIORITY_TABLES, + upstreamWeightingTables=UPSTREAM_WEIGHTING_TABLES, + upstreamWeightingInterRules=UPSTREAM_WEIGHTING_INTER_RULES, + upstreamWeightingIntraRules=UPSTREAM_WEIGHTING_INTRA_RULES, + downstreamWeightingTable=DOWNSTREAM_WEIGTHING_TABLE, + prefix=prefix, + feedback=feedback, + ) if debug or saveRockleZones: - cursor.execute(safe(""" + cursor.execute( + safe(""" DROP TABLE IF EXISTS point3D_All; {4}; {5}; @@ -619,85 +792,107 @@ def main(javaEnvironmentPath, FROM {2} AS a RIGHT JOIN {3} AS b ON a.{0} = b.{0} WHERE b.{0} IS NOT NULL - """).format( ID_POINT , GEOM_FIELD, - gridPoint , allZonesPointFactor, - DataUtil.createIndex(tableName=gridPoint, - fieldName=ID_POINT, - isSpatial=False), - DataUtil.createIndex(tableName=allZonesPointFactor, - fieldName=ID_POINT, - isSpatial=False))) - saveData.saveTable(cursor = cursor, - tableName = "point3D_All", - filedir = outputDataAbs["point3D_All"] + OUTPUT_VECTOR_EXTENSION, - delete = True, - rotationCenterCoordinates = rotationCenterCoordinates, - rotateAngle = - windDirection) - - + """).format( + ID_POINT, + GEOM_FIELD, + gridPoint, + allZonesPointFactor, + DataUtil.createIndex( + tableName=gridPoint, fieldName=ID_POINT, isSpatial=False + ), + DataUtil.createIndex( + tableName=allZonesPointFactor, + fieldName=ID_POINT, + isSpatial=False, + ), + ) + ) + saveData.saveTable( + cursor=cursor, + tableName="point3D_All", + filedir=outputDataAbs["point3D_All"] + OUTPUT_VECTOR_EXTENSION, + delete=True, + rotationCenterCoordinates=rotationCenterCoordinates, + rotateAngle=-windDirection, + ) + # ------------------------------------------------------------------- # 8. 3D WIND SPEED INITIALIZATION ----------------------------------- # ------------------------------------------------------------------- if feedback: - feedback.setProgressText('Initialize the 3D wind in the grid') + feedback.setProgressText("Initialize the 3D wind in the grid") if feedback.isCanceled(): cursor.close() feedback.setProgressText("Calculation cancelled by user") return {} # Identify 3D grid points intersected by buildings - df_gridBuil = \ - InitWindField.identifyBuildPoints(cursor = cursor, - gridPoint = gridPoint, - stackedBlocksWithBaseHeight = rotatedPropStackedBlocks, - dz = dz, - tempoDirectory = tmp_dir_unique) - + df_gridBuil = InitWindField.identifyBuildPoints( + cursor=cursor, + gridPoint=gridPoint, + stackedBlocksWithBaseHeight=rotatedPropStackedBlocks, + dz=dz, + tempoDirectory=tmp_dir_unique, + ) + # Set the initial 3D wind speed field - df_wind0, nPoints, verticalWindProfile = \ - InitWindField.setInitialWindField(cursor = cursor, - initializedWindFactorTable = allZonesPointFactor, - gridPoint = gridPoint, - df_gridBuil = df_gridBuil, - z0 = z0, - sketchHeight = sketchHeight, - profileType = profileType, - meshSize = meshSize, - dz = dz, - z_ref = z_ref, - V_ref = v_ref, - tempoDirectory = tmp_dir_unique, - d = d, - H = Hr, - lambda_f = lambda_f, - verticalProfileFile = verticalProfileFile) - + df_wind0, nPoints, verticalWindProfile = InitWindField.setInitialWindField( + cursor=cursor, + initializedWindFactorTable=allZonesPointFactor, + gridPoint=gridPoint, + df_gridBuil=df_gridBuil, + z0=z0, + sketchHeight=sketchHeight, + profileType=profileType, + meshSize=meshSize, + dz=dz, + z_ref=z_ref, + V_ref=v_ref, + tempoDirectory=tmp_dir_unique, + d=d, + H=Hr, + lambda_f=lambda_f, + verticalProfileFile=verticalProfileFile, + ) + # ------------------------------------------------------------------- # 9. "RASTERIZE" THE DATA - PREPARE MATRICES FOR WIND CALCULATION --- # ------------------------------------------------------------------- if feedback: - feedback.setProgressText('Rasterize the data') + feedback.setProgressText("Rasterize the data") if feedback.isCanceled(): cursor.close() feedback.setProgressText("Calculation cancelled by user") return {} # Set the ground as "building" (understand solid wall) - after getting grid size nx, ny, nz = nPoints.values() - df_gridBuil = df_gridBuil.reindex(df_gridBuil.index.append(pd.MultiIndex.from_product([range(1,nx-1), - range(1,ny-1), - [0]]))) + df_gridBuil = df_gridBuil.reindex( + df_gridBuil.index.append( + pd.MultiIndex.from_product( + [range(1, nx - 1), range(1, ny - 1), [0]] + ) + ) + ) - # Set the buildGrid3D object to zero when a cell intersect a building - buildGrid3D = pd.Series(1, index = df_wind0.index, dtype = np.int32) + # Set the buildGrid3D object to zero when a cell intersect a building + buildGrid3D = pd.Series(1, index=df_wind0.index, dtype=np.int32) buildGrid3D.loc[df_gridBuil.index] = 0 - + # Convert building coordinates and wind speeds to numpy matrix... # (note that v axis direction is changed since we first use Röckle schemes # considering wind speed coming from North thus axis facing South) - buildGrid3D = np.array([buildGrid3D.xs(i, level = 0).unstack().values for i in range(0,nx)]) - u0 = np.array([df_wind0[U].xs(i, level = 0).unstack().values for i in range(0,nx)]) - v0 = -np.array([df_wind0[V].xs(i, level = 0).unstack().values for i in range(0,nx)]) - w0 = np.array([df_wind0[W].xs(i, level = 0).unstack().values for i in range(0,nx)]) - + buildGrid3D = np.array( + [buildGrid3D.xs(i, level=0).unstack().values for i in range(0, nx)] + ) + u0 = np.array( + [df_wind0[U].xs(i, level=0).unstack().values for i in range(0, nx)] + ) + v0 = -np.array( + [df_wind0[V].xs(i, level=0).unstack().values for i in range(0, nx)] + ) + w0 = np.array( + [df_wind0[W].xs(i, level=0).unstack().values for i in range(0, nx)] + ) + # Identify all cells needing to be updated by the wind solver and store # their coordinates in a 1D array # (exclude buildings and sketch boundaries) @@ -708,192 +903,327 @@ def main(javaEnvironmentPath, cells4Solver = cells4Solver[cells4Solver[:, 0] < nx - 1] cells4Solver = cells4Solver[cells4Solver[:, 1] < ny - 1] cells4Solver = cells4Solver[cells4Solver[:, 2] < nz - 1] - cells4Solver = cells4Solver.astype(np.int32) - + cells4Solver = cells4Solver.astype(np.int32) + # Identify building 3D coordinates - buildingCoordinates = np.stack(np.where(buildGrid3D==0)).astype(np.int32) - + buildingCoordinates = np.stack(np.where(buildGrid3D == 0)).astype(np.int32) + # Interpolation is made in order to have wind speed located on the face of # each grid cell - u0[1:nx, :, :] = (u0[0:nx-1, :, :] + u0[1:nx, :, :])/2 - v0[:, 1:ny, :] = (v0[:, 0:ny-1, :] + v0[:,1:ny,:])/2 - w0[:, :, 1:nz] = (w0[:, :, 0:nz-1] + w0[:, :, 1:nz])/2 - + u0[1:nx, :, :] = (u0[0 : nx - 1, :, :] + u0[1:nx, :, :]) / 2 + v0[:, 1:ny, :] = (v0[:, 0 : ny - 1, :] + v0[:, 1:ny, :]) / 2 + w0[:, :, 1:nz] = (w0[:, :, 0 : nz - 1] + w0[:, :, 1:nz]) / 2 + # Reset input and output wind speed to zero for building cells - u0[buildingCoordinates[0],buildingCoordinates[1],buildingCoordinates[2]] = 0 - u0[buildingCoordinates[0]+1,buildingCoordinates[1],buildingCoordinates[2]]=0 - v0[buildingCoordinates[0],buildingCoordinates[1],buildingCoordinates[2]] = 0 - v0[buildingCoordinates[0],buildingCoordinates[1]+1,buildingCoordinates[2]]=0 - w0[buildingCoordinates[0],buildingCoordinates[1],buildingCoordinates[2]] = 0 - w0[buildingCoordinates[0],buildingCoordinates[1],buildingCoordinates[2]+1]=0 - + u0[ + buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] + ] = 0 + u0[ + buildingCoordinates[0] + 1, + buildingCoordinates[1], + buildingCoordinates[2], + ] = 0 + v0[ + buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] + ] = 0 + v0[ + buildingCoordinates[0], + buildingCoordinates[1] + 1, + buildingCoordinates[2], + ] = 0 + w0[ + buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] + ] = 0 + w0[ + buildingCoordinates[0], + buildingCoordinates[1], + buildingCoordinates[2] + 1, + ] = 0 + # Create local grid space coordinates (x, y, z) - Lz = (nz-1) * dz - Lx = (nx-1) * meshSize - Ly = (ny-1) * meshSize - x = np.linspace(0, Lx, nx) + Lz = (nz - 1) * dz + Lx = (nx - 1) * meshSize + Ly = (ny - 1) * meshSize + x = np.linspace(0, Lx, nx) y = np.linspace(0, Ly, ny) z = np.linspace(0, Lz, nz) - - print("Time spent for wind speed initialization: {0} s".format(time.time()-timeStartCalculation)) - print("Shape: " + str(u0.shape) + " - " + "Nb cells: " + str(u0.shape[0] * u0.shape[1] * u0.shape[2])) + + print( + "Time spent for wind speed initialization: {0} s".format( + time.time() - timeStartCalculation + ) + ) + print( + "Shape: " + + str(u0.shape) + + " - " + + "Nb cells: " + + str(u0.shape[0] * u0.shape[1] * u0.shape[2]) + ) # ------------------------------------------------------------------- # 10. WIND SOLVER APPLICATION ---------------------------------------- - # ------------------------------------------------------------------- + # ------------------------------------------------------------------- if feedback: - feedback.setProgressText('Apply the wind solver equations') + feedback.setProgressText("Apply the wind solver equations") if feedback.isCanceled(): cursor.close() feedback.setProgressText("Calculation cancelled by user") return {} if not onlyInitialization: # Apply a mass-flow balance to have a more physical 3D wind speed field - u, v, w = \ - WindSolver.solver( x = x , y = y , z = z, - dx = meshSize , dy = meshSize , dz = dz, - u0 = u0 , v0 = v0 , w0 = w0, cursor = cursor, - buildingCoordinates = buildingCoordinates , cells4Solver = cells4Solver, - maxIterations = maxIterations, thresholdIterations = thresholdIterations, - feedback = feedback) + u, v, w = WindSolver.solver( + x=x, + y=y, + z=z, + dx=meshSize, + dy=meshSize, + dz=dz, + u0=u0, + v0=v0, + w0=w0, + cursor=cursor, + buildingCoordinates=buildingCoordinates, + cells4Solver=cells4Solver, + maxIterations=maxIterations, + thresholdIterations=thresholdIterations, + feedback=feedback, + ) else: u = u0 v = v0 w = w0 - + # Wind speed values are recentered to the middle of the cells - u[0:nx-1 ,0:ny-1 ,0:nz-1]= (u[0:nx-1, 0:ny-1, 0:nz-1] + u[1:nx, 0:ny-1, 0:nz-1])/2 - v[0:nx-1 ,0:ny-1, 0:nz-1]= (v[0:nx-1, 0:ny-1, 0:nz-1] + v[0:nx-1, 1:ny, 0:nz-1])/2 - w[0:nx-1, 0:ny-1, 0:nz-1]= (w[0:nx-1, 0:ny-1, 0:nz-1] + w[0:nx-1, 0:ny-1, 1:nz])/2 - u0[0:nx-1 ,0:ny-1 ,0:nz-1]= (u0[0:nx-1, 0:ny-1, 0:nz-1] + u0[1:nx, 0:ny-1, 0:nz-1])/2 - v0[0:nx-1 ,0:ny-1, 0:nz-1]= (v0[0:nx-1, 0:ny-1, 0:nz-1] + v0[0:nx-1, 1:ny, 0:nz-1])/2 - w0[0:nx-1, 0:ny-1, 0:nz-1]= (w0[0:nx-1, 0:ny-1, 0:nz-1] + w0[0:nx-1, 0:ny-1, 1:nz])/2 - + u[0 : nx - 1, 0 : ny - 1, 0 : nz - 1] = ( + u[0 : nx - 1, 0 : ny - 1, 0 : nz - 1] + u[1:nx, 0 : ny - 1, 0 : nz - 1] + ) / 2 + v[0 : nx - 1, 0 : ny - 1, 0 : nz - 1] = ( + v[0 : nx - 1, 0 : ny - 1, 0 : nz - 1] + v[0 : nx - 1, 1:ny, 0 : nz - 1] + ) / 2 + w[0 : nx - 1, 0 : ny - 1, 0 : nz - 1] = ( + w[0 : nx - 1, 0 : ny - 1, 0 : nz - 1] + w[0 : nx - 1, 0 : ny - 1, 1:nz] + ) / 2 + u0[0 : nx - 1, 0 : ny - 1, 0 : nz - 1] = ( + u0[0 : nx - 1, 0 : ny - 1, 0 : nz - 1] + + u0[1:nx, 0 : ny - 1, 0 : nz - 1] + ) / 2 + v0[0 : nx - 1, 0 : ny - 1, 0 : nz - 1] = ( + v0[0 : nx - 1, 0 : ny - 1, 0 : nz - 1] + + v0[0 : nx - 1, 1:ny, 0 : nz - 1] + ) / 2 + w0[0 : nx - 1, 0 : ny - 1, 0 : nz - 1] = ( + w0[0 : nx - 1, 0 : ny - 1, 0 : nz - 1] + + w0[0 : nx - 1, 0 : ny - 1, 1:nz] + ) / 2 + # Reset input and output wind speed to zero for building cells - u[buildingCoordinates[0],buildingCoordinates[1],buildingCoordinates[2]] = 0 - v[buildingCoordinates[0],buildingCoordinates[1],buildingCoordinates[2]] = 0 - w[buildingCoordinates[0],buildingCoordinates[1],buildingCoordinates[2]] = 0 - u0[buildingCoordinates[0],buildingCoordinates[1],buildingCoordinates[2]] = 0 - v0[buildingCoordinates[0],buildingCoordinates[1],buildingCoordinates[2]] = 0 - w0[buildingCoordinates[0],buildingCoordinates[1],buildingCoordinates[2]] = 0 - + u[ + buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] + ] = 0 + v[ + buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] + ] = 0 + w[ + buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] + ] = 0 + u0[ + buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] + ] = 0 + v0[ + buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] + ] = 0 + w0[ + buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] + ] = 0 + # ------------------------------------------------------------------- # 11. ROTATE THE WIND FIELD TO THE INITIAL DISPOSITION -------------- - # ------------------------------------------------------------------- + # ------------------------------------------------------------------- # Get the relative position of the upper right corner of the grid from # the center of rotation used to rotate the grid - cursor.execute(safe( - """{0};{1} - """).format(DataUtil.createIndex(tableName=gridPoint, - fieldName=ID_POINT_X, - isSpatial=False), - DataUtil.createIndex(tableName=gridPoint, - fieldName=ID_POINT_Y, - isSpatial=False))) - cursor.execute(safe( - """ + cursor.execute( + safe("""{0};{1} + """).format( + DataUtil.createIndex( + tableName=gridPoint, fieldName=ID_POINT_X, isSpatial=False + ), + DataUtil.createIndex( + tableName=gridPoint, fieldName=ID_POINT_Y, isSpatial=False + ), + ) + ) + cursor.execute( + safe(""" SELECT {3}-ST_X(a.{0}) AS DIST_ROT_X, {4}-ST_Y(a.{0}) AS DIST_ROT_Y FROM {5} AS a WHERE a.{1} = (SELECT MAX({1}) FROM {5}) AND a.{2} = (SELECT MAX({2}) FROM {5}) - """).format(GEOM_FIELD , ID_POINT_X, - ID_POINT_Y , rotationCenterCoordinates[0], - rotationCenterCoordinates[1] , gridPoint)) + """).format( + GEOM_FIELD, + ID_POINT_X, + ID_POINT_Y, + rotationCenterCoordinates[0], + rotationCenterCoordinates[1], + gridPoint, + ) + ) dist_rot_x, dist_rot_y = cursor.fetchall()[0] x += dist_rot_x y += dist_rot_y - + x_rot = np.zeros((nx, ny)) y_rot = np.zeros((nx, ny)) - x_rot, y_rot, u_rot, v_rot = rotateData(theta = -windDirection*np.pi/180, nx = nx, - ny = ny , nz = nz, - x = x , y = y, - x_rot = x_rot , y_rot = y_rot, - u = u , v = v) - - x_rot, y_rot, u0_rot, v0_rot = rotateData(theta = -windDirection*np.pi/180 , nx = nx, - ny = ny , nz = nz, - x = x , y = y, - x_rot = x_rot , y_rot = y_rot, - u = u0 , v = v0) + x_rot, y_rot, u_rot, v_rot = rotateData( + theta=-windDirection * np.pi / 180, + nx=nx, + ny=ny, + nz=nz, + x=x, + y=y, + x_rot=x_rot, + y_rot=y_rot, + u=u, + v=v, + ) + + x_rot, y_rot, u0_rot, v0_rot = rotateData( + theta=-windDirection * np.pi / 180, + nx=nx, + ny=ny, + nz=nz, + x=x, + y=y, + x_rot=x_rot, + y_rot=y_rot, + u=u0, + v=v0, + ) # Set the real (x,y) grid coordinates x_rot += rotationCenterCoordinates[0] y_rot += rotationCenterCoordinates[1] - + # ------------------------------------------------------------------- # 12. SAVE EACH OF THE UROCK OUTPUT --------------------------------- - # ------------------------------------------------------------------- + # ------------------------------------------------------------------- # First rotate the coordinates of the grid of points - rotated_grid = Obstacles.windRotation(cursor = cursor, - dicOfInputTables = {gridPoint: gridPoint}, - rotateAngle = - windDirection, - rotationCenterCoordinates = rotationCenterCoordinates)[0][gridPoint] - - dicVectorTables, netcdf_path =\ - saveData.saveBasicOutputs(cursor = cursor , z_out = z_out, - dz = dz , u = u_rot, - v = v_rot , w = w, - gridName = rotated_grid , verticalWindProfile = verticalWindProfile, - outputFilePath = outputFilePath, outputFilename = outputFilename, - meshSize = meshSize , outputRaster = outputRaster, - saveRaster = saveRaster , saveVector = saveVector, - saveNetcdf = saveNetcdf , prefix_name = prefix, - stacked_blocks = outputDataAbs["stacked_blocks"], - tmp_dir = tmp_dir_unique) - + rotated_grid = Obstacles.windRotation( + cursor=cursor, + dicOfInputTables={gridPoint: gridPoint}, + rotateAngle=-windDirection, + rotationCenterCoordinates=rotationCenterCoordinates, + )[0][gridPoint] + + dicVectorTables, netcdf_path = saveData.saveBasicOutputs( + cursor=cursor, + z_out=z_out, + dz=dz, + u=u_rot, + v=v_rot, + w=w, + gridName=rotated_grid, + verticalWindProfile=verticalWindProfile, + outputFilePath=outputFilePath, + outputFilename=outputFilename, + meshSize=meshSize, + outputRaster=outputRaster, + saveRaster=saveRaster, + saveVector=saveVector, + saveNetcdf=saveNetcdf, + prefix_name=prefix, + stacked_blocks=outputDataAbs["stacked_blocks"], + tmp_dir=tmp_dir_unique, + ) + # Save also the initialisation field if needed if debug: init_tmp_dir = os.path.join(tmp_dir_unique, "wind_initialisation") os.mkdir(init_tmp_dir) - dicVectorTables_ini, netcdf_path_ini =\ - saveData.saveBasicOutputs(cursor = cursor , z_out = z_out, - dz = dz , u = u0_rot, - v = v0_rot , w = w0, - gridName = rotated_grid , verticalWindProfile = verticalWindProfile, - outputFilePath = init_tmp_dir , outputFilename = outputFilename, - meshSize = meshSize , outputRaster = outputRaster, - saveRaster = saveRaster , saveVector = saveVector, - saveNetcdf = saveNetcdf , prefix_name = prefix, - stacked_blocks = outputDataAbs["stacked_blocks"], - tmp_dir = tmp_dir_unique) + dicVectorTables_ini, netcdf_path_ini = saveData.saveBasicOutputs( + cursor=cursor, + z_out=z_out, + dz=dz, + u=u0_rot, + v=v0_rot, + w=w0, + gridName=rotated_grid, + verticalWindProfile=verticalWindProfile, + outputFilePath=init_tmp_dir, + outputFilename=outputFilename, + meshSize=meshSize, + outputRaster=outputRaster, + saveRaster=saveRaster, + saveVector=saveVector, + saveNetcdf=saveNetcdf, + prefix_name=prefix, + stacked_blocks=outputDataAbs["stacked_blocks"], + tmp_dir=tmp_dir_unique, + ) else: dicVectorTables_ini = None netcdf_path_ini = None # Last save the 2D grid for each Röckle zone - saveData.saveRockleZones(cursor = cursor, - outputDataAbs = outputDataAbs, - dicOfBuildZoneGridPoint = dicOfBuildZoneGridPoint, - dicOfVegZoneGridPoint = dicOfVegZoneGridPoint, - gridPoint = gridPoint, - rotationCenterCoordinates = rotationCenterCoordinates, - windDirection = windDirection) - + saveData.saveRockleZones( + cursor=cursor, + outputDataAbs=outputDataAbs, + dicOfBuildZoneGridPoint=dicOfBuildZoneGridPoint, + dicOfVegZoneGridPoint=dicOfVegZoneGridPoint, + gridPoint=gridPoint, + rotationCenterCoordinates=rotationCenterCoordinates, + windDirection=windDirection, + ) + # Close the Database connection and remove temporary files if not debug: - H2gisConnection.closeAndRemoveH2gisInstance(localH2InstanceDir = localH2InstanceDir, - conn = conn, - cur = cursor) - #rmtree(tmp_dir_unique, ignore_errors=True) + H2gisConnection.closeAndRemoveH2gisInstance( + localH2InstanceDir=localH2InstanceDir, conn=conn, cur=cursor + ) + # rmtree(tmp_dir_unique, ignore_errors=True) + + return ( + u_rot, + v_rot, + w, + u0_rot, + v0_rot, + w0, + x_rot, + y_rot, + z, + buildingCoordinates, + cursor, + rotated_grid, + rotationCenterCoordinates, + verticalWindProfile, + dicVectorTables, + netcdf_path, + netcdf_path_ini, + ) - return u_rot, v_rot, w, u0_rot, v0_rot, w0, x_rot, y_rot, z,\ - buildingCoordinates, cursor, rotated_grid, rotationCenterCoordinates,\ - verticalWindProfile, dicVectorTables, netcdf_path, netcdf_path_ini @jit(nopython=True) def rotateData(theta, nx, ny, nz, x, y, x_rot, y_rot, u, v): u_rot = np.zeros(u.shape) v_rot = np.zeros(v.shape) - rot = np.array([[math.cos(theta), -math.sin(theta)], - [math.sin(theta), math.cos(theta)]]) + rot = np.array( + [ + [math.cos(theta), -math.sin(theta)], + [math.sin(theta), math.cos(theta)], + ] + ) xmax = x.max() ymax = y.max() for i in range(nx): for j in range(ny): - x_rot[i,j] = (xmax-x[i]) * rot[0,0] + (ymax-y[j]) * rot[0,1] - y_rot[i,j] = (xmax-x[i]) * rot[1,0] + (ymax-y[j]) * rot[1,1] + x_rot[i, j] = (xmax - x[i]) * rot[0, 0] + (ymax - y[j]) * rot[0, 1] + y_rot[i, j] = (xmax - x[i]) * rot[1, 0] + (ymax - y[j]) * rot[1, 1] for k in range(nz): - u_rot[i, j, k] = u[i,j,k] * rot[0,0] + v[i,j,k] * rot[0,1] - v_rot[i, j, k] = u[i,j,k] * rot[1,0] + v[i,j,k] * rot[1,1] - - return x_rot, y_rot, u_rot, v_rot \ No newline at end of file + u_rot[i, j, k] = ( + u[i, j, k] * rot[0, 0] + v[i, j, k] * rot[0, 1] + ) + v_rot[i, j, k] = ( + u[i, j, k] * rot[1, 0] + v[i, j, k] * rot[1, 1] + ) + + return x_rot, y_rot, u_rot, v_rot diff --git a/functions/URock/Obstacles.py b/functions/URock/Obstacles.py index a809cf8..8464078 100644 --- a/functions/URock/Obstacles.py +++ b/functions/URock/Obstacles.py @@ -5,25 +5,32 @@ @author: Jérémy Bernard, University of Gothenburg """ + from . import DataUtil as DataUtil import pandas as pd -from .GlobalVariables import * +from .GlobalVariables import * from .DataUtil import safe -def windRotation(cursor, dicOfInputTables, rotateAngle, rotationCenterCoordinates = None, - prefix = PREFIX_NAME): - """ Rotates of 'rotateAngle' degrees counter-clockwise the geometries + +def windRotation( + cursor, + dicOfInputTables, + rotateAngle, + rotationCenterCoordinates=None, + prefix=PREFIX_NAME, +): + """Rotates of 'rotateAngle' degrees counter-clockwise the geometries of all tables from the 'rotationCenterCoordinates' specified by the user. If none is specified, the center of rotation used is the most North-East point of the enveloppe of all geometries contained in all tables. - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries dicOfInputTables: dictionary of String - Dictionary of String with type of obstacle as key and input + Dictionary of String with type of obstacle as key and input table name as value (tables containing the geometries to rotate) rotateAngle: float Counter clock-wise rotation angle (in degree) @@ -31,107 +38,129 @@ def windRotation(cursor, dicOfInputTables, rotateAngle, rotationCenterCoordinate x and y values of the point used as center of rotation prefix: String, default PREFIX_NAME Prefix to add to the output table name - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ dicOfRotateTables: dictionary Map of initial table names as keys and rotated table names as values rotationCenterCoordinates: tuple of float x and y values of the point used as center of rotation""" print("Rotates geometries from {0} degrees".format(rotateAngle)) - + # Calculate the rotation angle in radian rotateAngleRad = DataUtil.degToRad(rotateAngle) - + # If not specified, get the most North-East point of the envelope of all # geometries of all tables as the center of rotation if rotationCenterCoordinates is None: - queryUnionTables = " UNION ALL ".join([safe(""" + queryUnionTables = " UNION ALL ".join( + [ + safe(""" SELECT {0} FROM ST_EXPLODE('(SELECT {0} FROM {1})') - """).format( GEOM_FIELD, - t) - for t in dicOfInputTables.values()]) + """).format(GEOM_FIELD, t) + for t in dicOfInputTables.values() + ] + ) cursor.execute(safe(""" SELECT ST_XMAX(ST_EXTENT({0})), ST_YMAX(ST_EXTENT({0})) FROM ({1})""").format(GEOM_FIELD, queryUnionTables)) rotationCenterCoordinates = cursor.fetchall()[0] - + columnNames = {} # Store the column names (except geometry field) of each table into a dictionary for i, t in enumerate(dicOfInputTables.values()): - columnNames[t] = DataUtil.getColumns(cursor = cursor, - tableName = t) + columnNames[t] = DataUtil.getColumns(cursor=cursor, tableName=t) columnNames[t].remove(GEOM_FIELD) - + # Rotate table in one query in order to limit the number of connections - dicOfRotateTables = {t: dicOfInputTables[t]+"_ROTATED" for t in dicOfInputTables.keys()} - sqlRotateQueries = [safe(""" + dicOfRotateTables = { + t: dicOfInputTables[t] + "_ROTATED" for t in dicOfInputTables.keys() + } + sqlRotateQueries = [ + safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0} AS SELECT ST_MAKEVALID(ST_ROTATE({1}, {2}, {3}, {4})) AS {1}, {5} - FROM {6}""").format( dicOfRotateTables[t],\ - GEOM_FIELD,\ - rotateAngleRad, - rotationCenterCoordinates[0], - rotationCenterCoordinates[1], - ",".join(columnNames[dicOfInputTables[t]]), - dicOfInputTables[t]) for t in dicOfRotateTables.keys()] + FROM {6}""").format( + dicOfRotateTables[t], + GEOM_FIELD, + rotateAngleRad, + rotationCenterCoordinates[0], + rotationCenterCoordinates[1], + ",".join(columnNames[dicOfInputTables[t]]), + dicOfInputTables[t], + ) + for t in dicOfRotateTables.keys() + ] cursor.execute(";".join(sqlRotateQueries)) - - return dicOfRotateTables, rotationCenterCoordinates -def createsBlocks(cursor, inputBuildings, snappingTolerance = GEOMETRY_MERGE_TOLERANCE, - prefix = PREFIX_NAME): - """ Creates blocks and stacked blocks from buildings touching each other. - - Parameters - _ _ _ _ _ _ _ _ _ _ + return dicOfRotateTables, rotationCenterCoordinates - cursor: conn.cursor - A cursor object, used to perform spatial SQL queries - inputBuildings: String - Name of the table containing building geometries and height - snappingTolerance: float, default GEOMETRY_MERGE_TOLERANCE - Distance in meter below which two buildings are - considered as touching each other (m) - prefix: String, default PREFIX_NAME - Prefix to add to the output table name - - Returns - _ _ _ _ _ _ _ _ _ _ - blockTable: String - Name of the table containing the block geometries - (only block of touching buildings independantly of their height) - stackedBlockTable: String - Name of the table containing blocks considering the vertical dimension - (only buildings having the same height are merged)""" +def createsBlocks( + cursor, + inputBuildings, + snappingTolerance=GEOMETRY_MERGE_TOLERANCE, + prefix=PREFIX_NAME, +): + """Creates blocks and stacked blocks from buildings touching each other. + + Parameters + _ _ _ _ _ _ _ _ _ _ + + cursor: conn.cursor + A cursor object, used to perform spatial SQL queries + inputBuildings: String + Name of the table containing building geometries and height + snappingTolerance: float, default GEOMETRY_MERGE_TOLERANCE + Distance in meter below which two buildings are + considered as touching each other (m) + prefix: String, default PREFIX_NAME + Prefix to add to the output table name + + Returns + _ _ _ _ _ _ _ _ _ _ + + blockTable: String + Name of the table containing the block geometries + (only block of touching buildings independantly of their height) + stackedBlockTable: String + Name of the table containing blocks considering the vertical dimension + (only buildings having the same height are merged)""" print("Creates blocks and stacked blocks") - + # Create temporary table names (for tables that will be removed at the end of the IProcess) correlTable = DataUtil.postfix("correl_table") - + # Creates final tables - blockTable = DataUtil.prefix("block_table", prefix = prefix) - stackedBlockTable = DataUtil.prefix("stacked_block_table", prefix = prefix) + blockTable = DataUtil.prefix("block_table", prefix=prefix) + stackedBlockTable = DataUtil.prefix("stacked_block_table", prefix=prefix) # Creates the block (a method based on network - such as H2network # would be much more efficient) - cursor.execute(safe(""" + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0} AS SELECT EXPLOD_ID AS {1}, ST_MAKEVALID(ST_SIMPLIFY(ST_NORMALIZE({2}), {5})) AS {2} FROM ST_EXPLODE ('(SELECT ST_UNION(ST_ACCUM(ST_BUFFER({2},{3},''join=mitre''))) AS {2} FROM {4})'); - """).format(blockTable , ID_FIELD_BLOCK, - GEOM_FIELD , snappingTolerance, - inputBuildings , GEOMETRY_SIMPLIFICATION_DISTANCE)) - + """).format( + blockTable, + ID_FIELD_BLOCK, + GEOM_FIELD, + snappingTolerance, + inputBuildings, + GEOMETRY_SIMPLIFICATION_DISTANCE, + ) + ) + # Identify building/block relations and convert building height to integer - cursor.execute(safe(""" + cursor.execute( + safe(""" {7}; {8}; DROP TABLE IF EXISTS {0}; @@ -140,34 +169,48 @@ def createsBlocks(cursor, inputBuildings, snappingTolerance = GEOMETRY_MERGE_TOL b.{2} AS GEOM_BLOCK FROM {5} AS a, {6} AS b WHERE a.{2} && b.{2} AND ST_INTERSECTS(a.{2}, b.{2}); - """).format( correlTable , ID_FIELD_BUILD, - GEOM_FIELD , HEIGHT_FIELD, - ID_FIELD_BLOCK , inputBuildings, - blockTable , DataUtil.createIndex( tableName=inputBuildings, - fieldName=GEOM_FIELD, - isSpatial=True), - DataUtil.createIndex(tableName=blockTable, - fieldName=GEOM_FIELD, - isSpatial=True))) - + """).format( + correlTable, + ID_FIELD_BUILD, + GEOM_FIELD, + HEIGHT_FIELD, + ID_FIELD_BLOCK, + inputBuildings, + blockTable, + DataUtil.createIndex( + tableName=inputBuildings, fieldName=GEOM_FIELD, isSpatial=True + ), + DataUtil.createIndex( + tableName=blockTable, fieldName=GEOM_FIELD, isSpatial=True + ), + ) + ) + # Identify all possible values of height for buildings being in a more than 1 building block - cursor.execute(safe(""" + cursor.execute( + safe(""" {0}; - """).format(DataUtil.createIndex( tableName=correlTable, - fieldName=ID_FIELD_BLOCK, - isSpatial=False))) + """).format( + DataUtil.createIndex( + tableName=correlTable, + fieldName=ID_FIELD_BLOCK, + isSpatial=False, + ) + ) + ) cursor.execute(safe(""" SELECT DISTINCT a.{2} FROM {0} AS a RIGHT JOIN (SELECT {1} FROM {0} GROUP BY {1}) AS b ON a.{1} = b.{1}; - """).format( correlTable , ID_FIELD_BLOCK, - HEIGHT_FIELD)) + """).format(correlTable, ID_FIELD_BLOCK, HEIGHT_FIELD)) listOfHeight = cursor.fetchall() if len(listOfHeight) > 0: - df_listOfHeight = pd.DataFrame(listOfHeight).dropna()[0].astype(int).values - + df_listOfHeight = ( + pd.DataFrame(listOfHeight).dropna()[0].astype(int).values + ) + # Create stacked blocks according to building blocks and height - listOfSqlQueries = [ # nosec B608 + listOfSqlQueries = [ # nosec B608 f""" SELECT {ID_FIELD_BLOCK}, ST_MAKEVALID(ST_NORMALIZE({GEOM_FIELD})) AS {GEOM_FIELD} , {height_i} AS {HEIGHT_FIELD} FROM ST_EXPLODE('(SELECT ST_MAKEVALID(ST_SNAP(ST_SIMPLIFY(ST_UNION(ST_ACCUM(ST_BUFFER(a.{GEOM_FIELD}, {snappingTolerance}, @@ -183,7 +226,9 @@ def createsBlocks(cursor, inputBuildings, snappingTolerance = GEOMETRY_MERGE_TOL ON a.{ID_FIELD_BLOCK}=b.{ID_FIELD_BLOCK} WHERE a.{HEIGHT_FIELD}>={height_i} GROUP BY a.{ID_FIELD_BLOCK})') WHERE ST_ISEMPTY({GEOM_FIELD}) IS FALSE - """ for height_i in df_listOfHeight] # nosec B608 + """# nosec B608 + for height_i in df_listOfHeight + ] # nosec B608 cursor.execute(f""" DROP TABLE IF EXISTS {stackedBlockTable}; CREATE TABLE {stackedBlockTable}({ID_FIELD_STACKED_BLOCK} BIGINT AUTO_INCREMENT, @@ -195,31 +240,39 @@ def createsBlocks(cursor, inputBuildings, snappingTolerance = GEOMETRY_MERGE_TOL {GEOM_FIELD}, {HEIGHT_FIELD} FROM ({" UNION ALL ".join(listOfSqlQueries)}) - """) # nosec B608 - + """) # nosec B608 + else: # Create an empty block table - cursor.execute(safe(""" + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0}({1} BIGINT AUTO_INCREMENT, {2} INT, {3} GEOMETRY, {4} INT) - """).format(stackedBlockTable, ID_FIELD_STACKED_BLOCK, ID_FIELD_BLOCK, - GEOM_FIELD, HEIGHT_FIELD)) - + """).format( + stackedBlockTable, + ID_FIELD_STACKED_BLOCK, + ID_FIELD_BLOCK, + GEOM_FIELD, + HEIGHT_FIELD, + ) + ) + if not DEBUG: # Drop intermediate tables - cursor.execute("DROP TABLE IF EXISTS {0}".format(",".join([correlTable]))) - + cursor.execute( + "DROP TABLE IF EXISTS {0}".format(",".join([correlTable])) + ) + return blockTable, stackedBlockTable -def identifyBlockAndCavityBase(cursor, stackedBlockTable, - prefix = PREFIX_NAME): - """ Identify the base of each block and the base of their cavity zone +def identifyBlockAndCavityBase(cursor, stackedBlockTable, prefix=PREFIX_NAME): + """Identify the base of each block and the base of their cavity zone (which may go within the cavity zone of the base block where they sit). WARNING: THE CAVITY BASE HEIGHT DEPENDS ON WIND DIRECTION - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries @@ -227,9 +280,9 @@ def identifyBlockAndCavityBase(cursor, stackedBlockTable, Name of the table containing stacked blocks with block id prefix: String, default PREFIX_NAME Prefix to add to the output table name - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ stackedBlockPropTable: String Name of the table containing stacked blocks with block base @@ -240,15 +293,16 @@ def identifyBlockAndCavityBase(cursor, stackedBlockTable, tempoAllStacked = DataUtil.postfix("tempo_all_stacked_table") tempoAllBlocks = DataUtil.postfix("tempo_all_blocks_table") tempoCavityStacked = DataUtil.postfix("tempo_cavity_stacked_table") - tempoAllCavityStacked = DataUtil.postfix("tempo_all_cavity_stacked_table") - - # Creates final table - stackedBlockPropTable = DataUtil.prefix("stacked_block_prop_table", - prefix = prefix) + tempoAllCavityStacked = DataUtil.postfix("tempo_all_cavity_stacked_table") + # Creates final table + stackedBlockPropTable = DataUtil.prefix( + "stacked_block_prop_table", prefix=prefix + ) # Identify each block base height and ratio of area between the stacked and its base block - cursor.execute(safe(""" + cursor.execute( + safe(""" {7}; {8}; {9}; @@ -261,21 +315,35 @@ def identifyBlockAndCavityBase(cursor, stackedBlockTable, WHERE a.{3} > b.{3} AND a.{1} && b.{1} AND (ST_CONTAINS(b.{1}, a.{1}) OR ST_OVERLAPS(b.{1}, a.{1})) GROUP BY a.{5}, a.{2} - """).format( stackedBlockTable , GEOM_FIELD, - ID_FIELD_BLOCK , HEIGHT_FIELD, - tempoAllStacked , ID_FIELD_STACKED_BLOCK, - BASE_HEIGHT_FIELD , DataUtil.createIndex(tableName=stackedBlockTable, - fieldName=GEOM_FIELD, - isSpatial=True), - DataUtil.createIndex(tableName=stackedBlockTable, - fieldName=ID_FIELD_BLOCK, - isSpatial=False), - DataUtil.createIndex(tableName=stackedBlockTable, - fieldName=HEIGHT_FIELD, - isSpatial=False))) - + """).format( + stackedBlockTable, + GEOM_FIELD, + ID_FIELD_BLOCK, + HEIGHT_FIELD, + tempoAllStacked, + ID_FIELD_STACKED_BLOCK, + BASE_HEIGHT_FIELD, + DataUtil.createIndex( + tableName=stackedBlockTable, + fieldName=GEOM_FIELD, + isSpatial=True, + ), + DataUtil.createIndex( + tableName=stackedBlockTable, + fieldName=ID_FIELD_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=stackedBlockTable, + fieldName=HEIGHT_FIELD, + isSpatial=False, + ), + ) + ) + # Set the base height to ground base buildings... - cursor.execute(safe(""" + cursor.execute( + safe(""" {8}; {9}; DROP TABLE IF EXISTS {4}; @@ -283,20 +351,32 @@ def identifyBlockAndCavityBase(cursor, stackedBlockTable, AS SELECT b.{1}, b.{2}, b.{5}, b.{3}, COALESCE(a.{6}, 0) AS {6}, COALESCE(a.AREA_RATIO, 0) AS AREA_RATIO FROM {0} AS a RIGHT JOIN {7} AS b ON a.{5} = b.{5} - """).format( tempoAllStacked , GEOM_FIELD, - ID_FIELD_BLOCK , HEIGHT_FIELD, - tempoAllBlocks , ID_FIELD_STACKED_BLOCK, - BASE_HEIGHT_FIELD , stackedBlockTable, - DataUtil.createIndex(tableName=tempoAllStacked, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(tableName=stackedBlockTable, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False))) + """).format( + tempoAllStacked, + GEOM_FIELD, + ID_FIELD_BLOCK, + HEIGHT_FIELD, + tempoAllBlocks, + ID_FIELD_STACKED_BLOCK, + BASE_HEIGHT_FIELD, + stackedBlockTable, + DataUtil.createIndex( + tableName=tempoAllStacked, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=stackedBlockTable, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + ) + ) # Calculates the depth where the cavity zone # of a upper stacked block may go within the base block cavity zone - cursor.execute(safe(""" + cursor.execute( + safe(""" {8}; {9}; {10}; @@ -308,22 +388,34 @@ def identifyBlockAndCavityBase(cursor, stackedBlockTable, WHERE a.{3} > b.{3} AND a.{1} && b.{1} AND (ST_CONTAINS(b.{1}, a.{1}) OR ST_OVERLAPS(b.{1}, a.{1})) GROUP BY a.{5}, a.{2} - """).format( tempoAllBlocks , GEOM_FIELD, - ID_FIELD_BLOCK , HEIGHT_FIELD, - tempoCavityStacked , ID_FIELD_STACKED_BLOCK, - BASE_HEIGHT_FIELD , CAVITY_BASE_HEIGHT_FIELD, - DataUtil.createIndex(tableName=tempoAllBlocks, - fieldName=GEOM_FIELD, - isSpatial=True), - DataUtil.createIndex(tableName=tempoAllBlocks, - fieldName=ID_FIELD_BLOCK, - isSpatial=False), - DataUtil.createIndex(tableName=tempoAllBlocks, - fieldName=HEIGHT_FIELD, - isSpatial=False))) - - # Same as previous for stacked buildings being above a ground building (not a stacked one...) - cursor.execute(safe(""" + """).format( + tempoAllBlocks, + GEOM_FIELD, + ID_FIELD_BLOCK, + HEIGHT_FIELD, + tempoCavityStacked, + ID_FIELD_STACKED_BLOCK, + BASE_HEIGHT_FIELD, + CAVITY_BASE_HEIGHT_FIELD, + DataUtil.createIndex( + tableName=tempoAllBlocks, fieldName=GEOM_FIELD, isSpatial=True + ), + DataUtil.createIndex( + tableName=tempoAllBlocks, + fieldName=ID_FIELD_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=tempoAllBlocks, + fieldName=HEIGHT_FIELD, + isSpatial=False, + ), + ) + ) + + # Same as previous for stacked buildings being above a ground building (not a stacked one...) + cursor.execute( + safe(""" {9}; {10}; DROP TABLE IF EXISTS {4}; @@ -332,19 +424,32 @@ def identifyBlockAndCavityBase(cursor, stackedBlockTable, COALESCE(a.{7}, b.{6}*(1-b.AREA_RATIO)) AS {7} FROM {0} AS a RIGHT JOIN {8} AS b ON a.{5} = b.{5} - """).format( tempoCavityStacked , GEOM_FIELD, - ID_FIELD_BLOCK , HEIGHT_FIELD, - tempoAllCavityStacked , ID_FIELD_STACKED_BLOCK, - BASE_HEIGHT_FIELD , CAVITY_BASE_HEIGHT_FIELD, - tempoAllStacked , DataUtil.createIndex( tableName=tempoCavityStacked, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(tableName=tempoAllStacked, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False))) - + """).format( + tempoCavityStacked, + GEOM_FIELD, + ID_FIELD_BLOCK, + HEIGHT_FIELD, + tempoAllCavityStacked, + ID_FIELD_STACKED_BLOCK, + BASE_HEIGHT_FIELD, + CAVITY_BASE_HEIGHT_FIELD, + tempoAllStacked, + DataUtil.createIndex( + tableName=tempoCavityStacked, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=tempoAllStacked, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + ) + ) + # Join blocks being not stacked - cursor.execute(safe(""" + cursor.execute( + safe(""" {9}; {10}; DROP TABLE IF EXISTS {3}; @@ -353,35 +458,55 @@ def identifyBlockAndCavityBase(cursor, stackedBlockTable, COALESCE(b.{6}, 0) AS {6}, COALESCE(b.{7}, 0) AS {7} FROM {0} AS a LEFT JOIN {2} AS b ON a.{1} = b.{1} - """).format( stackedBlockTable , ID_FIELD_STACKED_BLOCK, - tempoAllCavityStacked , stackedBlockPropTable, - GEOM_FIELD , ID_FIELD_BLOCK, - BASE_HEIGHT_FIELD , CAVITY_BASE_HEIGHT_FIELD, - HEIGHT_FIELD , DataUtil.createIndex( tableName=stackedBlockTable, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(tableName=tempoAllCavityStacked, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False))) + """).format( + stackedBlockTable, + ID_FIELD_STACKED_BLOCK, + tempoAllCavityStacked, + stackedBlockPropTable, + GEOM_FIELD, + ID_FIELD_BLOCK, + BASE_HEIGHT_FIELD, + CAVITY_BASE_HEIGHT_FIELD, + HEIGHT_FIELD, + DataUtil.createIndex( + tableName=stackedBlockTable, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=tempoAllCavityStacked, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + ) + ) if not DEBUG: # Drop intermediate tables - cursor.execute("DROP TABLE IF EXISTS {0}".format(",".join([tempoAllStacked, - tempoCavityStacked, - tempoAllCavityStacked, - tempoAllBlocks]))) - + cursor.execute( + "DROP TABLE IF EXISTS {0}".format( + ",".join( + [ + tempoAllStacked, + tempoCavityStacked, + tempoAllCavityStacked, + tempoAllBlocks, + ] + ) + ) + ) + return stackedBlockPropTable -def initUpwindFacades(cursor, obstaclesTable, prefix = PREFIX_NAME): - """ Identify upwind facades, convert them to lines (they are initially - included within polygons) and calculates their direction from wind speed +def initUpwindFacades(cursor, obstaclesTable, prefix=PREFIX_NAME): + """Identify upwind facades, convert them to lines (they are initially + included within polygons) and calculates their direction from wind speed (90° for a facade perpendicular from the upwind). Also get the base height of each facade. - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries @@ -389,22 +514,23 @@ def initUpwindFacades(cursor, obstaclesTable, prefix = PREFIX_NAME): Name of the table containing the obstacle geometries prefix: String, default PREFIX_NAME Prefix to add to the output table name - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ upwindTable: String Name of the table containing the upwind obstacle facades""" print("Initializes upwind facades") - + # Output base name outputBaseName = "UPWIND_INIT" - + # Name of the output table - upwindTable = DataUtil.prefix(outputBaseName, prefix = prefix) - + upwindTable = DataUtil.prefix(outputBaseName, prefix=prefix) + # Identify upwind facade - cursor.execute(safe(""" + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0}({5} BIGINT AUTO_INCREMENT, {1} INTEGER, {2} GEOMETRY, {3} DOUBLE, {6} INTEGER, {7} INTEGER, {8} INTEGER, {9} DOUBLE, @@ -433,29 +559,32 @@ def initUpwindFacades(cursor, obstaclesTable, prefix = PREFIX_NAME): FROM {4})') WHERE ST_AZIMUTH(ST_STARTPOINT({2}), ST_ENDPOINT({2})) < PI() - """).format( upwindTable, - ID_FIELD_STACKED_BLOCK, - GEOM_FIELD, - UPWIND_FACADE_ANGLE_FIELD, - obstaclesTable, - UPWIND_FACADE_FIELD, - HEIGHT_FIELD, - BASE_HEIGHT_FIELD, - ID_FIELD_BLOCK, - STACKED_BLOCK_X_MED, - STACKED_BLOCK_WIDTH, - DISPLACEMENT_LENGTH_FIELD, - DISPLACEMENT_LENGTH_VORTEX_FIELD)) - + """).format( + upwindTable, + ID_FIELD_STACKED_BLOCK, + GEOM_FIELD, + UPWIND_FACADE_ANGLE_FIELD, + obstaclesTable, + UPWIND_FACADE_FIELD, + HEIGHT_FIELD, + BASE_HEIGHT_FIELD, + ID_FIELD_BLOCK, + STACKED_BLOCK_X_MED, + STACKED_BLOCK_WIDTH, + DISPLACEMENT_LENGTH_FIELD, + DISPLACEMENT_LENGTH_VORTEX_FIELD, + ) + ) + return upwindTable -def updateUpwindFacadeBase(cursor, upwindTable, prefix = PREFIX_NAME): - """ Update the base height of each upwind facade (when shared with a facade +def updateUpwindFacadeBase(cursor, upwindTable, prefix=PREFIX_NAME): + """Update the base height of each upwind facade (when shared with a facade of a stacked block below). - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries @@ -463,26 +592,27 @@ def updateUpwindFacadeBase(cursor, upwindTable, prefix = PREFIX_NAME): Name of the table containing the initialized upwind facades prefix: String, default PREFIX_NAME Prefix to add to the output table name - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ updatedUpwindBaseTable: String Name of the table containing the upwind obstacle facades with updated base height""" print("Update upwind facades base height") - + # Create temporary table names (for tables that will be removed at the end of the IProcess) tempoUpwind = DataUtil.postfix("tempo_upwind") - + # Output base name outputBaseName = "UPWIND_UPDATED_BASE" - + # Name of the output table - updatedUpwindBaseTable = DataUtil.prefix(outputBaseName, prefix = prefix) - + updatedUpwindBaseTable = DataUtil.prefix(outputBaseName, prefix=prefix) + # Update base height for facades being shared with the block below - cursor.execute(safe(""" + cursor.execute( + safe(""" {10}; {11}; {12}; @@ -496,26 +626,39 @@ def updateUpwindFacadeBase(cursor, upwindTable, prefix = PREFIX_NAME): AND ST_DIMENSION(ST_INTERSECTION(ST_SNAP(a.{1}, b.{1}, {9}), b.{1}))=1 GROUP BY a.{5}, a.{2}, a.{8} - """).format( upwindTable , GEOM_FIELD, - ID_FIELD_BLOCK , HEIGHT_FIELD, - tempoUpwind , ID_FIELD_STACKED_BLOCK, - BASE_HEIGHT_FIELD , UPWIND_FACADE_ANGLE_FIELD, - UPWIND_FACADE_FIELD , SNAPPING_TOLERANCE, - DataUtil.createIndex(tableName=upwindTable, - fieldName=GEOM_FIELD, - isSpatial=True), - DataUtil.createIndex(tableName=upwindTable, - fieldName=ID_FIELD_BLOCK, - isSpatial=False), - DataUtil.createIndex(tableName=upwindTable, - fieldName=HEIGHT_FIELD, - isSpatial=False), - DataUtil.createIndex(tableName=upwindTable, - fieldName=UPWIND_FACADE_FIELD, - isSpatial=False))) + """).format( + upwindTable, + GEOM_FIELD, + ID_FIELD_BLOCK, + HEIGHT_FIELD, + tempoUpwind, + ID_FIELD_STACKED_BLOCK, + BASE_HEIGHT_FIELD, + UPWIND_FACADE_ANGLE_FIELD, + UPWIND_FACADE_FIELD, + SNAPPING_TOLERANCE, + DataUtil.createIndex( + tableName=upwindTable, fieldName=GEOM_FIELD, isSpatial=True + ), + DataUtil.createIndex( + tableName=upwindTable, + fieldName=ID_FIELD_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=upwindTable, fieldName=HEIGHT_FIELD, isSpatial=False + ), + DataUtil.createIndex( + tableName=upwindTable, + fieldName=UPWIND_FACADE_FIELD, + isSpatial=False, + ), + ) + ) # Upwind facades being not updated are joined to the updated ones - cursor.execute(safe(""" + cursor.execute( + safe(""" {9}; {10}; DROP TABLE IF EXISTS {3}; @@ -524,32 +667,49 @@ def updateUpwindFacadeBase(cursor, upwindTable, prefix = PREFIX_NAME): a.{14}, a.{15}, COALESCE(b.{6}, a.{6}) AS {6} FROM {0} AS a LEFT JOIN {2} AS b ON a.{5} = b.{5} - """).format( upwindTable , ID_FIELD_STACKED_BLOCK, - tempoUpwind , updatedUpwindBaseTable, - GEOM_FIELD , UPWIND_FACADE_FIELD, - BASE_HEIGHT_FIELD , HEIGHT_FIELD, - UPWIND_FACADE_ANGLE_FIELD, DataUtil.createIndex( tableName=upwindTable, - fieldName=UPWIND_FACADE_FIELD, - isSpatial=False), - DataUtil.createIndex(tableName=tempoUpwind, - fieldName=UPWIND_FACADE_FIELD, - isSpatial=False), - ID_FIELD_BLOCK , STACKED_BLOCK_X_MED, - STACKED_BLOCK_WIDTH , DISPLACEMENT_LENGTH_FIELD, - DISPLACEMENT_LENGTH_VORTEX_FIELD)) - + """).format( + upwindTable, + ID_FIELD_STACKED_BLOCK, + tempoUpwind, + updatedUpwindBaseTable, + GEOM_FIELD, + UPWIND_FACADE_FIELD, + BASE_HEIGHT_FIELD, + HEIGHT_FIELD, + UPWIND_FACADE_ANGLE_FIELD, + DataUtil.createIndex( + tableName=upwindTable, + fieldName=UPWIND_FACADE_FIELD, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=tempoUpwind, + fieldName=UPWIND_FACADE_FIELD, + isSpatial=False, + ), + ID_FIELD_BLOCK, + STACKED_BLOCK_X_MED, + STACKED_BLOCK_WIDTH, + DISPLACEMENT_LENGTH_FIELD, + DISPLACEMENT_LENGTH_VORTEX_FIELD, + ) + ) + if not DEBUG: # Drop intermediate tables - cursor.execute("DROP TABLE IF EXISTS {0}".format(",".join([tempoUpwind]))) - + cursor.execute( + "DROP TABLE IF EXISTS {0}".format(",".join([tempoUpwind])) + ) + return updatedUpwindBaseTable -def initDownwindFacades(cursor, obstaclesTable, prefix = PREFIX_NAME): - """ Identify downwind facades, convert them to lines (they are initially + +def initDownwindFacades(cursor, obstaclesTable, prefix=PREFIX_NAME): + """Identify downwind facades, convert them to lines (they are initially included within polygons) and then union those which intersect each other. - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries @@ -559,26 +719,27 @@ def initDownwindFacades(cursor, obstaclesTable, prefix = PREFIX_NAME): 'HEIGHT_FIELD') prefix: String, default PREFIX_NAME Prefix to add to the output table name - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ downwindTable: String Name of the table containing the downwind obstacle facades""" print("Initializes downwind facades") - + # Output base name outputBaseName = "DOWNWIND_FACADES" - + # Name of the output table - downwindTable = DataUtil.prefix(outputBaseName, prefix = prefix) - + downwindTable = DataUtil.prefix(outputBaseName, prefix=prefix) + # Create temporary table names (for tables that will be removed at the end of the process) tempoDownwindSegments = DataUtil.postfix("TEMPO_DOWNWIND_SEGMENTS") tempoDownwindLines = DataUtil.postfix("TEMPO_DOWNWIND_LINES") - + # Identify upwind facade - cursor.execute(safe(""" + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0}({4} BIGINT AUTO_INCREMENT, {1} INTEGER, {2} GEOMETRY, {5} INTEGER) @@ -592,15 +753,19 @@ def initDownwindFacades(cursor, obstaclesTable, prefix = PREFIX_NAME): FROM {3})') WHERE ST_AZIMUTH(ST_STARTPOINT({2}), ST_ENDPOINT({2})) > PI() - """).format( tempoDownwindSegments, - ID_FIELD_STACKED_BLOCK, - GEOM_FIELD, - obstaclesTable, - DOWNWIND_FACADE_FIELD, - HEIGHT_FIELD)) - + """).format( + tempoDownwindSegments, + ID_FIELD_STACKED_BLOCK, + GEOM_FIELD, + obstaclesTable, + DOWNWIND_FACADE_FIELD, + HEIGHT_FIELD, + ) + ) + # Merge the ones that intersect each other and join stacked block properties - cursor.execute(safe(""" + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0}({1} BIGINT AUTO_INCREMENT, {2} GEOMETRY, {4} INTEGER) AS SELECT CAST((row_number() over()) as Integer) AS {1}, ST_NORMALIZE({2}) AS {2}, {4} @@ -615,25 +780,44 @@ def initDownwindFacades(cursor, obstaclesTable, prefix = PREFIX_NAME): b.{14}, b.{15}, b.{16}, b.{17}, b.{18}, b.{19} FROM {0} AS a LEFT JOIN {11} AS b ON a.{4} = b.{4} - """).format(tempoDownwindLines , DOWNWIND_FACADE_FIELD, - GEOM_FIELD , tempoDownwindSegments, - ID_FIELD_STACKED_BLOCK , DataUtil.createIndex(tableName=tempoDownwindLines, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(tableName=obstaclesTable, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - downwindTable, - CAVITY_LENGTH_FIELD , WAKE_LENGTH_FIELD, - HEIGHT_FIELD , obstaclesTable, - STACKED_BLOCK_X_MED , STACKED_BLOCK_WIDTH, - STACKED_BLOCK_UPSTREAMEST_X , SIN_BLOCK_LEFT_AZIMUTH, - COS_BLOCK_LEFT_AZIMUTH , COS_BLOCK_RIGHT_AZIMUTH, - SIN_BLOCK_RIGHT_AZIMUTH , ID_FIELD_BLOCK)) - + """).format( + tempoDownwindLines, + DOWNWIND_FACADE_FIELD, + GEOM_FIELD, + tempoDownwindSegments, + ID_FIELD_STACKED_BLOCK, + DataUtil.createIndex( + tableName=tempoDownwindLines, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=obstaclesTable, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + downwindTable, + CAVITY_LENGTH_FIELD, + WAKE_LENGTH_FIELD, + HEIGHT_FIELD, + obstaclesTable, + STACKED_BLOCK_X_MED, + STACKED_BLOCK_WIDTH, + STACKED_BLOCK_UPSTREAMEST_X, + SIN_BLOCK_LEFT_AZIMUTH, + COS_BLOCK_LEFT_AZIMUTH, + COS_BLOCK_RIGHT_AZIMUTH, + SIN_BLOCK_RIGHT_AZIMUTH, + ID_FIELD_BLOCK, + ) + ) + if not DEBUG: # Drop intermediate tables - cursor.execute(safe("DROP TABLE IF EXISTS {0}").format(",".join([tempoDownwindSegments, - tempoDownwindLines]))) - - return downwindTable \ No newline at end of file + cursor.execute( + safe("DROP TABLE IF EXISTS {0}").format( + ",".join([tempoDownwindSegments, tempoDownwindLines]) + ) + ) + + return downwindTable diff --git a/functions/URock/WindSolver.py b/functions/URock/WindSolver.py index cc21b77..26beb6f 100644 --- a/functions/URock/WindSolver.py +++ b/functions/URock/WindSolver.py @@ -11,45 +11,62 @@ # University of Gothenburg # Department of Earth Sciences """ + import numpy as np import time from .GlobalVariables import MAX_ITERATIONS, THRESHOLD_ITERATIONS, DESCENDING_Y + try: from numba import jit except ImportError: exit("'numba' Python package is missing") import pandas as pd -def solver(x, y, z, dx, dy, dz, u0, v0, w0, buildingCoordinates, cells4Solver, cursor, - maxIterations = MAX_ITERATIONS, thresholdIterations = THRESHOLD_ITERATIONS, - feedback = None): - """ Use the mass-balance solver minimizing the modification of the initial + +def solver( + x, + y, + z, + dx, + dy, + dz, + u0, + v0, + w0, + buildingCoordinates, + cells4Solver, + cursor, + maxIterations=MAX_ITERATIONS, + thresholdIterations=THRESHOLD_ITERATIONS, + feedback=None, +): + """Use the mass-balance solver minimizing the modification of the initial wind speed field. The method used is based on Pardyjak and Brown (2003). - + References: - Pardyjak, Eric R, et Michael Brown. « QUIC-URB v. 1.1: Theory and + Pardyjak, Eric R, et Michael Brown. « QUIC-URB v. 1.1: Theory and User’s Guide ». Los Alamos National Laboratory, Los Alamos, NM, 2003. - - Parameters - _ _ _ _ _ _ _ _ _ _ - + + Parameters + _ _ _ _ _ _ _ _ _ _ + x: 1D array X-axis cell coordinates in a local reference system (starting from 0) y: 1D array - Y-axis cell coordinates in a local reference system (starting from 0) + Y-axis cell coordinates in a local reference system (starting from 0) z: 1D array Z-axis cell coordinates in a local reference system (starting from 0) dx: int Grid spacing along X-axis dy: int - Grid spacing along Y-axis + Grid spacing along Y-axis dz: int Grid spacing along Z-axis u0: 3D array Initialized 3D wind speed value in X direction v0: 3D array - Initialized 3D wind speed value in Y direction + Initialized 3D wind speed value in Y direction w0: 3D array Initialized 3D wind speed value in Z direction buildingCoordinates: 3D array @@ -59,21 +76,21 @@ def solver(x, y, z, dx, dy, dz, u0, v0, w0, buildingCoordinates, cells4Solver, c maxIterations: int, default MAX_ITERATIONS Maximum number of wind solver iterations (solver stops if reached) thresholdIterations: float, default THRESHOLD_ITERATIONS - Threshold for stopping wind solver: when the relative - variation of lambda between 2 iterations goes under this + Threshold for stopping wind solver: when the relative + variation of lambda between 2 iterations goes under this threshold, the wind solver stops feedback: Qgis.core class QgsProcessingFeedback Base class for providing feedback to QGIS from a processing algorithm (if not in standalone mode). - - Returns - _ _ _ _ _ _ _ _ _ _ - + + Returns + _ _ _ _ _ _ _ _ _ _ + u: 3D array Updated 3D wind speed value in X direction v: 3D array - Updated 3D wind speed value in Y direction + Updated 3D wind speed value in Y direction w: 3D array - Updated 3D wind speed value in Z direction""" + Updated 3D wind speed value in Z direction""" print("Start to apply the wind solver") timeStartCalculation = time.time() @@ -82,7 +99,7 @@ def solver(x, y, z, dx, dy, dz, u0, v0, w0, buildingCoordinates, cells4Solver, c nx = x.size ny = y.size nz = z.size - + # Initialize the wind speed in the final wind speed vectors u = u0.copy() v = v0.copy() @@ -91,31 +108,34 @@ def solver(x, y, z, dx, dy, dz, u0, v0, w0, buildingCoordinates, cells4Solver, c # Preallocating lambda and lambda + 1 and set values to 0 on sketch boundaries lambdaN = np.ones([nx, ny, nz]) lambdaN1 = np.ones([nx, ny, nz]) - lambdaN[0, :, :] = 0. - lambdaN[:, 0, :] = 0. - lambdaN[:, :, 0] = 0. - lambdaN[-1, :, :] = 0. - lambdaN[:, -1, :] = 0. - lambdaN[:, :, -1] = 0. - lambdaN1[0, :, :] = 0. - lambdaN1[:, 0, :] = 0. - lambdaN1[:, :, 0] = 0. - lambdaN1[-1, :, :] = 0. - lambdaN1[:, -1, :] = 0. - lambdaN1[:, :, -1] = 0. - - Xi = ((np.cos(np.pi / nx) + (dx / dy) ** 2 * np.cos(np.pi / ny)) / (1 + (dx / dy) ** 2)) ** 2 - - omega = 2. * ((1 - np.sqrt(1 - Xi)) / Xi) + lambdaN[0, :, :] = 0.0 + lambdaN[:, 0, :] = 0.0 + lambdaN[:, :, 0] = 0.0 + lambdaN[-1, :, :] = 0.0 + lambdaN[:, -1, :] = 0.0 + lambdaN[:, :, -1] = 0.0 + lambdaN1[0, :, :] = 0.0 + lambdaN1[:, 0, :] = 0.0 + lambdaN1[:, :, 0] = 0.0 + lambdaN1[-1, :, :] = 0.0 + lambdaN1[:, -1, :] = 0.0 + lambdaN1[:, :, -1] = 0.0 + + Xi = ( + (np.cos(np.pi / nx) + (dx / dy) ** 2 * np.cos(np.pi / ny)) + / (1 + (dx / dy) ** 2) + ) ** 2 + + omega = 2.0 * ((1 - np.sqrt(1 - Xi)) / Xi) if (omega < 1) or (omega > 2): omega = 1.78 - # Coefficients such as defined in Pardyjak et Brown (2003) - alpha1 = 1. - alpha2 = 1. + # Coefficients such as defined in Pardyjak et Brown (2003) + alpha1 = 1.0 + alpha2 = 1.0 eta = alpha1 / alpha2 - A = dx ** 2 / dy ** 2 - B = eta ** 2 * dx ** 2 / dz ** 2 + A = dx**2 / dy**2 + B = eta**2 * dx**2 / dz**2 ######################################################################## # # Used for debug only @@ -126,7 +146,7 @@ def solver(x, y, z, dx, dy, dz, u0, v0, w0, buildingCoordinates, cells4Solver, c # ax.plot(buildingCoordinates[0][i], buildingCoordinates[1][i], marker = "o") ######################################################################## - # Set coefficients according to table 1 (Pardyjak et Brown, 2003) + # Set coefficients according to table 1 (Pardyjak et Brown, 2003) # to modify the Equation near obstacles e = np.ones([nx, ny, nz]) f = np.ones([nx, ny, nz]) @@ -137,68 +157,184 @@ def solver(x, y, z, dx, dy, dz, u0, v0, w0, buildingCoordinates, cells4Solver, c o = np.ones([nx, ny, nz]) p = np.ones([nx, ny, nz]) q = np.ones([nx, ny, nz]) - + # Identify index having wall below AND (front, left, right or behind) - indBelow = pd.MultiIndex.from_tuples(list(zip(*[buildingCoordinates[0], - buildingCoordinates[1], - buildingCoordinates[2] + 1]))) - indAbove = pd.MultiIndex.from_tuples(list(zip(*[buildingCoordinates[0], - buildingCoordinates[1], - buildingCoordinates[2] - 1]))) - indFront = pd.MultiIndex.from_tuples(list(zip(*[buildingCoordinates[0], - buildingCoordinates[1] - 1, - buildingCoordinates[2]]))) - indBehind = pd.MultiIndex.from_tuples(list(zip(*[buildingCoordinates[0], - buildingCoordinates[1] + 1, - buildingCoordinates[2]]))) - indLeft = pd.MultiIndex.from_tuples(list(zip(*[buildingCoordinates[0] + 1, - buildingCoordinates[1], - buildingCoordinates[2]]))) - indRight = pd.MultiIndex.from_tuples(list(zip(*[buildingCoordinates[0] - 1, - buildingCoordinates[1], - buildingCoordinates[2]]))) + indBelow = pd.MultiIndex.from_tuples( + list( + zip( + *[ + buildingCoordinates[0], + buildingCoordinates[1], + buildingCoordinates[2] + 1, + ] + ) + ) + ) + indAbove = pd.MultiIndex.from_tuples( + list( + zip( + *[ + buildingCoordinates[0], + buildingCoordinates[1], + buildingCoordinates[2] - 1, + ] + ) + ) + ) + indFront = pd.MultiIndex.from_tuples( + list( + zip( + *[ + buildingCoordinates[0], + buildingCoordinates[1] - 1, + buildingCoordinates[2], + ] + ) + ) + ) + indBehind = pd.MultiIndex.from_tuples( + list( + zip( + *[ + buildingCoordinates[0], + buildingCoordinates[1] + 1, + buildingCoordinates[2], + ] + ) + ) + ) + indLeft = pd.MultiIndex.from_tuples( + list( + zip( + *[ + buildingCoordinates[0] + 1, + buildingCoordinates[1], + buildingCoordinates[2], + ] + ) + ) + ) + indRight = pd.MultiIndex.from_tuples( + list( + zip( + *[ + buildingCoordinates[0] - 1, + buildingCoordinates[1], + buildingCoordinates[2], + ] + ) + ) + ) indBelowRight = indBelow.intersection(indRight) indBelowLeft = indBelow.intersection(indLeft) indBelowFront = indBelow.intersection(indFront) indBelowBehind = indBelow.intersection(indBehind) - + indE = indBelowRight.union(indRight) indF = indBelowLeft.union(indLeft) indG = indBelowFront.union(indFront) indH = indBelowBehind.union(indBehind) indM = indAbove - indN = indBelow.union(indBelowFront).union(indBelowLeft)\ - .union(indBelowRight).union(indBelowBehind) + indN = ( + indBelow.union(indBelowFront) + .union(indBelowLeft) + .union(indBelowRight) + .union(indBelowBehind) + ) indO = indRight.union(indLeft).union(indBelowLeft).union(indBelowRight) indP = indBehind.union(indFront).union(indBelowBehind).union(indBelowFront) - indQ = indBelow.union(indAbove).union(indBelowFront).union(indBelowLeft)\ - .union(indBelowRight).union(indBelowBehind) - + indQ = ( + indBelow.union(indAbove) + .union(indBelowFront) + .union(indBelowLeft) + .union(indBelowRight) + .union(indBelowBehind) + ) + # Go descending order along y if DESCENDING_Y: - e[indF.get_level_values(0), indF.get_level_values(1), indF.get_level_values(2)] = 0. - f[indE.get_level_values(0), indE.get_level_values(1), indE.get_level_values(2)] = 0. - g[indH.get_level_values(0), indH.get_level_values(1), indH.get_level_values(2)] = 0. - h[indG.get_level_values(0), indG.get_level_values(1), indG.get_level_values(2)] = 0. - m[indM.get_level_values(0), indM.get_level_values(1), indM.get_level_values(2)] = 0. - n[indN.get_level_values(0), indN.get_level_values(1), indN.get_level_values(2)] = 0. - else: - e[indE.get_level_values(0), indE.get_level_values(1), indE.get_level_values(2)] = 0. - f[indF.get_level_values(0), indF.get_level_values(1), indF.get_level_values(2)] = 0. - g[indG.get_level_values(0), indG.get_level_values(1), indG.get_level_values(2)] = 0. - h[indH.get_level_values(0), indH.get_level_values(1), indH.get_level_values(2)] = 0. - m[indM.get_level_values(0), indM.get_level_values(1), indM.get_level_values(2)] = 0. - n[indN.get_level_values(0), indN.get_level_values(1), indN.get_level_values(2)] = 0. - - o[indO.get_level_values(0), indO.get_level_values(1), indO.get_level_values(2)] = 0.5 - p[indP.get_level_values(0), indP.get_level_values(1), indP.get_level_values(2)] = 0.5 - q[indQ.get_level_values(0), indQ.get_level_values(1), indQ.get_level_values(2)] = 0.5 - + e[ + indF.get_level_values(0), + indF.get_level_values(1), + indF.get_level_values(2), + ] = 0.0 + f[ + indE.get_level_values(0), + indE.get_level_values(1), + indE.get_level_values(2), + ] = 0.0 + g[ + indH.get_level_values(0), + indH.get_level_values(1), + indH.get_level_values(2), + ] = 0.0 + h[ + indG.get_level_values(0), + indG.get_level_values(1), + indG.get_level_values(2), + ] = 0.0 + m[ + indM.get_level_values(0), + indM.get_level_values(1), + indM.get_level_values(2), + ] = 0.0 + n[ + indN.get_level_values(0), + indN.get_level_values(1), + indN.get_level_values(2), + ] = 0.0 + else: + e[ + indE.get_level_values(0), + indE.get_level_values(1), + indE.get_level_values(2), + ] = 0.0 + f[ + indF.get_level_values(0), + indF.get_level_values(1), + indF.get_level_values(2), + ] = 0.0 + g[ + indG.get_level_values(0), + indG.get_level_values(1), + indG.get_level_values(2), + ] = 0.0 + h[ + indH.get_level_values(0), + indH.get_level_values(1), + indH.get_level_values(2), + ] = 0.0 + m[ + indM.get_level_values(0), + indM.get_level_values(1), + indM.get_level_values(2), + ] = 0.0 + n[ + indN.get_level_values(0), + indN.get_level_values(1), + indN.get_level_values(2), + ] = 0.0 + + o[ + indO.get_level_values(0), + indO.get_level_values(1), + indO.get_level_values(2), + ] = 0.5 + p[ + indP.get_level_values(0), + indP.get_level_values(1), + indP.get_level_values(2), + ] = 0.5 + q[ + indQ.get_level_values(0), + indQ.get_level_values(1), + indQ.get_level_values(2), + ] = 0.5 + for N in range(maxIterations): - print("Iteration {0} (max {1})".format( N + 1, - maxIterations)) + print("Iteration {0} (max {1})".format(N + 1, maxIterations)) lambdaN = np.copy(lambdaN1) - + # ######################################################################## # # Only used for debug # if DESCENDING_Y: @@ -210,8 +346,8 @@ def solver(x, y, z, dx, dy, dz, u0, v0, w0, buildingCoordinates, cells4Solver, c # e[i, j, k] * lambdaN[i - 1, j, k] + f[i, j, k] * lambdaN1[i + 1, j, k] + A * ( # g[i, j, k] * lambdaN[i, j - 1, k] + h[i, j, k] * lambdaN1[i, j + 1, k]) + B * ( # m[i, j, k] * lambdaN[i, j, k - 1] + n[i, j, k] * lambdaN1[i, j, k + 1]))) / ( - # 2. * (o[i, j, k] + A * p[i, j, k] + B * q[i, j, k]))) + (1 - omega) * lambdaN1[i, j, k] - + # 2. * (o[i, j, k] + A * p[i, j, k] + B * q[i, j, k]))) + (1 - omega) * lambdaN1[i, j, k] + # else: # for i, j, k in cells4Solver: # if i == 115 and j == 214: @@ -223,89 +359,243 @@ def solver(x, y, z, dx, dy, dz, u0, v0, w0, buildingCoordinates, cells4Solver, c # e[i, j, k] * lambdaN[i + 1, j, k] + f[i, j, k] * lambdaN1[i - 1, j, k] + A * ( # g[i, j, k] * lambdaN[i, j + 1, k] + h[i, j, k] * lambdaN1[i, j - 1, k]) + B * ( # m[i, j, k] * lambdaN[i, j, k + 1] + n[i, j, k] * lambdaN1[i, j, k - 1]))) / ( - # 2. * (o[i, j, k] + A * p[i, j, k] + B * q[i, j, k]))) + (1 - omega) * lambdaN1[i, j, k] + # 2. * (o[i, j, k] + A * p[i, j, k] + B * q[i, j, k]))) + (1 - omega) * lambdaN1[i, j, k] # # end of debug # ######################################################################## - - lambdaN1 = calcLambda(cells4Solver, lambdaN, lambdaN1, omega, alpha1, - u0, v0, w0, dx, dy, dz, e, f, g, h, m, n, o, p, q, - DESCENDING_Y, A, B) - - # Calculate how much lambda evolves between 2 consecutive iterations + + lambdaN1 = calcLambda( + cells4Solver, + lambdaN, + lambdaN1, + omega, + alpha1, + u0, + v0, + w0, + dx, + dy, + dz, + e, + f, + g, + h, + m, + n, + o, + p, + q, + DESCENDING_Y, + A, + B, + ) + + # Calculate how much lambda evolves between 2 consecutive iterations eps = np.sum(np.abs(lambdaN1 - lambdaN)) / np.sum(np.abs(lambdaN1)) - + # Check if the condition for ending process is reached if eps < thresholdIterations: break else: - print(" eps = {0} >= {1}".format(np.round(eps,6), - thresholdIterations)) + print( + " eps = {0} >= {1}".format( + np.round(eps, 6), thresholdIterations + ) + ) # Feedback to QGIS every 50 iterations if (N % 50 == 0) & (feedback is not None): textToSend = """Iteration {0} (max {1}) - eps = {2} >= {3} - """.format( N + 1, - maxIterations, - np.round(eps,6), - thresholdIterations) + """.format( + N + 1, maxIterations, np.round(eps, 6), thresholdIterations + ) feedback.setProgressText(textToSend) if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled by user") break - - + # Calculates the final wind speed # go descending order along y if DESCENDING_Y: - u[1:nx, :, :] = u0[1:nx, :, :] + 0.5 * ( - 1. / (alpha1 ** 2)) * (lambdaN1[0:nx-1, :, :] - lambdaN1[1:nx, :, :]) / dx - v[:, 1:ny, :] = v0[:, 1:ny, :] + 0.5 * ( - 1. / (alpha1 ** 2)) * (lambdaN1[:, 0:ny-1, :] - lambdaN1[:, 1:ny, :]) / dy - w[:, :, 1:nz] = w0[:, :, 1:nz] + 0.5 * ( - 1. / (alpha2 ** 2)) * (lambdaN1[:, :, 0:nz - 1] - lambdaN1[:, :, 1:nz]) / dz + u[1:nx, :, :] = ( + u0[1:nx, :, :] + + 0.5 + * (1.0 / (alpha1**2)) + * (lambdaN1[0 : nx - 1, :, :] - lambdaN1[1:nx, :, :]) + / dx + ) + v[:, 1:ny, :] = ( + v0[:, 1:ny, :] + + 0.5 + * (1.0 / (alpha1**2)) + * (lambdaN1[:, 0 : ny - 1, :] - lambdaN1[:, 1:ny, :]) + / dy + ) + w[:, :, 1:nz] = ( + w0[:, :, 1:nz] + + 0.5 + * (1.0 / (alpha2**2)) + * (lambdaN1[:, :, 0 : nz - 1] - lambdaN1[:, :, 1:nz]) + / dz + ) else: - u[1:nx, :, :] = u0[1:nx, :, :] + 0.5 * ( - 1. / (alpha1 ** 2)) * (lambdaN1[1:nx, :, :] - lambdaN1[0:nx - 1, :, :]) / dx - v[:, 1:ny, :] = v0[:, 1:ny, :] + 0.5 * ( - 1. / (alpha1 ** 2)) * (lambdaN1[:, 1:ny, :] - lambdaN1[:, 0:ny - 1, :]) / dy - w[:, :, 1:nz] = w0[:, :, 1:nz] + 0.5 * ( - 1. / (alpha2 ** 2)) * (lambdaN1[:, :, 1:nz] - lambdaN1[:, :, 0:nz - 1]) / dz + u[1:nx, :, :] = ( + u0[1:nx, :, :] + + 0.5 + * (1.0 / (alpha1**2)) + * (lambdaN1[1:nx, :, :] - lambdaN1[0 : nx - 1, :, :]) + / dx + ) + v[:, 1:ny, :] = ( + v0[:, 1:ny, :] + + 0.5 + * (1.0 / (alpha1**2)) + * (lambdaN1[:, 1:ny, :] - lambdaN1[:, 0 : ny - 1, :]) + / dy + ) + w[:, :, 1:nz] = ( + w0[:, :, 1:nz] + + 0.5 + * (1.0 / (alpha2**2)) + * (lambdaN1[:, :, 1:nz] - lambdaN1[:, :, 0 : nz - 1]) + / dz + ) # Reset input and output wind speed to zero for building cells - u[buildingCoordinates[0],buildingCoordinates[1],buildingCoordinates[2]] = 0 - u[buildingCoordinates[0]+1,buildingCoordinates[1],buildingCoordinates[2]]=0 - v[buildingCoordinates[0],buildingCoordinates[1],buildingCoordinates[2]] = 0 - v[buildingCoordinates[0],buildingCoordinates[1]+1,buildingCoordinates[2]]=0 - w[buildingCoordinates[0],buildingCoordinates[1],buildingCoordinates[2]] = 0 - w[buildingCoordinates[0],buildingCoordinates[1],buildingCoordinates[2]+1]=0 - - print("Time spent by the wind speed solver: {0} s".format(time.time()-timeStartCalculation)) - + u[ + buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] + ] = 0 + u[ + buildingCoordinates[0] + 1, + buildingCoordinates[1], + buildingCoordinates[2], + ] = 0 + v[ + buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] + ] = 0 + v[ + buildingCoordinates[0], + buildingCoordinates[1] + 1, + buildingCoordinates[2], + ] = 0 + w[ + buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] + ] = 0 + w[ + buildingCoordinates[0], + buildingCoordinates[1], + buildingCoordinates[2] + 1, + ] = 0 + + print( + "Time spent by the wind speed solver: {0} s".format( + time.time() - timeStartCalculation + ) + ) + return u, v, w + @jit(nopython=True) -def calcLambda(cells4Solver, lambdaN, lambdaN1, omega, alpha1, u0, v0, w0, dx, dy, dz, e, f, g, h, m, n, o, p, q, DESCENDING_Y, A, B): +def calcLambda( + cells4Solver, + lambdaN, + lambdaN1, + omega, + alpha1, + u0, + v0, + w0, + dx, + dy, + dz, + e, + f, + g, + h, + m, + n, + o, + p, + q, + DESCENDING_Y, + A, + B, +): # Go descending order along y if DESCENDING_Y: for k, j, i in np.flip(cells4Solver): - lambdaN1[i, j, k] = omega * ( - ((-1.) * (dx ** 2 * (-2. * alpha1 ** 2) * (((u0[i, j, k] - u0[i + 1, j, k]) / (dx) + ( - v0[i, j, k] - v0[i, j + 1, k]) / (dy) + - (w0[i, j, k] - w0[i, j, k + 1]) / (dz)))) + ( - e[i, j, k] * lambdaN[i - 1, j, k] + f[i, j, k] * lambdaN1[i + 1, j, k] + A * ( - g[i, j, k] * lambdaN[i, j - 1, k] + h[i, j, k] * lambdaN1[i, j + 1, k]) + B * ( - m[i, j, k] * lambdaN[i, j, k - 1] + n[i, j, k] * lambdaN1[i, j, k + 1]))) / ( - 2. * (o[i, j, k] + A * p[i, j, k] + B * q[i, j, k]))) + (1 - omega) * lambdaN1[i, j, k] - + lambdaN1[i, j, k] = ( + omega + * ( + ( + (-1.0) + * ( + dx**2 + * (-2.0 * alpha1**2) + * ( + ( + (u0[i, j, k] - u0[i + 1, j, k]) / (dx) + + (v0[i, j, k] - v0[i, j + 1, k]) / (dy) + + (w0[i, j, k] - w0[i, j, k + 1]) / (dz) + ) + ) + ) + + ( + e[i, j, k] * lambdaN[i - 1, j, k] + + f[i, j, k] * lambdaN1[i + 1, j, k] + + A + * ( + g[i, j, k] * lambdaN[i, j - 1, k] + + h[i, j, k] * lambdaN1[i, j + 1, k] + ) + + B + * ( + m[i, j, k] * lambdaN[i, j, k - 1] + + n[i, j, k] * lambdaN1[i, j, k + 1] + ) + ) + ) + / (2.0 * (o[i, j, k] + A * p[i, j, k] + B * q[i, j, k])) + ) + + (1 - omega) * lambdaN1[i, j, k] + ) + else: for i, j, k in cells4Solver: - lambdaN1[i, j, k] = omega * ( - ((-1.) * (dx ** 2 * (-2. * alpha1 ** 2) * (((u0[i + 1, j, k] - u0[i, j, k]) / (dx) + ( - v0[i, j + 1, k] - v0[i, j, k]) / (dy) + - (w0[i, j, k + 1] - w0[i, j, k]) / (dz)))) + ( - e[i, j, k] * lambdaN[i + 1, j, k] + f[i, j, k] * lambdaN1[i - 1, j, k] + A * ( - g[i, j, k] * lambdaN[i, j + 1, k] + h[i, j, k] * lambdaN1[i, j - 1, k]) + B * ( - m[i, j, k] * lambdaN[i, j, k + 1] + n[i, j, k] * lambdaN1[i, j, k - 1]))) / ( - 2. * (o[i, j, k] + A * p[i, j, k] + B * q[i, j, k]))) + (1 - omega) * lambdaN1[i, j, k] - - return lambdaN1 \ No newline at end of file + lambdaN1[i, j, k] = ( + omega + * ( + ( + (-1.0) + * ( + dx**2 + * (-2.0 * alpha1**2) + * ( + ( + (u0[i + 1, j, k] - u0[i, j, k]) / (dx) + + (v0[i, j + 1, k] - v0[i, j, k]) / (dy) + + (w0[i, j, k + 1] - w0[i, j, k]) / (dz) + ) + ) + ) + + ( + e[i, j, k] * lambdaN[i + 1, j, k] + + f[i, j, k] * lambdaN1[i - 1, j, k] + + A + * ( + g[i, j, k] * lambdaN[i, j + 1, k] + + h[i, j, k] * lambdaN1[i, j - 1, k] + ) + + B + * ( + m[i, j, k] * lambdaN[i, j, k + 1] + + n[i, j, k] * lambdaN1[i, j, k - 1] + ) + ) + ) + / (2.0 * (o[i, j, k] + A * p[i, j, k] + B * q[i, j, k])) + ) + + (1 - omega) * lambdaN1[i, j, k] + ) + + return lambdaN1 diff --git a/functions/URock/WriteMetadataURock.py b/functions/URock/WriteMetadataURock.py index 67c4de0..4abad44 100644 --- a/functions/URock/WriteMetadataURock.py +++ b/functions/URock/WriteMetadataURock.py @@ -1,83 +1,113 @@ from builtins import str + # This file prints out run information used for each specific run from time import strftime from .GlobalVariables import * -def writeRunInfo(folderPath, build_file, heightBuild, - veg_file, attenuationVeg, baseHeightVeg, topHeightVeg, - z_ref, v_ref, windDirection, profileType, - profileFile, - meshSize, dz): + +def writeRunInfo( + folderPath, + build_file, + heightBuild, + veg_file, + attenuationVeg, + baseHeightVeg, + topHeightVeg, + z_ref, + v_ref, + windDirection, + profileType, + profileFile, + meshSize, + dz, +): # with open(folderPath + '/RunInfoSOLWEIG.txt', 'w') as file: #FO# - #FO# - with open(folderPath + '/RunInfoURock.txt', 'w') as file: - file.write('This file provides run settings for the URock run initiated at: ' + strftime("%a, %d %b %Y %H:%M:%S")) - file.write('\n') - file.write('\n') - file.write('Version: ' + 'URock v2023a') - file.write('\n') - file.write('\n') - file.write('SURFACE DATA') - file.write('\n') + # FO# + with open(folderPath + "/RunInfoURock.txt", "w") as file: + file.write( + "This file provides run settings for the URock run initiated at: " + + strftime("%a, %d %b %Y %H:%M:%S") + ) + file.write("\n") + file.write("\n") + file.write("Version: " + "URock v2023a") + file.write("\n") + file.write("\n") + file.write("SURFACE DATA") + file.write("\n") if build_file is not None: - file.write('Building layer: ' + build_file) - file.write('\n') - file.write('Building height (attribute name): ' + heightBuild) - file.write('\n') + file.write("Building layer: " + build_file) + file.write("\n") + file.write("Building height (attribute name): " + heightBuild) + file.write("\n") else: - file.write('No building data used') - file.write('\n') - + file.write("No building data used") + file.write("\n") + if veg_file is not None: - file.write('Vegetation layer: ' + veg_file) - file.write('\n') - file.write('Vegetation top height (attribute name): ' + topHeightVeg) - file.write('\n') + file.write("Vegetation layer: " + veg_file) + file.write("\n") + file.write( + "Vegetation top height (attribute name): " + topHeightVeg + ) + file.write("\n") if baseHeightVeg is not None: - file.write('Vegetation base height (attribute name): ' + baseHeightVeg) + file.write( + "Vegetation base height (attribute name): " + baseHeightVeg + ) else: - file.write('Vegetation base height (fraction of top height): ' + str(DEFAULT_VEG_CROWN_BASE_HEIGHT_FRAC)) - file.write('\n') + file.write( + "Vegetation base height (fraction of top height): " + + str(DEFAULT_VEG_CROWN_BASE_HEIGHT_FRAC) + ) + file.write("\n") if attenuationVeg is not None: - file.write('Attenuation though vegetation (attribute name): ' + attenuationVeg) + file.write( + "Attenuation though vegetation (attribute name): " + + attenuationVeg + ) else: - file.write('Attenuation though vegetation (value): ' + str(DEFAULT_VEG_ATTEN_FACT)) - file.write('\n') + file.write( + "Attenuation though vegetation (value): " + + str(DEFAULT_VEG_ATTEN_FACT) + ) + file.write("\n") else: - file.write('No vegetation data used') - file.write('\n') - - file.write('METEOROLOGICAL DATA') - file.write('\n') - + file.write("No vegetation data used") + file.write("\n") + + file.write("METEOROLOGICAL DATA") + file.write("\n") + if profileFile is not None: - file.write('Wind profile file: ' + profileFile) - file.write('\n') - + file.write("Wind profile file: " + profileFile) + file.write("\n") + else: - file.write('Reference height for wind (m): ' + str(z_ref)) - file.write('\n') - file.write('Reference wind speed at ref height (m/s): ' + str(v_ref)) - file.write('\n') - file.write('Wind direction (° from North): ' + str(windDirection)) - file.write('\n') - file.write('Wind profile type: ' + profileType) - file.write('\n') + file.write("Reference height for wind (m): " + str(z_ref)) + file.write("\n") + file.write( + "Reference wind speed at ref height (m/s): " + str(v_ref) + ) + file.write("\n") + file.write("Wind direction (° from North): " + str(windDirection)) + file.write("\n") + file.write("Wind profile type: " + profileType) + file.write("\n") - file.write('\n') - file.write('MODEL SETTINGS') - file.write('\n') + file.write("\n") + file.write("MODEL SETTINGS") + file.write("\n") - file.write('Horizontal resolution (m): ' + str(meshSize)) - file.write('\n') - file.write('Vertical resolution (m): ' + str(dz)) - file.write('\n') - + file.write("Horizontal resolution (m): " + str(meshSize)) + file.write("\n") + file.write("Vertical resolution (m): " + str(dz)) + file.write("\n") - file.write('\n') + file.write("\n") file.close() # if metfileexist == 1: # file.write('Meteorological file: ' + filePath_metfile) diff --git a/functions/URock/Zones.py b/functions/URock/Zones.py index 3cce2f8..1d28600 100644 --- a/functions/URock/Zones.py +++ b/functions/URock/Zones.py @@ -5,34 +5,35 @@ @author: Jérémy Bernard, University of Gothenburg """ + from . import DataUtil as DataUtil from .DataUtil import safe import pandas as pd from .GlobalVariables import * -def displacementZones2(cursor, upwindWithPropTable, srid, - prefix = PREFIX_NAME): - """ Creates the displacement zone and the displacement vortex zone + +def displacementZones2(cursor, upwindWithPropTable, srid, prefix=PREFIX_NAME): + """Creates the displacement zone and the displacement vortex zone for each of the building upwind facade based on Kaplan et Dinar (1996) - for the equations of the ellipsoid + for the equations of the ellipsoid - Equation 2 when the facade is perpendicular to the wind, - Figure 2 and Table 1 when the facade has an angle Theta with the wind. - Note that the displacement vortex zone is only calculated is the facade is + Note that the displacement vortex zone is only calculated is the facade is nearly perpendicular to wind direction. - + Obstacle length and width in the equations are given in an input table. Note that we strongly recommand to use the 'CalculatesIndicators.zoneProperties' function to calculate effective length and width instead of maximum length and width... References: Kaplan, H., et N. Dinar. « A Lagrangian Dispersion Model for Calculating - Concentration Distribution within a Built-up Domain ». Atmospheric + Concentration Distribution within a Built-up Domain ». Atmospheric Environment 30, nᵒ 24 (1 décembre 1996): 4197‑4207. https://doi.org/10.1016/1352-2310(96)00144-6. - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries @@ -43,30 +44,47 @@ def displacementZones2(cursor, upwindWithPropTable, srid, SRID of the building data (useful for zone calculation) prefix: String, default PREFIX_NAME Prefix to add to the output table name - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ displacementZonesTable: String Name of the table containing the displacement zones displacementVortexZonesTable: String Name of the table containing the displacement vortex zones""" print("Creates displacement zones") - + # Output base names - outputZoneTableNames = {DISPLACEMENT_NAME: DataUtil.prefix("DISPLACEMENT_ZONES", prefix = prefix), - DISPLACEMENT_VORTEX_NAME: DataUtil.prefix("DISPLACEMENT_VORTEX_ZONES", prefix = prefix)} + outputZoneTableNames = { + DISPLACEMENT_NAME: DataUtil.prefix( + "DISPLACEMENT_ZONES", prefix=prefix + ), + DISPLACEMENT_VORTEX_NAME: DataUtil.prefix( + "DISPLACEMENT_VORTEX_ZONES", prefix=prefix + ), + } # Create temporary table names (for tables that will be removed at the end of the process) densifiedLinePoints = DataUtil.postfix("DENSIFIED_LINE_POINTS") - ZonePoints = {DISPLACEMENT_NAME: DISPLACEMENT_NAME + DataUtil.postfix("_ZONE_POINTS"), - DISPLACEMENT_VORTEX_NAME: DISPLACEMENT_VORTEX_NAME + DataUtil.postfix("_ZONE_POINTS")} - ZonePolygons = {DISPLACEMENT_NAME: DISPLACEMENT_NAME + DataUtil.postfix("_ZONE_POLYGONS"), - DISPLACEMENT_VORTEX_NAME: DISPLACEMENT_VORTEX_NAME + DataUtil.postfix("_ZONE_POLYGONS")} - + ZonePoints = { + DISPLACEMENT_NAME: ( + DISPLACEMENT_NAME + DataUtil.postfix("_ZONE_POINTS") + ), + DISPLACEMENT_VORTEX_NAME: ( + DISPLACEMENT_VORTEX_NAME + DataUtil.postfix("_ZONE_POINTS") + ), + } + ZonePolygons = { + DISPLACEMENT_NAME: ( + DISPLACEMENT_NAME + DataUtil.postfix("_ZONE_POLYGONS") + ), + DISPLACEMENT_VORTEX_NAME: ( + DISPLACEMENT_VORTEX_NAME + DataUtil.postfix("_ZONE_POLYGONS") + ), + } + # First densify the upwind facades - cursor.execute( # nosec B608 - f""" + cursor.execute(f""" DROP TABLE IF EXISTS {densifiedLinePoints}; CREATE TABLE {densifiedLinePoints} AS SELECT EXPLOD_ID, @@ -91,14 +109,19 @@ def displacementZones2(cursor, upwindWithPropTable, srid, {STACKED_BLOCK_WIDTH} / 2 AS HALF_WIDTH FROM {upwindWithPropTable})'') GROUP BY {UPWIND_FACADE_FIELD})') - """) # nosec B608 - + """) # nosec B608 # nosec B608 + # Define the names of variables for displacement and displacement vortex zones - variablesNames = pd.DataFrame({"L": [DISPLACEMENT_LENGTH_FIELD, DISPLACEMENT_LENGTH_VORTEX_FIELD]}, - index = [DISPLACEMENT_NAME, DISPLACEMENT_VORTEX_NAME]) - + variablesNames = pd.DataFrame( + {"L": [DISPLACEMENT_LENGTH_FIELD, DISPLACEMENT_LENGTH_VORTEX_FIELD]}, + index=[DISPLACEMENT_NAME, DISPLACEMENT_VORTEX_NAME], + ) + # Create the half ellipse for displacement and displacement vortex zones from the densified upwind facade points - cursor.execute(safe(";").join([safe(""" + cursor.execute( + safe(";").join( + [ + safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0} AS SELECT {1}, {2}, EXPLOD_ID @@ -116,23 +139,35 @@ def displacementZones2(cursor, upwindWithPropTable, srid, FROM {3} WHERE EXPLOD_ID = 1 ORDER BY EXPLOD_ID ASC - """).format(ZonePoints[z] , GEOM_FIELD, - UPWIND_FACADE_FIELD , densifiedLinePoints, - variablesNames.loc[z,"L"]) - for z in variablesNames.index])) - + """).format( + ZonePoints[z], + GEOM_FIELD, + UPWIND_FACADE_FIELD, + densifiedLinePoints, + variablesNames.loc[z, "L"], + ) + for z in variablesNames.index + ] + ) + ) + # Create the zone only if the following conditions are respected - whereCond = {DISPLACEMENT_NAME :" b.{0}*SIN(b.{1})*SIN(b.{1})>{2}"\ - .format(DISPLACEMENT_LENGTH_FIELD, - UPWIND_FACADE_ANGLE_FIELD, - ELLIPSOID_MIN_LENGTH), - DISPLACEMENT_VORTEX_NAME : " b.{0}>RADIANS(90-{1}) AND b.{0}{2}".format( + DISPLACEMENT_LENGTH_FIELD, + UPWIND_FACADE_ANGLE_FIELD, + ELLIPSOID_MIN_LENGTH, + ), + DISPLACEMENT_VORTEX_NAME: ( + " b.{0}>RADIANS(90-{1}) AND b.{0} 0 AND {whereCond[z]}; - """ # nosec B608 - for z in variablesNames.index])) # nosec B608 - + """ for z in variablesNames.index]) # nosec B608 # nosec B608 + ) # nosec B608 + if not DEBUG: # Drop intermediate tables - cursor.execute(safe(""" + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0} - """).format(",".join([densifiedLinePoints] + list(ZonePoints.values())\ - + list(ZonePolygons.values())))) + """).format( + ",".join( + [densifiedLinePoints] + + list(ZonePoints.values()) + + list(ZonePolygons.values()) + ) + ) + ) return list(outputZoneTableNames.values()) -def displacementZones(cursor, upwindTable, zonePropertiesTable, srid, - prefix = PREFIX_NAME): - """ Creates the displacement zone and the displacement vortex zone + +def displacementZones( + cursor, upwindTable, zonePropertiesTable, srid, prefix=PREFIX_NAME +): + """Creates the displacement zone and the displacement vortex zone for each of the building upwind facade based on Kaplan et Dinar (1996) - for the equations of the ellipsoid + for the equations of the ellipsoid - Equation 2 when the facade is perpendicular to the wind, - Figure 2 and Table 1 when the facade has an angle Theta with the wind. - Note that the displacement vortex zone is only calculated is the facade is + Note that the displacement vortex zone is only calculated is the facade is nearly perpendicular to wind direction. - + Obstacle length and width in the equations are given in an input table. Note that we strongly recommand to use the 'CalculatesIndicators.zoneProperties' function to calculate effective length and width instead of maximum length and width... References: Kaplan, H., et N. Dinar. « A Lagrangian Dispersion Model for Calculating - Concentration Distribution within a Built-up Domain ». Atmospheric + Concentration Distribution within a Built-up Domain ». Atmospheric Environment 30, nᵒ 24 (1 décembre 1996): 4197‑4207. https://doi.org/10.1016/1352-2310(96)00144-6. - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries @@ -207,40 +251,50 @@ def displacementZones(cursor, upwindTable, zonePropertiesTable, srid, SRID of the building data (useful for zone calculation) prefix: String, default PREFIX_NAME Prefix to add to the output table name - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ displacementZonesTable: String Name of the table containing the displacement zones displacementVortexZonesTable: String Name of the table containing the displacement vortex zones""" print("Creates displacement zones") - + # Output base name outputBaseDispName = "DISPLACEMENT_ZONES" outputBaseDispVortexName = "DISPLACEMENT_VORTEX_ZONES" - + # Name of the output table - displacementZonesTable = DataUtil.prefix(outputBaseDispName, - prefix = prefix) - displacementVortexZonesTable = DataUtil.prefix(outputBaseDispVortexName, - prefix = prefix) - + displacementZonesTable = DataUtil.prefix(outputBaseDispName, prefix=prefix) + displacementVortexZonesTable = DataUtil.prefix( + outputBaseDispVortexName, prefix=prefix + ) + # Separate the query into two almost similar queries having only # different case when conditions and ellipse size - partOfQueryThatDiffer = pd.DataFrame({ - "where": [" b.{0}*SIN(a.{1})*SIN(a.{1})>{2}".format(DISPLACEMENT_LENGTH_FIELD, - UPWIND_FACADE_ANGLE_FIELD, - ELLIPSOID_MIN_LENGTH), - " a.{0}>RADIANS(90-{1}) AND a.{0}{2}".format( + DISPLACEMENT_LENGTH_FIELD, + UPWIND_FACADE_ANGLE_FIELD, + ELLIPSOID_MIN_LENGTH, + ), + " a.{0}>RADIANS(90-{1}) AND a.{0} 0; - """ # nosec B608 - for z in variablesNames.index])) - + """ for z in variablesNames.index])) # nosec B608 + if not DEBUG: # Drop intermediate tables - cursor.execute(safe(""" + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0} - """).format(",".join([densifiedLinePoints] + list(ZonePoints.values())\ - + list(ZonePolygons.values())))) + """).format( + ",".join( + [densifiedLinePoints] + + list(ZonePoints.values()) + + list(ZonePolygons.values()) + ) + ) + ) return outputZoneTableNames -def streetCanyonZones(cursor, cavityZonesTable, zonePropertiesTable, upwindTable, - downwindTable, srid, prefix = PREFIX_NAME): - """ Creates the street canyon zones for each of the stacked building + +def streetCanyonZones( + cursor, + cavityZonesTable, + zonePropertiesTable, + upwindTable, + downwindTable, + srid, + prefix=PREFIX_NAME, +): + """Creates the street canyon zones for each of the stacked building based on Nelson et al. (2008) Figure 8b. The method is slightly different since we use the cavity zone instead of the Lr buffer. References: - Nelson, Matthew, Bhagirath Addepalli, Fawn Hornsby, Akshay Gowardhan, - Eric Pardyjak, et Michael Brown. « 5.2 Improvements to a Fast-Response + Nelson, Matthew, Bhagirath Addepalli, Fawn Hornsby, Akshay Gowardhan, + Eric Pardyjak, et Michael Brown. « 5.2 Improvements to a Fast-Response Urban Wind Model », 2008. - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries @@ -481,14 +579,14 @@ def streetCanyonZones(cursor, cavityZonesTable, zonePropertiesTable, upwindTable Name of the table containing upwind segment geometries (and also the ID of each stacked obstacle) downwindTable: String - Name of the table containing downwind line geometries and ID + Name of the table containing downwind line geometries and ID srid: int SRID of the building data (useful for zone calculation) prefix: String, default PREFIX_NAME Prefix to add to the output table name - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ streetCanyonZoneTable: String Name of the table containing the street canyon zones""" @@ -496,14 +594,14 @@ def streetCanyonZones(cursor, cavityZonesTable, zonePropertiesTable, upwindTable # Output base name outputBaseName = "STREETCANYON_ZONE" - + # Name of the output tables - streetCanyonZoneTable = DataUtil.prefix(outputBaseName, prefix = prefix) - + streetCanyonZoneTable = DataUtil.prefix(outputBaseName, prefix=prefix) + # Create temporary table names (for tables that will be removed at the end of the IProcess) intersectTable = DataUtil.postfix("intersect_table") canyonExtendTable = DataUtil.postfix("canyon_extend_table") - + # Identify pieces of upwind facades intersected by cavity zones (only when street canyon angle < 45°) cursor.execute(f""" {DataUtil.createIndex(tableName=upwindTable, @@ -536,9 +634,8 @@ def streetCanyonZones(cursor, cavityZonesTable, zonePropertiesTable, upwindTable AND a.{UPWIND_FACADE_ANGLE_FIELD} >= RADIANS({STREET_CANYON_ANGLE_THRESH}) AND a.{UPWIND_FACADE_ANGLE_FIELD} <= RADIANS(180-{STREET_CANYON_ANGLE_THRESH}) AND a.{ID_FIELD_BLOCK} != b.{ID_FIELD_BLOCK} - """ # nosec B608 - ) - + """) # nosec B608 + # Identify street canyon extend canyonExtendQuery = safe(""" {14}; @@ -564,22 +661,36 @@ def streetCanyonZones(cursor, cavityZonesTable, zonePropertiesTable, upwindTable a.{17} FROM {0} AS a LEFT JOIN {2} AS b ON a.{1} = b.{10} WHERE NOT ST_ISEMPTY(a.{4}) - """).format( intersectTable , ID_UPSTREAM_STACKED_BLOCK, - zonePropertiesTable , canyonExtendTable, - GEOM_FIELD , CAVITY_LENGTH_FIELD, - HEIGHT_FIELD , DOWNSTREAM_HEIGHT_FIELD, - UPSTREAM_HEIGHT_FIELD , ID_DOWNSTREAM_STACKED_BLOCK, - ID_FIELD_STACKED_BLOCK , UPWIND_FACADE_ANGLE_FIELD, - BASE_HEIGHT_FIELD , UPWIND_FACADE_FIELD, - DataUtil.createIndex(tableName=intersectTable, - fieldName=ID_UPSTREAM_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(tableName=zonePropertiesTable, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - srid , DOWNWIND_FACADE_FIELD) + """).format( + intersectTable, + ID_UPSTREAM_STACKED_BLOCK, + zonePropertiesTable, + canyonExtendTable, + GEOM_FIELD, + CAVITY_LENGTH_FIELD, + HEIGHT_FIELD, + DOWNSTREAM_HEIGHT_FIELD, + UPSTREAM_HEIGHT_FIELD, + ID_DOWNSTREAM_STACKED_BLOCK, + ID_FIELD_STACKED_BLOCK, + UPWIND_FACADE_ANGLE_FIELD, + BASE_HEIGHT_FIELD, + UPWIND_FACADE_FIELD, + DataUtil.createIndex( + tableName=intersectTable, + fieldName=ID_UPSTREAM_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=zonePropertiesTable, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + srid, + DOWNWIND_FACADE_FIELD, + ) cursor.execute(canyonExtendQuery) - + # Creates street canyon zones streetCanyonQuery = safe(""" {15}; @@ -616,29 +727,44 @@ def streetCanyonZones(cursor, cavityZonesTable, zonePropertiesTable, upwindTable a.{9} FROM {0} AS a LEFT JOIN {7} AS b ON a.{9}=b.{9})') WHERE EXPLOD_ID = 1 - """).format( canyonExtendTable , ID_UPSTREAM_STACKED_BLOCK, - streetCanyonZoneTable , GEOM_FIELD, - DOWNSTREAM_HEIGHT_FIELD , UPSTREAM_HEIGHT_FIELD, - SNAPPING_TOLERANCE , downwindTable, - ID_DOWNSTREAM_STACKED_BLOCK , DOWNWIND_FACADE_FIELD, - MESH_SIZE , UPWIND_FACADE_ANGLE_FIELD, - BASE_HEIGHT_FIELD , ID_FIELD_CANYON, - UPWIND_FACADE_FIELD , DataUtil.createIndex( tableName=canyonExtendTable, - fieldName=ID_UPSTREAM_STACKED_BLOCK, - isSpatial=False), - srid) + """).format( + canyonExtendTable, + ID_UPSTREAM_STACKED_BLOCK, + streetCanyonZoneTable, + GEOM_FIELD, + DOWNSTREAM_HEIGHT_FIELD, + UPSTREAM_HEIGHT_FIELD, + SNAPPING_TOLERANCE, + downwindTable, + ID_DOWNSTREAM_STACKED_BLOCK, + DOWNWIND_FACADE_FIELD, + MESH_SIZE, + UPWIND_FACADE_ANGLE_FIELD, + BASE_HEIGHT_FIELD, + ID_FIELD_CANYON, + UPWIND_FACADE_FIELD, + DataUtil.createIndex( + tableName=canyonExtendTable, + fieldName=ID_UPSTREAM_STACKED_BLOCK, + isSpatial=False, + ), + srid, + ) cursor.execute(streetCanyonQuery) - + if not DEBUG: # Drop intermediate tables - cursor.execute(safe("DROP TABLE IF EXISTS {0}").format(",".join([intersectTable, - canyonExtendTable]))) - + cursor.execute( + safe("DROP TABLE IF EXISTS {0}").format( + ",".join([intersectTable, canyonExtendTable]) + ) + ) + return streetCanyonZoneTable -def rooftopZones(cursor, upwindTable, zonePropertiesTable, - prefix = PREFIX_NAME): - """ Creates the rooftop zones for each of the upwind facade: + +def rooftopZones(cursor, upwindTable, zonePropertiesTable, prefix=PREFIX_NAME): + """Creates the rooftop zones for each of the upwind facade: - recirculation zone if the angle between the wind and the facade is included within the range [90-PERPENDICULAR_THRESHOLD_ANGLE, 90+PERPENDICULAR_THRESHOLD_ANGLE]. See Pol et al. (2006) for more details @@ -646,21 +772,21 @@ def rooftopZones(cursor, upwindTable, zonePropertiesTable, within the range [90-CORNER_THRESHOLD_ANGLE[1], 90-CORNER_THRESHOLD_ANGLE[0]] or [90+CORNER_THRESHOLD_ANGLE[0], 90+CORNER_THRESHOLD_ANGLE[1]]. See Bagal et al. (2004) for more details - + Obstacle length and width in the equations are given in an input table. Note that we strongly recommand to use the 'CalculatesIndicators.zoneProperties' function to calculate effective length and width instead of maximum length and width... References: - Pol, SU, NL Bagal, B Singh, MJ Brown, et ER Pardyjak. « IMPLEMENTATION - OF A ROOFTOP RECIRCULATION PARAMETERIZATION INTO THE QUIC FAST + Pol, SU, NL Bagal, B Singh, MJ Brown, et ER Pardyjak. « IMPLEMENTATION + OF A ROOFTOP RECIRCULATION PARAMETERIZATION INTO THE QUIC FAST RESPONSE URBAN WIND MODEL », 2006. Bagal, NL, B Singh, ER Pardyjak, et MJ Brown. « Implementation of rooftop recirculation parameterization into the QUIC fast response urban wind model ». In Proc. 5th AMS Urban Environ. Symp. Conf, 2004. - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries @@ -671,41 +797,48 @@ def rooftopZones(cursor, upwindTable, zonePropertiesTable, Name of the table stacked obstacle geometries and zone properties prefix: String, default PREFIX_NAME Prefix to add to the output table name - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ rooftopPerpendicularZoneTable: String Name of the table containing the rooftop perpendicular zones rooftopCornerZoneTable: String Name of the table containing the rooftop corner zones""" print("Creates rooftop zones (perpendicular and corner)") - + # Output base name outputBaseNameroofPerp = "ROOFTOP_PERP_ZONES" outputBaseNameroofCorner = "ROOFTOP_CORNER_ZONES" - + # Name of the output tables - roofPerpZonesTable = DataUtil.prefix(outputBaseNameroofPerp, - prefix = prefix) - RoofCornerZonesTable = DataUtil.prefix(outputBaseNameroofCorner, - prefix = prefix) - + roofPerpZonesTable = DataUtil.prefix(outputBaseNameroofPerp, prefix=prefix) + RoofCornerZonesTable = DataUtil.prefix( + outputBaseNameroofCorner, prefix=prefix + ) + # Create temporary table names (for tables that will be removed at the end of the IProcess) temporaryRooftopPerp = DataUtil.postfix("temporary_rooftop_perp") temporaryRooftopCorner = DataUtil.postfix("temporary_rooftop_corner") - + # Creates a dictionary of table names in order to simplify the final query - dicTableNames = pd.DataFrame({"final": [roofPerpZonesTable, RoofCornerZonesTable], - "temporary": [temporaryRooftopPerp, temporaryRooftopCorner]}, - index = ["perp", "corner"]) - + dicTableNames = pd.DataFrame( + { + "final": [roofPerpZonesTable, RoofCornerZonesTable], + "temporary": [temporaryRooftopPerp, temporaryRooftopCorner], + }, + index=["perp", "corner"], + ) + # Piece of query to get Lcx and Lcy (based on equations 3, 4 and 5 from # Bagal et al. 2004 - note that in 4 and 5, we assumed that X and Y had been # reverted) - pieceOfQueryLcCorner = "2*ST_LENGTH({0})*TAN(2.94*EXP(0.0297*ABS(PI()/2-{1})))".format(GEOM_FIELD, - UPWIND_FACADE_ANGLE_FIELD) - + pieceOfQueryLcCorner = ( + "2*ST_LENGTH({0})*TAN(2.94*EXP(0.0297*ABS(PI()/2-{1})))".format( + GEOM_FIELD, UPWIND_FACADE_ANGLE_FIELD + ) + ) + # Queries to create temporary rooftop zones (perpendicular and corner) queryTempoRooftop = safe(""" DROP TABLE IF EXISTS {0}, {12}; @@ -754,31 +887,51 @@ def rooftopZones(cursor, upwindTable, zonePropertiesTable, ST_STARTPOINT(a.{3}))) AS {3} FROM {7} AS a LEFT JOIN {8} AS b ON a.{1} = b.{1} WHERE a.{5} > RADIANS(90-{13}) AND a.{5} < RADIANS(90+{13}) - """).format( temporaryRooftopCorner , ID_FIELD_STACKED_BLOCK, - UPWIND_FACADE_FIELD , GEOM_FIELD, - HEIGHT_FIELD , UPWIND_FACADE_ANGLE_FIELD, - pieceOfQueryLcCorner , upwindTable, - zonePropertiesTable , CORNER_THRESHOLD_ANGLE[1], - CORNER_THRESHOLD_ANGLE[0] , ROOFTOP_PERP_LENGTH, - temporaryRooftopPerp , PERPENDICULAR_THRESHOLD_ANGLE, - ROOFTOP_CORNER_LENGTH , ROOFTOP_CORNER_FACADE_LENGTH, - DataUtil.createIndex(tableName=upwindTable, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(tableName=zonePropertiesTable, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False)) + """).format( + temporaryRooftopCorner, + ID_FIELD_STACKED_BLOCK, + UPWIND_FACADE_FIELD, + GEOM_FIELD, + HEIGHT_FIELD, + UPWIND_FACADE_ANGLE_FIELD, + pieceOfQueryLcCorner, + upwindTable, + zonePropertiesTable, + CORNER_THRESHOLD_ANGLE[1], + CORNER_THRESHOLD_ANGLE[0], + ROOFTOP_PERP_LENGTH, + temporaryRooftopPerp, + PERPENDICULAR_THRESHOLD_ANGLE, + ROOFTOP_CORNER_LENGTH, + ROOFTOP_CORNER_FACADE_LENGTH, + DataUtil.createIndex( + tableName=upwindTable, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=zonePropertiesTable, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + ) cursor.execute(queryTempoRooftop) - + # Queries to limit the rooftop zones to the rooftop of the stacked block... - extraFieldToKeep = {"perp": "b.{0}, b.{1},".format(ROOFTOP_PERP_LENGTH, - ROOFTOP_PERP_HEIGHT), - "corner": """a.{0}, a.{1}, a.{2}, b.{3}, - a.GEOM_CORNER_POINT,""".format(ROOFTOP_CORNER_LENGTH, - ROOFTOP_CORNER_FACADE_LENGTH, - UPWIND_FACADE_ANGLE_FIELD, - ROOFTOP_WIND_FACTOR)} - queryCutRooftop = [safe(""" + extraFieldToKeep = { + "perp": "b.{0}, b.{1},".format( + ROOFTOP_PERP_LENGTH, ROOFTOP_PERP_HEIGHT + ), + "corner": """a.{0}, a.{1}, a.{2}, b.{3}, + a.GEOM_CORNER_POINT,""".format( + ROOFTOP_CORNER_LENGTH, + ROOFTOP_CORNER_FACADE_LENGTH, + UPWIND_FACADE_ANGLE_FIELD, + ROOFTOP_WIND_FACTOR, + ), + } + queryCutRooftop = [ + safe(""" {8}; {9}; {10}; @@ -792,46 +945,66 @@ def rooftopZones(cursor, upwindTable, zonePropertiesTable, ST_INTERSECTION(a.{3}, b.{3}) AS {3} FROM {5} AS a LEFT JOIN {6} AS b ON a.{1} = b.{1} WHERE a.{3} && b.{3} AND ST_INTERSECTS(a.{3}, b.{3}) - """).format( dicTableNames.loc[typeZone, "final"] , ID_FIELD_STACKED_BLOCK, - UPWIND_FACADE_FIELD , GEOM_FIELD, - HEIGHT_FIELD , dicTableNames.loc[typeZone, "temporary"], - zonePropertiesTable , extraFieldToKeep[typeZone], - DataUtil.createIndex(tableName=dicTableNames.loc[typeZone, "temporary"], - fieldName=GEOM_FIELD, - isSpatial=True), - DataUtil.createIndex(tableName=zonePropertiesTable, - fieldName=GEOM_FIELD, - isSpatial=True), - DataUtil.createIndex(tableName=dicTableNames.loc[typeZone, "temporary"], - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(tableName=zonePropertiesTable, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - SNAPPING_TOLERANCE) - for typeZone in dicTableNames.index] + """).format( + dicTableNames.loc[typeZone, "final"], + ID_FIELD_STACKED_BLOCK, + UPWIND_FACADE_FIELD, + GEOM_FIELD, + HEIGHT_FIELD, + dicTableNames.loc[typeZone, "temporary"], + zonePropertiesTable, + extraFieldToKeep[typeZone], + DataUtil.createIndex( + tableName=dicTableNames.loc[typeZone, "temporary"], + fieldName=GEOM_FIELD, + isSpatial=True, + ), + DataUtil.createIndex( + tableName=zonePropertiesTable, + fieldName=GEOM_FIELD, + isSpatial=True, + ), + DataUtil.createIndex( + tableName=dicTableNames.loc[typeZone, "temporary"], + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=zonePropertiesTable, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + SNAPPING_TOLERANCE, + ) + for typeZone in dicTableNames.index + ] cursor.execute(safe(";").join(safe(queryCutRooftop))) - + if not DEBUG: # Drop intermediate tables - cursor.execute(safe("DROP TABLE IF EXISTS {0}").format(",".join(dicTableNames["temporary"].values))) - + cursor.execute( + safe("DROP TABLE IF EXISTS {0}").format( + ",".join(dicTableNames["temporary"].values) + ) + ) + return roofPerpZonesTable, RoofCornerZonesTable -def vegetationZones(cursor, vegetationTable, wakeZonesTable, - prefix = PREFIX_NAME): - """ Identify vegetation zones which are in "built up" areas and those +def vegetationZones( + cursor, vegetationTable, wakeZonesTable, prefix=PREFIX_NAME +): + """Identify vegetation zones which are in "built up" areas and those being in "open areas". Vegetation is considered in a built up area when it intersects with build wake zone. References: - Nelson et al., Evaluation of an urban vegetative canopy scheme + Nelson et al., Evaluation of an urban vegetative canopy scheme and impact on plume dispersion. 2009 - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries @@ -841,33 +1014,36 @@ def vegetationZones(cursor, vegetationTable, wakeZonesTable, Name of the table containing the wake zones prefix: String, default PREFIX_NAME Prefix to add to the output table name - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ vegetationBuiltZoneTable: String - Name of the table containing the vegetation zone located in + Name of the table containing the vegetation zone located in built-up areas vegetationOpenZoneTable: String - Name of the table containing the vegetation zone located in + Name of the table containing the vegetation zone located in open areas""" print("Creates built-up and open vegetation zones") - + # Output base name outputBaseNameOpen = "OPEN_VEGETATION_ZONES" outputBaseNameBuilt = "BUILTUP_VEGETATION_ZONES" - + # Name of the output tables - vegetationOpenZoneTable = DataUtil.prefix(outputBaseNameOpen, - prefix = prefix) - vegetationBuiltZoneTable = DataUtil.prefix(outputBaseNameBuilt, - prefix = prefix) - + vegetationOpenZoneTable = DataUtil.prefix( + outputBaseNameOpen, prefix=prefix + ) + vegetationBuiltZoneTable = DataUtil.prefix( + outputBaseNameBuilt, prefix=prefix + ) + # Create temporary table names (for tables that will be removed at the end of the IProcess) temporary_built_vegetation = DataUtil.postfix("temporary_built_vegetation") - + # Identify vegetation zones being in building wake zones - cursor.execute(safe(""" + cursor.execute( + safe(""" {10}; {11}; DROP TABLE IF EXISTS {7}; @@ -896,24 +1072,35 @@ def vegetationZones(cursor, vegetationTable, wakeZonesTable, FROM {7} WHERE ST_ISEMPTY({1}) IS FALSE GROUP BY {6})') - """).format( vegetationTable , GEOM_FIELD, - wakeZonesTable , VEGETATION_CROWN_BASE_HEIGHT, - VEGETATION_CROWN_TOP_HEIGHT , VEGETATION_ATTENUATION_FACTOR, - ID_VEGETATION , temporary_built_vegetation, - vegetationBuiltZoneTable , ID_ZONE_VEGETATION, - DataUtil.createIndex(tableName=vegetationTable, - fieldName=GEOM_FIELD, - isSpatial=True), - DataUtil.createIndex(tableName=wakeZonesTable, - fieldName=GEOM_FIELD, - isSpatial=True), - DataUtil.createIndex(tableName=temporary_built_vegetation, - fieldName=ID_VEGETATION, - isSpatial=False), - GEOMETRY_SIMPLIFICATION_DISTANCE)) - + """).format( + vegetationTable, + GEOM_FIELD, + wakeZonesTable, + VEGETATION_CROWN_BASE_HEIGHT, + VEGETATION_CROWN_TOP_HEIGHT, + VEGETATION_ATTENUATION_FACTOR, + ID_VEGETATION, + temporary_built_vegetation, + vegetationBuiltZoneTable, + ID_ZONE_VEGETATION, + DataUtil.createIndex( + tableName=vegetationTable, fieldName=GEOM_FIELD, isSpatial=True + ), + DataUtil.createIndex( + tableName=wakeZonesTable, fieldName=GEOM_FIELD, isSpatial=True + ), + DataUtil.createIndex( + tableName=temporary_built_vegetation, + fieldName=ID_VEGETATION, + isSpatial=False, + ), + GEOMETRY_SIMPLIFICATION_DISTANCE, + ) + ) + # Identify vegetation zones being in open areas - cursor.execute(safe(""" + cursor.execute( + safe(""" {9}; {10}; DROP TABLE IF EXISTS {7}; @@ -940,37 +1127,56 @@ def vegetationZones(cursor, vegetationTable, wakeZonesTable, a.{6} FROM {0} AS a LEFT JOIN {2} AS b ON a.{6} = b.{6} WHERE b.{6} IS NULL - """).format( vegetationTable , GEOM_FIELD, - temporary_built_vegetation , VEGETATION_CROWN_BASE_HEIGHT, - VEGETATION_CROWN_TOP_HEIGHT , VEGETATION_ATTENUATION_FACTOR, - ID_VEGETATION , vegetationOpenZoneTable, - ID_ZONE_VEGETATION , DataUtil.createIndex( tableName=vegetationTable, - fieldName=ID_VEGETATION, - isSpatial=False), - DataUtil.createIndex(tableName=temporary_built_vegetation, - fieldName=ID_VEGETATION, - isSpatial=False))) - + """).format( + vegetationTable, + GEOM_FIELD, + temporary_built_vegetation, + VEGETATION_CROWN_BASE_HEIGHT, + VEGETATION_CROWN_TOP_HEIGHT, + VEGETATION_ATTENUATION_FACTOR, + ID_VEGETATION, + vegetationOpenZoneTable, + ID_ZONE_VEGETATION, + DataUtil.createIndex( + tableName=vegetationTable, + fieldName=ID_VEGETATION, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=temporary_built_vegetation, + fieldName=ID_VEGETATION, + isSpatial=False, + ), + ) + ) + if not DEBUG: # Drop intermediate tables - cursor.execute(safe("DROP TABLE IF EXISTS {0}").format(",".join([temporary_built_vegetation]))) - + cursor.execute( + safe("DROP TABLE IF EXISTS {0}").format( + ",".join([temporary_built_vegetation]) + ) + ) + return vegetationBuiltZoneTable, vegetationOpenZoneTable -def identifyImpactingStackedBlocks(cursor, - dicOfBuildRockleZoneTable, - dicOfVegRockleZoneTable, - impactedZone, - stackedBlocksTable, - vegetationTable, - crossWindExtend, - prefix): - """ Identify all stacked blocks potentially impacting a given impacted zone. + +def identifyImpactingStackedBlocks( + cursor, + dicOfBuildRockleZoneTable, + dicOfVegRockleZoneTable, + impactedZone, + stackedBlocksTable, + vegetationTable, + crossWindExtend, + prefix, +): + """Identify all stacked blocks potentially impacting a given impacted zone. This is done in 3 steps STEP 1. Whenever any Röckle zone of a stacked block SB1 intersects the impacted zone, all the stacked blocks belonging to the same block as SB1 are identified. - STEP 2. We also identify the blocks which does not intersect the impacted zone - but which are at the same upstream or downstream position as the blocks + STEP 2. We also identify the blocks which does not intersect the impacted zone + but which are at the same upstream or downstream position as the blocks impacting the impacted zone. In the cross-wind direction, we stop searching for blocks when we get further than the most extreme cross-wind positions of both the previous identified blocks and the impacted zone @@ -978,16 +1184,16 @@ def identifyImpactingStackedBlocks(cursor, STEP 3. For each block identified in 1 and 2, we select the stacked blocks and the corresponding Röckle zones. - Parameters - _ _ _ _ _ _ _ _ _ _ + Parameters + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries dicOfBuildRockleZoneTable: Dictionary - Dictionary containing as key the building Rockle + Dictionary containing as key the building Rockle zone name and as value the corresponding table name dicOfVegRockleZoneTable: Dictionary - Dictionary containing as key the vegetation Rockle + Dictionary containing as key the vegetation Rockle zone name and as value the corresponding table name impactedZone: String Name of the table where is saved the study area table @@ -1001,88 +1207,116 @@ def identifyImpactingStackedBlocks(cursor, rotated obstacles and the impactedZone in the cross-wind direction prefix: String, default PREFIX_NAME Prefix to add to the output table name - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ dicOfSelectedBuiltZones: Dictionary of Rockle zone tables - Dictionary containing as key the building or vegetation Rockle + Dictionary containing as key the building or vegetation Rockle zone name and as value the corresponding table name stackedBlocksTableSelection: String Name of the table used to save selected stacked blocks - + """ - print("Identify the buildings concerned by the impacted zone chosen by the user") + print( + "Identify the buildings concerned by the impacted zone chosen by the user" + ) # Name of the output tables - dicOfSelectedBuildZones = {t: dicOfBuildRockleZoneTable[t] + SELECTED_SUFFIX \ - for t in dicOfBuildRockleZoneTable.keys()} - dicOfSelectedVegZones = {t: dicOfVegRockleZoneTable[t] + SELECTED_SUFFIX \ - for t in dicOfVegRockleZoneTable.keys()} - outputStackedBlocks = DataUtil.prefix("IMPACTING_STACKED_BLOCKS", - prefix = prefix) - outputVegetation = DataUtil.prefix("IMPACTING_VEGETATION", - prefix = prefix) - + dicOfSelectedBuildZones = { + t: dicOfBuildRockleZoneTable[t] + SELECTED_SUFFIX + for t in dicOfBuildRockleZoneTable.keys() + } + dicOfSelectedVegZones = { + t: dicOfVegRockleZoneTable[t] + SELECTED_SUFFIX + for t in dicOfVegRockleZoneTable.keys() + } + outputStackedBlocks = DataUtil.prefix( + "IMPACTING_STACKED_BLOCKS", prefix=prefix + ) + outputVegetation = DataUtil.prefix("IMPACTING_VEGETATION", prefix=prefix) + # Create temporary table names (for tables that will be removed at the end of the IProcess) tabAllBuildZones = DataUtil.postfix("tab_all_build_zones") tabTempStack = DataUtil.postfix("tab_temp_stack") tabTempBlock = DataUtil.postfix("tab_temp_blocks") tabCrossExtBox = DataUtil.postfix("tab_cross_extend_box") tabTempBlock2 = DataUtil.postfix("tab_temp_blocks2") - + # ------------------------------------------------------------------------ # 1. MAKE THE CALCULATION FOR THE BUILDINGS ------------------------------ # ------------------------------------------------------------------------ # Gather all building Röckle zones in one - gatherBuQuery = [safe("""SELECT {0}, {1} FROM {2}""").format(ID_FIELD_STACKED_BLOCK, - GEOM_FIELD, - dicOfBuildRockleZoneTable[t]) - for t in dicOfBuildRockleZoneTable.keys()] + gatherBuQuery = [ + safe("""SELECT {0}, {1} FROM {2}""").format( + ID_FIELD_STACKED_BLOCK, GEOM_FIELD, dicOfBuildRockleZoneTable[t] + ) + for t in dicOfBuildRockleZoneTable.keys() + ] cursor.execute(safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0} AS {1} """).format(tabAllBuildZones, " UNION ALL ".join(gatherBuQuery))) - + # Identify which stacked blocks intersect the Röckle zones - cursor.execute(safe(""" + cursor.execute( + safe(""" {0};{1}; DROP TABLE IF EXISTS {2}; CREATE TABLE {2} AS SELECT DISTINCT(a.{3}) AS {3} FROM {4} AS a, {5} AS b WHERE a.{6} && b.{6} AND ST_INTERSECTS(a.{6}, b.{6}) - """).format(DataUtil.createIndex(tableName=tabAllBuildZones, - fieldName=GEOM_FIELD, - isSpatial=True), - DataUtil.createIndex(tableName=impactedZone, - fieldName=GEOM_FIELD, - isSpatial=True), - tabTempStack , ID_FIELD_STACKED_BLOCK, - tabAllBuildZones , impactedZone, - GEOM_FIELD)) - + """).format( + DataUtil.createIndex( + tableName=tabAllBuildZones, + fieldName=GEOM_FIELD, + isSpatial=True, + ), + DataUtil.createIndex( + tableName=impactedZone, fieldName=GEOM_FIELD, isSpatial=True + ), + tabTempStack, + ID_FIELD_STACKED_BLOCK, + tabAllBuildZones, + impactedZone, + GEOM_FIELD, + ) + ) + # Identify to which blocks belong the identified stacked blocks - cursor.execute(safe(""" + cursor.execute( + safe(""" {0};{1}; DROP TABLE IF EXISTS {2}; CREATE TABLE {2} AS SELECT DISTINCT(b.{3}) AS {3}, b.{7} FROM {4} AS a RIGHT JOIN {5} AS b ON a.{6} = b.{6} - """).format(DataUtil.createIndex(tableName=tabTempStack, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - DataUtil.createIndex(tableName=stackedBlocksTable, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - tabTempBlock , ID_FIELD_BLOCK, - tabTempStack , stackedBlocksTable, - ID_FIELD_STACKED_BLOCK , GEOM_FIELD)) + """).format( + DataUtil.createIndex( + tableName=tabTempStack, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=stackedBlocksTable, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + tabTempBlock, + ID_FIELD_BLOCK, + tabTempStack, + stackedBlocksTable, + ID_FIELD_STACKED_BLOCK, + GEOM_FIELD, + ) + ) # Identify which blocks are in the cross-wind extend of blocks and impacted zone - cursor.execute(safe(""" + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0} AS SELECT ST_EXPAND(ST_EXTENT(a.{1}), {2}, 0) AS {1} @@ -1099,38 +1333,59 @@ def identifyImpactingStackedBlocks(cursor, UNION ALL SELECT {9} FROM {11}) - """).format( tabCrossExtBox , GEOM_FIELD, - crossWindExtend , stackedBlocksTable, - tabTempBlock , impactedZone, - DataUtil.createIndex(tableName=tabCrossExtBox, - fieldName=GEOM_FIELD, - isSpatial=True), - DataUtil.createIndex(tableName=stackedBlocksTable, - fieldName=GEOM_FIELD, - isSpatial=True), - tabTempBlock2 , ID_FIELD_BLOCK, - stackedBlocksTable , tabTempBlock)) - + """).format( + tabCrossExtBox, + GEOM_FIELD, + crossWindExtend, + stackedBlocksTable, + tabTempBlock, + impactedZone, + DataUtil.createIndex( + tableName=tabCrossExtBox, fieldName=GEOM_FIELD, isSpatial=True + ), + DataUtil.createIndex( + tableName=stackedBlocksTable, + fieldName=GEOM_FIELD, + isSpatial=True, + ), + tabTempBlock2, + ID_FIELD_BLOCK, + stackedBlocksTable, + tabTempBlock, + ) + ) + # Identify all stacked blocks belonging to the previously identified blocks - cursor.execute(safe(""" + cursor.execute( + safe(""" {0};{1}; DROP TABLE IF EXISTS {2}; CREATE TABLE {2} AS SELECT a.* FROM {4} AS a RIGHT JOIN {5} AS b ON a.{6} = b.{6} - """).format(DataUtil.createIndex(tableName=tabTempBlock2, - fieldName=ID_FIELD_BLOCK, - isSpatial=False), - DataUtil.createIndex(tableName=stackedBlocksTable, - fieldName=ID_FIELD_BLOCK, - isSpatial=False), - outputStackedBlocks , ID_FIELD_STACKED_BLOCK, - stackedBlocksTable , tabTempBlock2, - ID_FIELD_BLOCK)) - + """).format( + DataUtil.createIndex( + tableName=tabTempBlock2, + fieldName=ID_FIELD_BLOCK, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=stackedBlocksTable, + fieldName=ID_FIELD_BLOCK, + isSpatial=False, + ), + outputStackedBlocks, + ID_FIELD_STACKED_BLOCK, + stackedBlocksTable, + tabTempBlock2, + ID_FIELD_BLOCK, + ) + ) + # Select the Rôckle zones corresponding to the stacked blocks selection - selectionBuildQueries = [safe(""" + selectionBuildQueries = [ + safe(""" {4}; DROP TABLE IF EXISTS {0}; CREATE TABLE {0} @@ -1138,41 +1393,61 @@ def identifyImpactingStackedBlocks(cursor, FROM {1} AS a RIGHT JOIN {2} AS b ON a.{3} = b.{3} WHERE a.{5} IS NOT NULL - """).format( dicOfSelectedBuildZones[t] , dicOfBuildRockleZoneTable[t], - outputStackedBlocks , ID_FIELD_STACKED_BLOCK, - DataUtil.createIndex(tableName=dicOfBuildRockleZoneTable[t], - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - GEOM_FIELD) - for t in dicOfBuildRockleZoneTable] - cursor.execute(safe("""{0}; {1} - """).format(DataUtil.createIndex(tableName=outputStackedBlocks, - fieldName=ID_FIELD_STACKED_BLOCK, - isSpatial=False), - ";".join(selectionBuildQueries))) - + """).format( + dicOfSelectedBuildZones[t], + dicOfBuildRockleZoneTable[t], + outputStackedBlocks, + ID_FIELD_STACKED_BLOCK, + DataUtil.createIndex( + tableName=dicOfBuildRockleZoneTable[t], + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + GEOM_FIELD, + ) + for t in dicOfBuildRockleZoneTable + ] + cursor.execute( + safe("""{0}; {1} + """).format( + DataUtil.createIndex( + tableName=outputStackedBlocks, + fieldName=ID_FIELD_STACKED_BLOCK, + isSpatial=False, + ), + ";".join(selectionBuildQueries), + ) + ) + # ------------------------------------------------------------------------ # 2. MAKE THE CALCULATION FOR THE VEGETATION ----------------------------- # ------------------------------------------------------------------------ # Identify which vegetation patches are in the cross-wind extend of blocks and impacted zone - cursor.execute(safe(""" + cursor.execute( + safe(""" {0};{1}; DROP TABLE IF EXISTS {2}; CREATE TABLE {2} AS SELECT a.* FROM {3} AS a, {4} AS b WHERE a.{5} && b.{5} AND ST_INTERSECTS(a.{5}, b.{5}) - """).format( DataUtil.createIndex(tableName=tabCrossExtBox, - fieldName=GEOM_FIELD, - isSpatial=True), - DataUtil.createIndex(tableName=vegetationTable, - fieldName=GEOM_FIELD, - isSpatial=True), - outputVegetation , vegetationTable, - tabCrossExtBox , GEOM_FIELD)) + """).format( + DataUtil.createIndex( + tableName=tabCrossExtBox, fieldName=GEOM_FIELD, isSpatial=True + ), + DataUtil.createIndex( + tableName=vegetationTable, fieldName=GEOM_FIELD, isSpatial=True + ), + outputVegetation, + vegetationTable, + tabCrossExtBox, + GEOM_FIELD, + ) + ) # Select the Rôckle zones corresponding to the vegetation patches selection - selectionVegQueries = [safe(""" + selectionVegQueries = [ + safe(""" {4}; DROP TABLE IF EXISTS {0}; CREATE TABLE {0} @@ -1180,26 +1455,45 @@ def identifyImpactingStackedBlocks(cursor, FROM {1} AS a RIGHT JOIN {2} AS b ON a.{3} = b.{3} WHERE a.{5} IS NOT NULL - """).format( dicOfSelectedVegZones[t] , dicOfVegRockleZoneTable[t], - outputVegetation , ID_VEGETATION, - DataUtil.createIndex(tableName=dicOfVegRockleZoneTable[t], - fieldName=ID_VEGETATION, - isSpatial=False), - GEOM_FIELD) - for t in dicOfVegRockleZoneTable] - cursor.execute(safe("""{0}; {1} - """).format(DataUtil.createIndex(tableName=outputVegetation, - fieldName=ID_VEGETATION, - isSpatial=False), - ";".join(selectionVegQueries))) - - + """).format( + dicOfSelectedVegZones[t], + dicOfVegRockleZoneTable[t], + outputVegetation, + ID_VEGETATION, + DataUtil.createIndex( + tableName=dicOfVegRockleZoneTable[t], + fieldName=ID_VEGETATION, + isSpatial=False, + ), + GEOM_FIELD, + ) + for t in dicOfVegRockleZoneTable + ] + cursor.execute( + safe("""{0}; {1} + """).format( + DataUtil.createIndex( + tableName=outputVegetation, + fieldName=ID_VEGETATION, + isSpatial=False, + ), + ";".join(selectionVegQueries), + ) + ) + if not DEBUG: # Drop intermediate tables - cursor.execute(safe("DROP TABLE IF EXISTS {0}").format(",".join([tabTempStack, - tabTempBlock, - tabCrossExtBox, - tabTempBlock2]))) - - - return dicOfSelectedBuildZones, dicOfSelectedVegZones, outputStackedBlocks, outputVegetation \ No newline at end of file + cursor.execute( + safe("DROP TABLE IF EXISTS {0}").format( + ",".join( + [tabTempStack, tabTempBlock, tabCrossExtBox, tabTempBlock2] + ) + ) + ) + + return ( + dicOfSelectedBuildZones, + dicOfSelectedVegZones, + outputStackedBlocks, + outputVegetation, + ) diff --git a/functions/URock/__init__dep.py b/functions/URock/__init__dep.py index bf096df..46b5e23 100644 --- a/functions/URock/__init__dep.py +++ b/functions/URock/__init__dep.py @@ -22,9 +22,10 @@ This script initializes the plugin, making it known to QGIS. """ -__author__ = 'Jérémy Bernard / University of Gothenburg' -__date__ = '2021-10-04' -__copyright__ = '(C) 2021 by Jérémy Bernard / University of Gothenburg' +__author__ = "Jérémy Bernard / University of Gothenburg" +__date__ = "2021-10-04" +__copyright__ = "(C) 2021 by Jérémy Bernard / University of Gothenburg" + # noinspection PyPep8Naming def classFactory(iface): # pylint: disable=invalid-name @@ -35,4 +36,5 @@ def classFactory(iface): # pylint: disable=invalid-name """ # from .urock_processing import URockPlugin + return URockPlugin() diff --git a/functions/URock/loadData.py b/functions/URock/loadData.py index f8794df..b4fa3ac 100644 --- a/functions/URock/loadData.py +++ b/functions/URock/loadData.py @@ -11,49 +11,58 @@ from .DataUtil import safe import os -def loadData(fromCad , prefix, - idFieldBuild , buildingHeightField, - vegetationBaseHeight , vegetationTopHeight, - idVegetation , vegetationAttenuationFactor, - cursor , buildingFilePath, - vegetationFilePath , srid): - """ Load the input files into the database (could be converted if from CAD) - - Parameters - _ _ _ _ _ _ _ _ _ _ - - fromCad: boolean - Whether or not the data has to be converted from a CAD file - prefix: String - Name of the case to run. Also the name of the subdirectory containing - the geometry files. - idFieldBuild: String - Name of the ID field from the input building data - buildingHeightField: String - Name of the height field from the input building data - vegetationBaseHeight: String - Name of the base height field from the input vegetation data - vegetationTopHeight: String - Name of the top height field from the input vegetation data - idVegetation: String - Name of the ID field from the input vegetation data - vegetationAttenuationFactor: String - Name of the attenuatiojn factor field from the input vegetation data - cursor: conn.cursor - A cursor object, used to perform spatial SQL queries - buildingFilePath: String - The path of the file where are saved buildings data - vegetationFilePath: String - The path of the file where are saved vegetation data - srid: int - The SRID of the data - - Returns - _ _ _ _ _ _ _ _ _ _ - None""" +def loadData( + fromCad, + prefix, + idFieldBuild, + buildingHeightField, + vegetationBaseHeight, + vegetationTopHeight, + idVegetation, + vegetationAttenuationFactor, + cursor, + buildingFilePath, + vegetationFilePath, + srid, +): + """Load the input files into the database (could be converted if from CAD) + + Parameters + _ _ _ _ _ _ _ _ _ _ + + fromCad: boolean + Whether or not the data has to be converted from a CAD file + prefix: String + Name of the case to run. Also the name of the subdirectory containing + the geometry files. + idFieldBuild: String + Name of the ID field from the input building data + buildingHeightField: String + Name of the height field from the input building data + vegetationBaseHeight: String + Name of the base height field from the input vegetation data + vegetationTopHeight: String + Name of the top height field from the input vegetation data + idVegetation: String + Name of the ID field from the input vegetation data + vegetationAttenuationFactor: String + Name of the attenuatiojn factor field from the input vegetation data + cursor: conn.cursor + A cursor object, used to perform spatial SQL queries + buildingFilePath: String + The path of the file where are saved buildings data + vegetationFilePath: String + The path of the file where are saved vegetation data + srid: int + The SRID of the data + + Returns + _ _ _ _ _ _ _ _ _ _ + + None""" print("Load input data") - + # Create temporary table names (for tables that will be removed at the end of the IProcess) buildTablePreSrid = DataUtil.postfix("build_pre_srid") vegTablePreSrid = DataUtil.postfix("veg_pre_srid") @@ -63,184 +72,240 @@ def loadData(fromCad , prefix, # Check if the input comes from CAD file if fromCad: # IMPORT TRIANGLES AND CONVERT TO BUILDINGS AND VEGETATION GEOMETRIES - inputDataRel["cadTriangles"] = os.path.join(inputDirectory, prefix, - inputGeometries["cadTriangles"]) - inputDataAbs["cadTriangles"] = os.path.abspath(inputDataRel["cadTriangles"]) - + inputDataRel["cadTriangles"] = os.path.join( + inputDirectory, prefix, inputGeometries["cadTriangles"] + ) + inputDataAbs["cadTriangles"] = os.path.abspath( + inputDataRel["cadTriangles"] + ) + # Load CAD triangles into H2GIS DB - loadFile(cursor = cursor, - filePath = inputDataAbs["cadTriangles"], - tableName = CAD_TRIANGLE_NAME) - + loadFile( + cursor=cursor, + filePath=inputDataAbs["cadTriangles"], + tableName=CAD_TRIANGLE_NAME, + ) + if inputGeometries["cadTreesIntersection"]: - inputDataRel["cadTreesIntersection"] = os.path.join(inputDirectory, prefix, - inputGeometries["cadTreesIntersection"]) - inputDataAbs["cadTreesIntersection"] = os.path.abspath(inputDataRel["cadTreesIntersection"]) - + inputDataRel["cadTreesIntersection"] = os.path.join( + inputDirectory, prefix, inputGeometries["cadTreesIntersection"] + ) + inputDataAbs["cadTreesIntersection"] = os.path.abspath( + inputDataRel["cadTreesIntersection"] + ) + # Load vegetation intersection into H2GIS DB - loadFile(cursor = cursor, - filePath = inputDataAbs["cadTreesIntersection"], - tableName = CAD_VEG_INTERSECTION) + loadFile( + cursor=cursor, + filePath=inputDataAbs["cadTreesIntersection"], + tableName=CAD_VEG_INTERSECTION, + ) treesZone = CAD_VEG_INTERSECTION else: - cursor.execute(safe(""" + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0}(PK INTEGER, {1} GEOMETRY, {2} DOUBLE, {3} DOUBLE, {4} INTEGER, {5} DOUBLE) - """).format( vegTablePreSrid, - GEOM_FIELD, - VEGETATION_CROWN_BASE_HEIGHT, - VEGETATION_CROWN_TOP_HEIGHT, - ID_VEGETATION, - VEGETATION_ATTENUATION_FACTOR)) + """).format( + vegTablePreSrid, + GEOM_FIELD, + VEGETATION_CROWN_BASE_HEIGHT, + VEGETATION_CROWN_TOP_HEIGHT, + ID_VEGETATION, + VEGETATION_ATTENUATION_FACTOR, + ) + ) treesZone = None - + # Convert 3D triangles to 2.5 d buildings and tree patches - fromShp3dTo2_5(cursor = cursor , triangles3d = CAD_TRIANGLE_NAME, - TreesZone = treesZone , buildTableName = buildTablePreSrid, - vegTableName = vegTablePreSrid , prefix = PREFIX_NAME) - + fromShp3dTo2_5( + cursor=cursor, + triangles3d=CAD_TRIANGLE_NAME, + TreesZone=treesZone, + buildTableName=buildTablePreSrid, + vegTableName=vegTablePreSrid, + prefix=PREFIX_NAME, + ) + # Save the building and vegetation layers ready to be used in URock - DataUtil.saveTable(cursor = cursor, - tableName = buildTablePreSrid, - filedir = os.path.join(buildingFilePath), - delete=True) - DataUtil.saveTable(cursor = cursor, - tableName = vegTablePreSrid, - filedir = os.path.join(vegetationFilePath), - delete=True) - + DataUtil.saveTable( + cursor=cursor, + tableName=buildTablePreSrid, + filedir=os.path.join(buildingFilePath), + delete=True, + ) + DataUtil.saveTable( + cursor=cursor, + tableName=vegTablePreSrid, + filedir=os.path.join(vegetationFilePath), + delete=True, + ) + else: importQuery = "" h2gisBuildSrid = 0 h2gisVegSrid = 0 - # 1. IMPORT BUILDING GEOMETRIES + # 1. IMPORT BUILDING GEOMETRIES if buildingFilePath: # Load buildings into H2GIS DB - loadFile(cursor = cursor, - filePath = os.path.abspath(buildingFilePath), - tableName = buildTablePreSrid) - + loadFile( + cursor=cursor, + filePath=os.path.abspath(buildingFilePath), + tableName=buildTablePreSrid, + ) + # Get the building SRID cursor.execute(safe(""" SELECT ST_SRID({0}) AS SRID FROM {1} LIMIT 1 - """).format(GEOM_FIELD , buildTablePreSrid)) + """).format(GEOM_FIELD, buildTablePreSrid)) h2gisBuildSrid = cursor.fetchall()[0][0] - + # Create an ID FIELD if None. if idFieldBuild is None or idFieldBuild == "": cursor.execute(safe(""" ALTER TABLE {0} DROP COLUMN IF EXISTS {1}; ALTER TABLE {0} ADD COLUMN {1} BIGINT AUTO_INCREMENT; - """).format( buildTablePreSrid , ID_FIELD_BUILD)) + """).format(buildTablePreSrid, ID_FIELD_BUILD)) idFieldBuild = ID_FIELD_BUILD - + # Rename building fields to generic names if idFieldBuild.upper() != ID_FIELD_BUILD.upper(): importQuery += """ ALTER TABLE {0} DROP COLUMN IF EXISTS {2}; ALTER TABLE {0} RENAME COLUMN {1} TO {2}; - """.format( buildTablePreSrid, - idFieldBuild, ID_FIELD_BUILD) + """.format(buildTablePreSrid, idFieldBuild, ID_FIELD_BUILD) if buildingHeightField.upper() != HEIGHT_FIELD.upper(): importQuery += """ ALTER TABLE {0} DROP COLUMN IF EXISTS {2}; ALTER TABLE {0} RENAME COLUMN {1} TO {2}; - """.format( buildTablePreSrid, - buildingHeightField, HEIGHT_FIELD) - + """.format( + buildTablePreSrid, buildingHeightField, HEIGHT_FIELD + ) + else: importQuery += """ DROP TABLE IF EXISTS {0}; CREATE TABLE {0}({1} INTEGER, {2} GEOMETRY, {3} INTEGER); - """.format( buildTablePreSrid, - ID_FIELD_BUILD, - GEOM_FIELD, - HEIGHT_FIELD) - + """.format( + buildTablePreSrid, ID_FIELD_BUILD, GEOM_FIELD, HEIGHT_FIELD + ) + # 2. IMPORT VEGETATION GEOMETRIES if vegetationFilePath: # Load vegetation into H2GIS DB - loadFile(cursor = cursor, - filePath = os.path.abspath(vegetationFilePath), - tableName = vegTablePreSrid) - + loadFile( + cursor=cursor, + filePath=os.path.abspath(vegetationFilePath), + tableName=vegTablePreSrid, + ) + # Get the vegetation SRID cursor.execute(safe(""" SELECT ST_SRID({0}) AS SRID FROM {1} LIMIT 1 - """).format(GEOM_FIELD , vegTablePreSrid)) + """).format(GEOM_FIELD, vegTablePreSrid)) h2gisVegSrid = cursor.fetchall()[0][0] - + # Create an ID FIELD if None. if idVegetation is None or idVegetation == "": cursor.execute(safe(""" ALTER TABLE {0} DROP COLUMN IF EXISTS {1}; ALTER TABLE {0} ADD COLUMN {1} BIGINT AUTO_INCREMENT; - """).format( vegTablePreSrid , ID_VEGETATION)) + """).format(vegTablePreSrid, ID_VEGETATION)) idVegetation = ID_VEGETATION # Create an attenuation attribute with default 'DEFAULT_VEG_ATTEN_FACT' # if no column - if vegetationAttenuationFactor is None or vegetationAttenuationFactor == "": - cursor.execute(safe(""" + if ( + vegetationAttenuationFactor is None + or vegetationAttenuationFactor == "" + ): + cursor.execute( + safe(""" ALTER TABLE {0} DROP COLUMN IF EXISTS {1}; ALTER TABLE {0} ADD COLUMN {1} DOUBLE DEFAULT {2}; - """).format( vegTablePreSrid , VEGETATION_ATTENUATION_FACTOR, - DEFAULT_VEG_ATTEN_FACT)) + """).format( + vegTablePreSrid, + VEGETATION_ATTENUATION_FACTOR, + DEFAULT_VEG_ATTEN_FACT, + ) + ) vegetationAttenuationFactor = VEGETATION_ATTENUATION_FACTOR # Create a base height attribute with default 'DEFAULT_VEG_CROWN_BASE_HEIGHT_FRAC' # of the maximum height if no attribute for base height if vegetationBaseHeight is None or vegetationBaseHeight == "": - cursor.execute(safe(""" + cursor.execute( + safe(""" ALTER TABLE {0} DROP COLUMN IF EXISTS {1}; ALTER TABLE {0} ADD COLUMN {1} DOUBLE; UPDATE {0} SET {1} = {2} * {3}; - """).format( vegTablePreSrid, - VEGETATION_CROWN_BASE_HEIGHT, - DEFAULT_VEG_CROWN_BASE_HEIGHT_FRAC, - vegetationTopHeight)) + """).format( + vegTablePreSrid, + VEGETATION_CROWN_BASE_HEIGHT, + DEFAULT_VEG_CROWN_BASE_HEIGHT_FRAC, + vegetationTopHeight, + ) + ) vegetationBaseHeight = VEGETATION_CROWN_BASE_HEIGHT - + # Load vegetation data and rename fields to generic names - if vegetationBaseHeight.upper() != VEGETATION_CROWN_BASE_HEIGHT.upper(): + if ( + vegetationBaseHeight.upper() + != VEGETATION_CROWN_BASE_HEIGHT.upper() + ): importQuery += """ ALTER TABLE {0} DROP COLUMN IF EXISTS {2}; ALTER TABLE {0} RENAME COLUMN {1} TO {2}; - """.format( vegTablePreSrid, - vegetationBaseHeight, VEGETATION_CROWN_BASE_HEIGHT) - if vegetationTopHeight.upper() != VEGETATION_CROWN_TOP_HEIGHT.upper(): + """.format( + vegTablePreSrid, + vegetationBaseHeight, + VEGETATION_CROWN_BASE_HEIGHT, + ) + if ( + vegetationTopHeight.upper() + != VEGETATION_CROWN_TOP_HEIGHT.upper() + ): importQuery += """ ALTER TABLE {0} DROP COLUMN IF EXISTS {2}; ALTER TABLE {0} RENAME COLUMN {1} TO {2}; - """.format( vegTablePreSrid, - vegetationTopHeight, VEGETATION_CROWN_TOP_HEIGHT) + """.format( + vegTablePreSrid, + vegetationTopHeight, + VEGETATION_CROWN_TOP_HEIGHT, + ) if idVegetation.upper() != ID_VEGETATION.upper(): importQuery += """ ALTER TABLE {0} DROP COLUMN IF EXISTS {2}; ALTER TABLE {0} RENAME COLUMN {1} TO {2}; - """.format( vegTablePreSrid, - idVegetation, ID_VEGETATION) - if vegetationAttenuationFactor.upper() != VEGETATION_ATTENUATION_FACTOR.upper(): + """.format(vegTablePreSrid, idVegetation, ID_VEGETATION) + if ( + vegetationAttenuationFactor.upper() + != VEGETATION_ATTENUATION_FACTOR.upper() + ): importQuery += """ ALTER TABLE {0} DROP COLUMN IF EXISTS {2}; ALTER TABLE {0} RENAME COLUMN {1} TO {2}; - """.format( vegTablePreSrid, - vegetationAttenuationFactor, VEGETATION_ATTENUATION_FACTOR) + """.format( + vegTablePreSrid, + vegetationAttenuationFactor, + VEGETATION_ATTENUATION_FACTOR, + ) else: importQuery += """ DROP TABLE IF EXISTS {0}; CREATE TABLE {0}(PK INTEGER, {1} GEOMETRY, {2} DOUBLE, {3} DOUBLE, {4} INTEGER, {5} DOUBLE); - """.format( vegTablePreSrid, - GEOM_FIELD, - VEGETATION_CROWN_BASE_HEIGHT, - VEGETATION_CROWN_TOP_HEIGHT, - ID_VEGETATION, - VEGETATION_ATTENUATION_FACTOR) + """.format( + vegTablePreSrid, + GEOM_FIELD, + VEGETATION_CROWN_BASE_HEIGHT, + VEGETATION_CROWN_TOP_HEIGHT, + ID_VEGETATION, + VEGETATION_ATTENUATION_FACTOR, + ) cursor.execute(importQuery) - - # 3. SET VEGETATION AND BUILDING TABLE SRID AND REMOVE SMALL OBSTACLES + # 3. SET VEGETATION AND BUILDING TABLE SRID AND REMOVE SMALL OBSTACLES # If H2GIS does not identify any SRID for the tables, set the ones identied by GDAL if h2gisBuildSrid == 0 and h2gisVegSrid == 0: buildSrid = srid @@ -254,8 +319,9 @@ def loadData(fromCad , prefix, else: vegSrid = h2gisVegSrid buildSrid = h2gisBuildSrid - - cursor.execute(safe(""" + + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0} AS SELECT ST_COLLECTIONEXTRACT(ST_SETSRID({1}, {2}), 3) AS {1}, @@ -269,97 +335,121 @@ def loadData(fromCad , prefix, ROW_NUMBER() OVER (ORDER by {7}) AS {7}, {8}, {9}, {10} FROM ST_EXPLODE('{11}') WHERE {9} > 0.5; - """).format(BUILDING_TABLE_NAME , GEOM_FIELD, - buildSrid , ID_FIELD_BUILD, - HEIGHT_FIELD , buildTablePreSrid, - VEGETATION_TABLE_NAME , ID_VEGETATION, - VEGETATION_CROWN_BASE_HEIGHT , VEGETATION_CROWN_TOP_HEIGHT, - VEGETATION_ATTENUATION_FACTOR , vegTablePreSrid, - vegSrid)) - - + """).format( + BUILDING_TABLE_NAME, + GEOM_FIELD, + buildSrid, + ID_FIELD_BUILD, + HEIGHT_FIELD, + buildTablePreSrid, + VEGETATION_TABLE_NAME, + ID_VEGETATION, + VEGETATION_CROWN_BASE_HEIGHT, + VEGETATION_CROWN_TOP_HEIGHT, + VEGETATION_ATTENUATION_FACTOR, + vegTablePreSrid, + vegSrid, + ) + ) + if not DEBUG: # Drop intermediate tables - cursor.execute("DROP TABLE IF EXISTS {0}".format(",".join([vegTablePreSrid, buildTablePreSrid]))) - -def loadFile(cursor, filePath, tableName, srid = None, srid_repro = None): - """ Load a file in the database according to its extension - - Parameters - _ _ _ _ _ _ _ _ _ _ + cursor.execute( + "DROP TABLE IF EXISTS {0}".format( + ",".join([vegTablePreSrid, buildTablePreSrid]) + ) + ) - cursor: conn.cursor - A cursor object, used to perform spatial SQL queries - filePath: String - Path of the file to load - tableName: String - Name of the table for the loaded file - srid: int, default None - SRID of the loaded file (if known) - srid_repro: int, default None - SRID if you want to reproject the data - - Returns - _ _ _ _ _ _ _ _ _ _ - None""" - print("Load table '{0}'".format(tableName)) +def loadFile(cursor, filePath, tableName, srid=None, srid_repro=None): + """Load a file in the database according to its extension + + Parameters + _ _ _ _ _ _ _ _ _ _ + + cursor: conn.cursor + A cursor object, used to perform spatial SQL queries + filePath: String + Path of the file to load + tableName: String + Name of the table for the loaded file + srid: int, default None + SRID of the loaded file (if known) + srid_repro: int, default None + SRID if you want to reproject the data + + Returns + _ _ _ _ _ _ _ _ _ _ + + None""" + print("Load table '{0}'".format(tableName)) # Get the input building file extension and the appropriate h2gis read function name fileExtension = filePath.split(".")[-1] readFunction = DataUtil.readFunction(fileExtension) - + if readFunction == "CSVREAD": cursor.execute(safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0} AS SELECT * FROM {2}('{1}'); - """).format( tableName, filePath, readFunction)) - else: # Import and then copy into a new table to remove all constraints (primary keys...) + """).format(tableName, filePath, readFunction)) + else: # Import and then copy into a new table to remove all constraints (primary keys...) cursor.execute(safe(""" DROP TABLE IF EXISTS TEMPO, {0}; CALL {2}('{1}','TEMPO'); CREATE TABLE {0} AS SELECT * FROM TEMPO; - """).format( tableName, filePath, readFunction)) - + """).format(tableName, filePath, readFunction)) + if srid_repro: reproject_function = "ST_TRANSFORM(" reproject_srid = ", {0})".format(srid_repro) else: reproject_function = "" reproject_srid = "" - + if srid: listCols = DataUtil.getColumns(cursor, tableName) listCols.remove(GEOM_FIELD) listCols_sql = ",".join(listCols) if listCols_sql != "": listCols_sql += "," - - cursor.execute(safe(""" + + cursor.execute( + safe(""" DROP TABLE IF EXISTS TEMPO_LOAD; CREATE TABLE TEMPO_LOAD AS SELECT {0} {4}ST_SETSRID({1}, {2}){5} AS {1} FROM {3}; DROP TABLE {3}; ALTER TABLE TEMPO_LOAD RENAME TO {3} - """).format(listCols_sql, - GEOM_FIELD, - srid, - tableName, - reproject_function, - reproject_srid)) - - -def fromShp3dTo2_5(cursor, triangles3d, TreesZone, buildTableName, - vegTableName, prefix = PREFIX_NAME, save = True): - """ Convert 3D shapefile to 2.5 D shapefiles distinguishing + """).format( + listCols_sql, + GEOM_FIELD, + srid, + tableName, + reproject_function, + reproject_srid, + ) + ) + + +def fromShp3dTo2_5( + cursor, + triangles3d, + TreesZone, + buildTableName, + vegTableName, + prefix=PREFIX_NAME, + save=True, +): + """Convert 3D shapefile to 2.5 D shapefiles distinguishing buildings from trees if the surface intersecting trees is passed - - Parameters - _ _ _ _ _ _ _ _ _ _ + + Parameters + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries @@ -375,13 +465,13 @@ def fromShp3dTo2_5(cursor, triangles3d, TreesZone, buildTableName, Prefix to add to the output table name save: boolean, default True Whether or not the resulting 2.5 layers are saved - - Returns - _ _ _ _ _ _ _ _ _ _ + + Returns + _ _ _ _ _ _ _ _ _ _ None""" print("From 3D to 2.5D geometries") - + # Create temporary table names (for tables that will be removed at the end of the IProcess) trianglesWithId = DataUtil.postfix("triangles_with_id") trees2d = DataUtil.postfix("trees_2d") @@ -396,10 +486,11 @@ def fromShp3dTo2_5(cursor, triangles3d, TreesZone, buildTableName, AS (SELECT CAST((row_number() over()) as Integer) AS ID, {1} FROM ST_EXPLODE('(SELECT * FROM {2} WHERE ST_AREA({1})>0)')) """).format(trianglesWithId, GEOM_FIELD, triangles3d)) - + if TreesZone: # Identify triangles being trees and convert them to 2D polygons - cursor.execute(safe(""" + cursor.execute( + safe(""" {6}; {7}; DROP TABLE IF EXISTS {0}; @@ -409,18 +500,27 @@ def fromShp3dTo2_5(cursor, triangles3d, TreesZone, buildTableName, CAST(ST_ZMIN(a.{1}) AS INT) AS {3} FROM {4} AS a, {5} AS b WHERE a.{1} && b.{1} AND ST_INTERSECTS(a.{1}, b.{1}) - """).format( trees2d , GEOM_FIELD, - VEGETATION_CROWN_TOP_HEIGHT , VEGETATION_CROWN_BASE_HEIGHT, - trianglesWithId , TreesZone, - DataUtil.createIndex(tableName=trianglesWithId, - fieldName=GEOM_FIELD, - isSpatial=True), - DataUtil.createIndex(tableName=TreesZone, - fieldName=GEOM_FIELD, - isSpatial=True))) - + """).format( + trees2d, + GEOM_FIELD, + VEGETATION_CROWN_TOP_HEIGHT, + VEGETATION_CROWN_BASE_HEIGHT, + trianglesWithId, + TreesZone, + DataUtil.createIndex( + tableName=trianglesWithId, + fieldName=GEOM_FIELD, + isSpatial=True, + ), + DataUtil.createIndex( + tableName=TreesZone, fieldName=GEOM_FIELD, isSpatial=True + ), + ) + ) + # Identify triangles being buildings and convert them to 2D polygons - cursor.execute(safe(""" + cursor.execute( + safe(""" {5}; {6}; DROP TABLE IF EXISTS {0}; @@ -430,18 +530,25 @@ def fromShp3dTo2_5(cursor, triangles3d, TreesZone, buildTableName, FROM {3} AS a LEFT JOIN {4} AS b ON a.ID = b.ID WHERE b.ID IS NULL - """).format( buildings2d , GEOM_FIELD, - HEIGHT_FIELD , trianglesWithId, - trees2d , DataUtil.createIndex( tableName=trianglesWithId, - fieldName="ID", - isSpatial=False), - DataUtil.createIndex(tableName=trees2d, - fieldName="ID", - isSpatial=False))) - - # Identify unique trees triangles keeping only the highest one whenever + """).format( + buildings2d, + GEOM_FIELD, + HEIGHT_FIELD, + trianglesWithId, + trees2d, + DataUtil.createIndex( + tableName=trianglesWithId, fieldName="ID", isSpatial=False + ), + DataUtil.createIndex( + tableName=trees2d, fieldName="ID", isSpatial=False + ), + ) + ) + + # Identify unique trees triangles keeping only the highest one whenever # 2 triangles are superimposed - cursor.execute(safe(""" + cursor.execute( + safe(""" {9}; {10}; {11}; @@ -460,34 +567,48 @@ def fromShp3dTo2_5(cursor, triangles3d, TreesZone, buildTableName, FROM {3} AS a LEFT JOIN {0} AS b ON a.ID = b.ID WHERE b.ID IS NULL - """).format( treesCovered , GEOM_FIELD, - VEGETATION_CROWN_TOP_HEIGHT , trees2d, - vegTableName , ID_VEGETATION, - VEGETATION_CROWN_BASE_HEIGHT , DEFAULT_VEG_ATTEN_FACT, - VEGETATION_ATTENUATION_FACTOR , DataUtil.createIndex(tableName=trees2d, - fieldName=GEOM_FIELD, - isSpatial=True), - DataUtil.createIndex(tableName=trees2d, - fieldName=VEGETATION_CROWN_TOP_HEIGHT, - isSpatial=False), - DataUtil.createIndex(tableName=trees2d, - fieldName="ID", - isSpatial=False))) - + """).format( + treesCovered, + GEOM_FIELD, + VEGETATION_CROWN_TOP_HEIGHT, + trees2d, + vegTableName, + ID_VEGETATION, + VEGETATION_CROWN_BASE_HEIGHT, + DEFAULT_VEG_ATTEN_FACT, + VEGETATION_ATTENUATION_FACTOR, + DataUtil.createIndex( + tableName=trees2d, fieldName=GEOM_FIELD, isSpatial=True + ), + DataUtil.createIndex( + tableName=trees2d, + fieldName=VEGETATION_CROWN_TOP_HEIGHT, + isSpatial=False, + ), + DataUtil.createIndex( + tableName=trees2d, fieldName="ID", isSpatial=False + ), + ) + ) + else: # Convert building triangles to to 2.5D polygons - cursor.execute(safe(""" + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0} AS SELECT ID, ST_FORCE2D({1}) AS {1}, CAST(ST_ZMAX({1}) AS INT) AS {2} FROM {3} - """).format( buildings2d , GEOM_FIELD, - HEIGHT_FIELD , trianglesWithId)) - - # Identify unique building triangles keeping only the highest one whenever + """).format( + buildings2d, GEOM_FIELD, HEIGHT_FIELD, trianglesWithId + ) + ) + + # Identify unique building triangles keeping only the highest one whenever # 2 triangles are superimposed - cursor.execute(safe(""" + cursor.execute( + safe(""" {6}; {7}; {8}; @@ -506,24 +627,32 @@ def fromShp3dTo2_5(cursor, triangles3d, TreesZone, buildTableName, FROM {3} AS a LEFT JOIN {0} AS b ON a.ID = b.ID WHERE b.ID IS NULL - """).format( buildingsCovered , GEOM_FIELD, - HEIGHT_FIELD , buildings2d, - buildTableName , ID_FIELD_BUILD, - DataUtil.createIndex(tableName=buildings2d, - fieldName="ID", - isSpatial=False), - DataUtil.createIndex(tableName=buildings2d, - fieldName=GEOM_FIELD, - isSpatial=True), - DataUtil.createIndex(tableName=buildings2d, - fieldName=HEIGHT_FIELD, - isSpatial=False), - DataUtil.createIndex(tableName=buildingsCovered, - fieldName="ID", - isSpatial=False))) + """).format( + buildingsCovered, + GEOM_FIELD, + HEIGHT_FIELD, + buildings2d, + buildTableName, + ID_FIELD_BUILD, + DataUtil.createIndex( + tableName=buildings2d, fieldName="ID", isSpatial=False + ), + DataUtil.createIndex( + tableName=buildings2d, fieldName=GEOM_FIELD, isSpatial=True + ), + DataUtil.createIndex( + tableName=buildings2d, fieldName=HEIGHT_FIELD, isSpatial=False + ), + DataUtil.createIndex( + tableName=buildingsCovered, fieldName="ID", isSpatial=False + ), + ) + ) if not DEBUG: # Drop intermediate tables - cursor.execute(safe("DROP TABLE IF EXISTS {0}").format(",".join([trianglesWithId, - trees2d, - buildings2d]))) \ No newline at end of file + cursor.execute( + safe("DROP TABLE IF EXISTS {0}").format( + ",".join([trianglesWithId, trees2d, buildings2d]) + ) + ) diff --git a/functions/URock/saveData.py b/functions/URock/saveData.py index 4598161..57a548f 100644 --- a/functions/URock/saveData.py +++ b/functions/URock/saveData.py @@ -5,110 +5,176 @@ @author: Jérémy Bernard, University of Gothenburg """ + import pandas as pd import geopandas as gpd import shutil import numpy as np + # from scipy.interpolate import griddata # from rasterio.transform import from_origin # import rasterio -from .DataUtil import radToDeg, windDirectionFromXY, createIndex, prefix,safe +from .DataUtil import radToDeg, windDirectionFromXY, createIndex, prefix, safe from .Obstacles import windRotation from osgeo.osr import SpatialReference -from osgeo.gdal import Grid, GridOptions, FillNodata, Open, GA_Update, GetDriverByName -from .GlobalVariables import HORIZ_WIND_DIRECTION, HORIZ_WIND_SPEED, WIND_SPEED,\ - ID_POINT, TEMPO_DIRECTORY, TEMPO_HORIZ_WIND_FILE, VERT_WIND_SPEED, GEOM_FIELD,\ - OUTPUT_DIRECTORY, MESH_SIZE, OUTPUT_FILENAME, DELETE_OUTPUT_IF_EXISTS,\ - OUTPUT_RASTER_EXTENSION, OUTPUT_VECTOR_EXTENSION, OUTPUT_NETCDF_EXTENSION,\ - WIND_GROUP, WINDSPEED_PROFILE, RLON, RLAT, LON, LAT, LEVELS, WINDSPEED_X,\ - WINDSPEED_Y, WINDSPEED_Z, VERT_WIND, Z, OUTPUT_FILENAME, PREFIX_NAME,\ - BASE_HEIGHT_FIELD, HEIGHT_FIELD +from osgeo.gdal import ( + Grid, + GridOptions, + FillNodata, + Open, + GA_Update, + GetDriverByName, +) +from .GlobalVariables import ( + HORIZ_WIND_DIRECTION, + HORIZ_WIND_SPEED, + WIND_SPEED, + ID_POINT, + TEMPO_DIRECTORY, + TEMPO_HORIZ_WIND_FILE, + VERT_WIND_SPEED, + GEOM_FIELD, + OUTPUT_DIRECTORY, + MESH_SIZE, + OUTPUT_FILENAME, + DELETE_OUTPUT_IF_EXISTS, + OUTPUT_RASTER_EXTENSION, + OUTPUT_VECTOR_EXTENSION, + OUTPUT_NETCDF_EXTENSION, + WIND_GROUP, + WINDSPEED_PROFILE, + RLON, + RLAT, + LON, + LAT, + LEVELS, + WINDSPEED_X, + WINDSPEED_Y, + WINDSPEED_Z, + VERT_WIND, + Z, + OUTPUT_FILENAME, + PREFIX_NAME, + BASE_HEIGHT_FIELD, + HEIGHT_FIELD, +) from datetime import datetime import netCDF4 as nc4 import os import processing -def saveBasicOutputs(cursor, z_out, dz, u, v, w, gridName, - verticalWindProfile, outputFilePath, meshSize, - stacked_blocks, outputFilename = OUTPUT_FILENAME, - outputRaster = None, saveRaster = True, - saveVector = True, saveNetcdf = True, - prefix_name = PREFIX_NAME, tmp_dir = TEMPO_DIRECTORY): + +def saveBasicOutputs( + cursor, + z_out, + dz, + u, + v, + w, + gridName, + verticalWindProfile, + outputFilePath, + meshSize, + stacked_blocks, + outputFilename=OUTPUT_FILENAME, + outputRaster=None, + saveRaster=True, + saveVector=True, + saveNetcdf=True, + prefix_name=PREFIX_NAME, + tmp_dir=TEMPO_DIRECTORY, +): # Get the srid of the input geometry cursor.execute(safe(""" SELECT ST_SRID({0}) AS srid FROM {1} LIMIT 1 - """).format( GEOM_FIELD, - gridName)) + """).format(GEOM_FIELD, gridName)) srid = cursor.fetchall()[0][0] - + # ------------------------------------------------------------------- # SAVE NETCDF ------------------------------------------------------- - # ------------------------------------------------------------------- + # ------------------------------------------------------------------- final_netcdf_path = None - if saveNetcdf: - # Get the coordinate in lat/lon of each point + if saveNetcdf: + # Get the coordinate in lat/lon of each point # WARNING : for now keep the data in local coordinates) cursor.execute(safe(""" SELECT ST_X({0}) AS LON, ST_Y({0}) AS LAT FROM (SELECT ST_TRANSFORM(ST_SETSRID({0},{2}), 4326) AS {0} FROM {1}) - """).format( GEOM_FIELD, - gridName, - srid)) + """).format(GEOM_FIELD, gridName, srid)) coord = np.array(cursor.fetchall()) # Convert to a 2D (X, Y) array nx = u.shape[0] ny = u.shape[1] - longitude = np.array([[coord[i * nx + j, 0] for i in range(ny)] for j in range(nx)]) - latitude = np.array([[coord[i * nx + j, 1] for i in range(ny)] for j in range(nx)]) - - + longitude = np.array( + [[coord[i * nx + j, 0] for i in range(ny)] for j in range(nx)] + ) + latitude = np.array( + [[coord[i * nx + j, 1] for i in range(ny)] for j in range(nx)] + ) + # Save the data into a NetCDF file # If delete = False, add a suffix to the file - netcdf_base_dir_name = os.path.join(outputFilePath, - prefix(outputFilename, prefix_name)) + netcdf_base_dir_name = os.path.join( + outputFilePath, prefix(outputFilename, prefix_name) + ) if os.path.isfile(netcdf_base_dir_name + OUTPUT_NETCDF_EXTENSION): if DELETE_OUTPUT_IF_EXISTS: os.remove(netcdf_base_dir_name + OUTPUT_NETCDF_EXTENSION) else: - netcdf_base_dir_name = renameFileIfExists(filedir = netcdf_base_dir_name, - extension = OUTPUT_NETCDF_EXTENSION) - final_netcdf_path = saveToNetCDF(longitude = longitude, - latitude = latitude, - x = range(nx), - y = range(ny), - u = u, - v = v, - w = w, - verticalWindProfile = verticalWindProfile, - path = netcdf_base_dir_name, - urock_srid = srid, - horizontal_res = meshSize, - vertical_res = dz) - - horizOutputUrock = {z_i : "HORIZ_OUTPUT_UROCK_{0}".format(str(z_i).replace(".","_")) for z_i in z_out} + netcdf_base_dir_name = renameFileIfExists( + filedir=netcdf_base_dir_name, + extension=OUTPUT_NETCDF_EXTENSION, + ) + final_netcdf_path = saveToNetCDF( + longitude=longitude, + latitude=latitude, + x=range(nx), + y=range(ny), + u=u, + v=v, + w=w, + verticalWindProfile=verticalWindProfile, + path=netcdf_base_dir_name, + urock_srid=srid, + horizontal_res=meshSize, + vertical_res=dz, + ) + + horizOutputUrock = { + z_i: "HORIZ_OUTPUT_UROCK_{0}".format(str(z_i).replace(".", "_")) + for z_i in z_out + } for z_i in z_out: # Keep only wind field for a single horizontal plan (and convert carthesian # wind speed into polar at least for horizontal) tempoTable = "TEMPO_HORIZ" if z_i % dz % (dz / 2) == 0: n_lev = int(z_i / dz) + 1 - ufin = u[:,:,n_lev] - vfin = v[:,:,n_lev] - wfin = w[:,:,n_lev] + ufin = u[:, :, n_lev] + vfin = v[:, :, n_lev] + wfin = w[:, :, n_lev] else: - n_lev = int((z_i + dz /2) / dz) + n_lev = int((z_i + dz / 2) / dz) n_lev1 = n_lev + 1 weight1 = (z_i - (n_lev - 0.5) * dz) / dz weight = 1 - weight1 - ufin = (weight * u[:,:,n_lev] + weight1 * u[:,:,n_lev1]) - vfin = (weight * v[:,:,n_lev] + weight1 * v[:,:,n_lev1]) - wfin = (weight * w[:,:,n_lev] + weight1 * w[:,:,n_lev1]) - df = pd.DataFrame({HORIZ_WIND_SPEED: ((ufin ** 2 + vfin ** 2) ** 0.5).flatten("F"), - WIND_SPEED: ((ufin ** 2 + vfin ** 2 + wfin ** 2) ** 0.5).flatten("F"), - HORIZ_WIND_DIRECTION: radToDeg(windDirectionFromXY(ufin, vfin)).flatten("F"), - VERT_WIND_SPEED: wfin.flatten("F")}).rename_axis(ID_POINT) - + ufin = weight * u[:, :, n_lev] + weight1 * u[:, :, n_lev1] + vfin = weight * v[:, :, n_lev] + weight1 * v[:, :, n_lev1] + wfin = weight * w[:, :, n_lev] + weight1 * w[:, :, n_lev1] + df = pd.DataFrame( + { + HORIZ_WIND_SPEED: ((ufin**2 + vfin**2) ** 0.5).flatten("F"), + WIND_SPEED: ( + ((ufin**2 + vfin**2 + wfin**2) ** 0.5).flatten("F") + ), + HORIZ_WIND_DIRECTION: ( + radToDeg(windDirectionFromXY(ufin, vfin)).flatten("F") + ), + VERT_WIND_SPEED: wfin.flatten("F"), + } + ).rename_axis(ID_POINT) + # Load horizontal wind speed, wind direction and # vertical wind speed in a file containing a geometry field csv_tmp_file = os.path.join(tmp_dir, TEMPO_HORIZ_WIND_FILE) @@ -126,73 +192,91 @@ def saveBasicOutputs(cursor, z_out, dz, u, v, w, gridName, FROM {8} AS a LEFT JOIN {9} AS b ON a.{3} = b.{3} - """).format(createIndex(tableName=gridName, - fieldName=ID_POINT, - isSpatial=False), - createIndex(tableName=tempoTable, - fieldName=ID_POINT, - isSpatial=False), - horizOutputUrock[z_i] , ID_POINT, - GEOM_FIELD , HORIZ_WIND_SPEED, - HORIZ_WIND_DIRECTION , VERT_WIND_SPEED, - gridName , tempoTable, - csv_tmp_file, - WIND_SPEED)) - + """).format( + createIndex( + tableName=gridName, fieldName=ID_POINT, isSpatial=False + ), + createIndex( + tableName=tempoTable, fieldName=ID_POINT, isSpatial=False + ), + horizOutputUrock[z_i], + ID_POINT, + GEOM_FIELD, + HORIZ_WIND_SPEED, + HORIZ_WIND_DIRECTION, + VERT_WIND_SPEED, + gridName, + tempoTable, + csv_tmp_file, + WIND_SPEED, + ) + ) + # ------------------------------------------------------------------- # SAVE VECTOR ------------------------------------------------------- - # ------------------------------------------------------------------- + # ------------------------------------------------------------------- if saveVector or saveRaster: - outputDir_zi = os.path.join(outputFilePath, - "z" + str(z_i).replace(".","_")) + outputDir_zi = os.path.join( + outputFilePath, "z" + str(z_i).replace(".", "_") + ) if not os.path.exists(outputDir_zi): os.mkdir(outputDir_zi) - outputVectorFile = saveTable(cursor = cursor, - tableName = horizOutputUrock[z_i], - filedir = os.path.join(outputDir_zi, - prefix(outputFilename, prefix_name)+\ - OUTPUT_VECTOR_EXTENSION), - delete = DELETE_OUTPUT_IF_EXISTS) - + outputVectorFile = saveTable( + cursor=cursor, + tableName=horizOutputUrock[z_i], + filedir=os.path.join( + outputDir_zi, + prefix(outputFilename, prefix_name) + + OUTPUT_VECTOR_EXTENSION, + ), + delete=DELETE_OUTPUT_IF_EXISTS, + ) + # ------------------------------------------------------------------- # SAVE RASTER ------------------------------------------------------- - # ------------------------------------------------------------------- + # ------------------------------------------------------------------- if saveRaster: # Save the all direction, horizontal and vertical wind speeds into a a different raster for var in [WIND_SPEED, HORIZ_WIND_SPEED, VERT_WIND_SPEED]: - saveRasterFile(cursor = cursor, - outputVectorFile = outputVectorFile, - outputFilePathAndNameBase = os.path.join(outputDir_zi, - prefix(outputFilename, prefix_name)), - horizOutputUrock = horizOutputUrock, - outputRaster = outputRaster, - z_i = z_i, - meshSize = meshSize, - var2save = var, - stacked_blocks = stacked_blocks, - srid = srid, - tmp_dir = tmp_dir) + saveRasterFile( + cursor=cursor, + outputVectorFile=outputVectorFile, + outputFilePathAndNameBase=os.path.join( + outputDir_zi, prefix(outputFilename, prefix_name) + ), + horizOutputUrock=horizOutputUrock, + outputRaster=outputRaster, + z_i=z_i, + meshSize=meshSize, + var2save=var, + stacked_blocks=stacked_blocks, + srid=srid, + tmp_dir=tmp_dir, + ) return horizOutputUrock, final_netcdf_path - -def saveToNetCDF(longitude, - latitude, - x, - y, - u, - v, - w, - verticalWindProfile, - path, - urock_srid, - horizontal_res, - vertical_res): + + +def saveToNetCDF( + longitude, + latitude, + x, + y, + u, + v, + w, + verticalWindProfile, + path, + urock_srid, + horizontal_res, + vertical_res, +): """ - Create a netCDF file and save wind speed, direction and initial + Create a netCDF file and save wind speed, direction and initial vertical wind profile in it (based on https://pyhogs.github.io/intro_netcdf4.html ) - + Parameters - _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ longitude: np.array (2D - X, Y) Longitude of each of the (X, Y) points latitude: np.array (2D - X, Y) @@ -213,98 +297,110 @@ def saveToNetCDF(longitude, Path and filename to save NetCDF file urock_srid: int EPSG code initially used for the URock calculations - + Returns ------- String being the path, filename and extension of the netCdf file where are stored the results - """ + """ # Opens a netCDF file in writing mode ('w') - f = nc4.Dataset(path + OUTPUT_NETCDF_EXTENSION,'w', format='NETCDF4') - + f = nc4.Dataset(path + OUTPUT_NETCDF_EXTENSION, "w", format="NETCDF4") + # 3D WIND SPEED DATA # Creates a group within this file for the 3D wind speed wind3dGrp = f.createGroup(WIND_GROUP) - + # Creates dimensions within this group - wind3dGrp.createDimension('rlon', len(x)) - wind3dGrp.createDimension('rlat', len(y)) - wind3dGrp.createDimension('z', verticalWindProfile.index.size) - + wind3dGrp.createDimension("rlon", len(x)) + wind3dGrp.createDimension("rlat", len(y)) + wind3dGrp.createDimension("z", verticalWindProfile.index.size) + # Build the variables - rlon = wind3dGrp.createVariable(RLON, 'i4', 'rlon') - rlat = wind3dGrp.createVariable(RLAT, 'i4', 'rlat') - z = wind3dGrp.createVariable(Z, 'f4', 'z') - lon = wind3dGrp.createVariable(LON, 'f8', ('rlon', 'rlat')) - lat = wind3dGrp.createVariable(LAT, 'f8', ('rlon', 'rlat')) - windSpeed_x = wind3dGrp.createVariable(WINDSPEED_X, 'f4', ('rlon', 'rlat', 'z')) - windSpeed_y = wind3dGrp.createVariable(WINDSPEED_Y, 'f4', ('rlon', 'rlat', 'z')) - windSpeed_z = wind3dGrp.createVariable(WINDSPEED_Z, 'f4', ('rlon', 'rlat', 'z')) - + rlon = wind3dGrp.createVariable(RLON, "i4", "rlon") + rlat = wind3dGrp.createVariable(RLAT, "i4", "rlat") + z = wind3dGrp.createVariable(Z, "f4", "z") + lon = wind3dGrp.createVariable(LON, "f8", ("rlon", "rlat")) + lat = wind3dGrp.createVariable(LAT, "f8", ("rlon", "rlat")) + windSpeed_x = wind3dGrp.createVariable( + WINDSPEED_X, "f4", ("rlon", "rlat", "z") + ) + windSpeed_y = wind3dGrp.createVariable( + WINDSPEED_Y, "f4", ("rlon", "rlat", "z") + ) + windSpeed_z = wind3dGrp.createVariable( + WINDSPEED_Z, "f4", ("rlon", "rlat", "z") + ) + # Fill the variables rlon[:] = x rlat[:] = y z[:] = verticalWindProfile[Z].values - lon[:,:] = longitude - lat[:,:] = latitude - windSpeed_x[:,:,:] = u - windSpeed_y[:,:,:] = v - windSpeed_z[:,:,:] = w - + lon[:, :] = longitude + lat[:, :] = latitude + windSpeed_x[:, :, :] = u + windSpeed_y[:, :, :] = v + windSpeed_z[:, :, :] = w + # VERTICAL WIND PROFILE DATA # Creates a group within this file for the vertical wind profile vertWindProfGrp = f.createGroup(VERT_WIND) - + # Creates dimensions within this group - vertWindProfGrp.createDimension('z', verticalWindProfile.index.size) - - # Build the variables - z_profile = vertWindProfGrp.createVariable(Z, 'f4', 'z') - WindSpeed = vertWindProfGrp.createVariable(WINDSPEED_PROFILE, 'f4', 'z') - + vertWindProfGrp.createDimension("z", verticalWindProfile.index.size) + + # Build the variables + z_profile = vertWindProfGrp.createVariable(Z, "f4", "z") + WindSpeed = vertWindProfGrp.createVariable(WINDSPEED_PROFILE, "f4", "z") + # Fill the variables z_profile[:] = verticalWindProfile[Z].values WindSpeed[:] = verticalWindProfile[HORIZ_WIND_SPEED].values - - + # ADD METADATA - #Add local attributes to variable instances - lon.units = 'degrees east' - lat.units = 'degrees north' - windSpeed_x.units = 'meter per second' - windSpeed_y.units = 'meter per second' - windSpeed_z.units = 'meter per second' - z.units = 'meters' - WindSpeed.units = 'meter per second' - z_profile.units = 'meters' - - #Add global attributes + # Add local attributes to variable instances + lon.units = "degrees east" + lat.units = "degrees north" + windSpeed_x.units = "meter per second" + windSpeed_y.units = "meter per second" + windSpeed_z.units = "meter per second" + z.units = "meters" + WindSpeed.units = "meter per second" + z_profile.units = "meters" + + # Add global attributes f.description = "URock dataset containing one group of 3D wind field value and one group of input vertical wind speed profile" f.history = "Created " + datetime.today().strftime("%y-%m-%d") - + # Add the srid (epsg code) used for the URock processing calculation f.urock_srid = urock_srid - + # Add horizontal and vertical resolution into the metadata f.horizontal_res = horizontal_res f.vertical_res = vertical_res - + f.close() - + return path + OUTPUT_NETCDF_EXTENSION - -def saveTable(cursor, tableName, filedir, delete = False, - rotationCenterCoordinates = None, rotateAngle = None): - """ Save a table in .geojson or .shp (the table can be rotated before saving if needed). - + + +def saveTable( + cursor, + tableName, + filedir, + delete=False, + rotationCenterCoordinates=None, + rotateAngle=None, +): + """Save a table in .geojson or .shp (the table can be rotated before saving if needed). + Parameters - _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries - tableName : String - Name of the table to save + tableName : String + Name of the table to save filedir: String - Directory (including filename and extension) of the file where to + Directory (including filename and extension) of the file where to store the table delete: Boolean, default False Whether or not the file is delete if exist @@ -313,24 +409,26 @@ def saveTable(cursor, tableName, filedir, delete = False, rotateAngle: float, default None Counter clock-wise rotation angle (in degree) - + Returns - _ _ _ _ _ _ _ _ _ _ - output_filedir: String + _ _ _ _ _ _ _ _ _ _ + output_filedir: String Directory (including filename and extension) of the saved file - (could be different from input 'filedir' since the file may + (could be different from input 'filedir' since the file may have been renamed if exists)""" # Rotate the table if needed if rotationCenterCoordinates is not None and rotateAngle is not None: - tableName = windRotation(cursor = cursor, - dicOfInputTables = {tableName: tableName}, - rotateAngle = rotateAngle, - rotationCenterCoordinates = rotationCenterCoordinates)[0][tableName] - + tableName = windRotation( + cursor=cursor, + dicOfInputTables={tableName: tableName}, + rotateAngle=rotateAngle, + rotationCenterCoordinates=rotationCenterCoordinates, + )[0][tableName] + # Get extension extension = "." + filedir.split(".")[-1] filedirWithoutExt = ".".join(filedir.split(".")[0:-1]) - + # Define the H2GIS function depending on extension if extension.upper() == ".GEOJSON": h2_function = "GEOJSONWRITE" @@ -345,130 +443,172 @@ def saveTable(cursor, tableName, filedir, delete = False, output_filedir = filedir os.remove(filedir) if extension.upper() == ".SHP": - os.remove(filedirWithoutExt+".dbf") - os.remove(filedirWithoutExt+".shx") - if os.path.isfile(filedirWithoutExt+".prj"): - os.remove(filedirWithoutExt+".prj") + os.remove(filedirWithoutExt + ".dbf") + os.remove(filedirWithoutExt + ".shx") + if os.path.isfile(filedirWithoutExt + ".prj"): + os.remove(filedirWithoutExt + ".prj") # If delete = False, add a suffix to the file elif os.path.isfile(filedir): - output_filedir = renameFileIfExists(filedir = filedirWithoutExt, - extension = extension) + extension + output_filedir = ( + renameFileIfExists(filedir=filedirWithoutExt, extension=extension) + + extension + ) else: output_filedir = filedir # Write files - cursor.execute(safe("""CALL {0}('{1}','{2}')""").format(h2_function, - output_filedir, - tableName)) + cursor.execute( + safe("""CALL {0}('{1}','{2}')""").format( + h2_function, output_filedir, tableName + ) + ) return output_filedir + def renameFileIfExists(filedir, extension): - """ Rename a file with a numbering prefix if exists. - + """Rename a file with a numbering prefix if exists. + Parameters - _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ filedir: String Directory (including filename but without extension) of the file - + Returns - _ _ _ _ _ _ _ _ _ _ - newFileDir: String + _ _ _ _ _ _ _ _ _ _ + newFileDir: String Directory with renamed file""" i = 1 newFileDir = filedir - while(os.path.isfile(newFileDir + extension)): + while os.path.isfile(newFileDir + extension): newFileDir = filedir + "({0})".format(i) i += 1 return newFileDir -def saveRasterFile(cursor, outputVectorFile, outputFilePathAndNameBase, - horizOutputUrock, outputRaster, z_i, meshSize, var2save, - stacked_blocks, srid, tmp_dir): - """ Save results in a raster file. - +def saveRasterFile( + cursor, + outputVectorFile, + outputFilePathAndNameBase, + horizOutputUrock, + outputRaster, + z_i, + meshSize, + var2save, + stacked_blocks, + srid, + tmp_dir, +): + """Save results in a raster file. + Parameters - _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ outputFilePathAndNameBase: String Directory (including filename but without extension) of the file - + Returns - _ _ _ _ _ _ _ _ _ _ - None""" + _ _ _ _ _ _ _ _ _ _ + None""" # Define output path name outputFilePathAndNameBaseRaster = outputFilePathAndNameBase + var2save # If delete = False, add a suffix to the filename - if (os.path.isfile(outputFilePathAndNameBaseRaster + OUTPUT_RASTER_EXTENSION)) \ - and (not DELETE_OUTPUT_IF_EXISTS): - outputFilePathAndNameBaseRaster = renameFileIfExists(filedir = outputFilePathAndNameBaseRaster, - extension = OUTPUT_RASTER_EXTENSION) - + if ( + os.path.isfile( + outputFilePathAndNameBaseRaster + OUTPUT_RASTER_EXTENSION + ) + ) and (not DELETE_OUTPUT_IF_EXISTS): + outputFilePathAndNameBaseRaster = renameFileIfExists( + filedir=outputFilePathAndNameBaseRaster, + extension=OUTPUT_RASTER_EXTENSION, + ) + # Whether or not a raster output is given as input, the rasterization process is slightly different if outputRaster: outputRasterExtent = outputRaster.extent() - resX = (outputRasterExtent.xMaximum() - outputRasterExtent.xMinimum()) / outputRaster.width() - resY = (outputRasterExtent.yMaximum() - outputRasterExtent.yMinimum()) / outputRaster.height() + resX = ( + outputRasterExtent.xMaximum() - outputRasterExtent.xMinimum() + ) / outputRaster.width() + resY = ( + outputRasterExtent.yMaximum() - outputRasterExtent.yMinimum() + ) / outputRaster.height() xmin = outputRasterExtent.xMinimum() ymax = outputRasterExtent.yMaximum() xmax = outputRasterExtent.xMaximum() ymin = outputRasterExtent.yMinimum() - tmp_file = os.path.join(tmp_dir, f"interp_before_fillna_{var2save}.tif") + tmp_file = os.path.join( + tmp_dir, f"interp_before_fillna_{var2save}.tif" + ) # If a single output raster cell contains more than 4 points, average instead of interpolate if resX * resY > 4 * meshSize**2: - Grid(destName = tmp_file, - srcDS = outputVectorFile, - options = GridOptions(format = OUTPUT_RASTER_EXTENSION.split(".")[-1], - zfield = var2save, - width = outputRaster.width(), - height = outputRaster.height(), - outputBounds = [xmin, - ymax, - xmax, - ymin], - algorithm = "average:radius1={0}:radius2={0}".format(1.1*meshSize))) + Grid( + destName=tmp_file, + srcDS=outputVectorFile, + options=GridOptions( + format=OUTPUT_RASTER_EXTENSION.split(".")[-1], + zfield=var2save, + width=outputRaster.width(), + height=outputRaster.height(), + outputBounds=[xmin, ymax, xmax, ymin], + algorithm="average:radius1={0}:radius2={0}".format( + 1.1 * meshSize + ), + ), + ) else: # Interpolate with building constraints - interp_vec_to_rast(outputVectorFile = outputVectorFile, - stacked_blocks = stacked_blocks, - outputFilePathAndNameBaseRaster = ".".join(tmp_file.split(".")[0:-1]), - extent = f'{xmin},{xmax},{ymin},{ymax} [EPSG:{srid}]', - resX = resX, - resY = resY, - z_i = z_i, - colname = var2save, - tmp_dir = tmp_dir) - + interp_vec_to_rast( + outputVectorFile=outputVectorFile, + stacked_blocks=stacked_blocks, + outputFilePathAndNameBaseRaster=".".join( + tmp_file.split(".")[0:-1] + ), + extent=f"{xmin},{xmax},{ymin},{ymax} [EPSG:{srid}]", + resX=resX, + resY=resY, + z_i=z_i, + colname=var2save, + tmp_dir=tmp_dir, + ) + # Interpolate values to fill no data - ds = Open(tmp_file) + ds = Open(tmp_file) driver = GetDriverByName("GTiff") - output_ds = driver.CreateCopy(outputFilePathAndNameBaseRaster + OUTPUT_RASTER_EXTENSION, - ds) + output_ds = driver.CreateCopy( + outputFilePathAndNameBaseRaster + OUTPUT_RASTER_EXTENSION, ds + ) band = output_ds.GetRasterBand(1) - _ = FillNodata(targetBand = band, maskBand = None, - maxSearchDist = 9999, smoothingIterations = 0) - + _ = FillNodata( + targetBand=band, + maskBand=None, + maxSearchDist=9999, + smoothingIterations=0, + ) + # Set the srid - vec_srid = gpd.read_file(outputVectorFile, rows = slice(0,)).crs.to_epsg() + vec_srid = gpd.read_file( + outputVectorFile, + rows=slice( + 0, + ), + ).crs.to_epsg() srs = SpatialReference() srs.ImportFromEPSG(vec_srid) output_ds.SetProjection(srs.ExportToWkt()) - + # Release the datasets. ds = ds.FlushCache() output_ds = output_ds.FlushCache() - - # ET = Open(outputFilePathAndNameBaseRaster + OUTPUT_RASTER_EXTENSION, GA_Update) + + # ET = Open(outputFilePathAndNameBaseRaster + OUTPUT_RASTER_EXTENSION, GA_Update) # ETband = ET.GetRasterBand(1) - - # FillNodata(targetBand = ETband, maskBand = None, + + # FillNodata(targetBand = ETband, maskBand = None, # maxSearchDist = 999, smoothingIterations = 0) # ET = None else: - cursor.execute( - safe(""" + cursor.execute(safe(""" SELECT ST_XMIN({0}) AS XMIN, ST_XMAX({0}) AS XMAX, ST_YMIN({0}) AS YMIN, ST_YMAX({0}) AS YMAX FROM (SELECT ST_ACCUM({0}) AS {0} FROM {1}) - """).format(GEOM_FIELD , horizOutputUrock[z_i])) + """).format(GEOM_FIELD, horizOutputUrock[z_i])) vectorBounds = cursor.fetchall()[0] width = int((vectorBounds[1] - vectorBounds[0]) / meshSize) + 1 height = int((vectorBounds[3] - vectorBounds[2]) / meshSize) + 1 @@ -476,56 +616,68 @@ def saveRasterFile(cursor, outputVectorFile, outputFilePathAndNameBase, ymax = vectorBounds[3] + float(meshSize) / 2 xmax = vectorBounds[0] + meshSize * (width - 0.5) ymin = vectorBounds[3] - meshSize * (height + 0.5) - + # Interpolate with building constraints - interp_vec_to_rast(outputVectorFile = outputVectorFile, - stacked_blocks = stacked_blocks, - outputFilePathAndNameBaseRaster = outputFilePathAndNameBaseRaster, - extent = f'{xmin},{xmax},{ymin},{ymax} [EPSG:{srid}]', - resX = meshSize, - resY = meshSize, - z_i = z_i, - colname = var2save, - tmp_dir = tmp_dir) - -def interp_vec_to_rast(outputVectorFile, stacked_blocks, outputFilePathAndNameBaseRaster, - extent, resX, resY, z_i, colname, tmp_dir): - """ Interpolate and save wind data saved in a vector to a raster. - - Parameters - _ _ _ _ _ _ _ _ _ _ - outputVectorFile: String - Directory (including filename and extension) of the file containing the - wind field to interpolate from vector to raster - stacked_blocks: String - Directory (including filename only - no extension) of the file containing stacked blocks - (building footprint + where it starts and ends vertically) - outputFilePathAndNameBaseRaster: String - Directory (including filename and extension) of the file that will - be used to save the results - extent: String - QGIS extent in the shape '{xmin},{xmax},{ymin},{ymax} [EPSG:epsgcode]' - resX: float - Resolution of the output raster along the X axis - resY: float - Resolution of the output raster along the Y axis - z_i: float - Height of the output wind field - colname: string - Name of the attribute column in teh vector table to convert to raster - tmp_dir: String - Path of the directory used for saving temporary results (should be unique) - - - Returns - _ _ _ _ _ _ _ _ _ _ - output_file_path: String - Path and name of the file containing the resulting raster""" + interp_vec_to_rast( + outputVectorFile=outputVectorFile, + stacked_blocks=stacked_blocks, + outputFilePathAndNameBaseRaster=outputFilePathAndNameBaseRaster, + extent=f"{xmin},{xmax},{ymin},{ymax} [EPSG:{srid}]", + resX=meshSize, + resY=meshSize, + z_i=z_i, + colname=var2save, + tmp_dir=tmp_dir, + ) + + +def interp_vec_to_rast( + outputVectorFile, + stacked_blocks, + outputFilePathAndNameBaseRaster, + extent, + resX, + resY, + z_i, + colname, + tmp_dir, +): + """Interpolate and save wind data saved in a vector to a raster. + + Parameters + _ _ _ _ _ _ _ _ _ _ + outputVectorFile: String + Directory (including filename and extension) of the file containing the + wind field to interpolate from vector to raster + stacked_blocks: String + Directory (including filename only - no extension) of the file containing stacked blocks + (building footprint + where it starts and ends vertically) + outputFilePathAndNameBaseRaster: String + Directory (including filename and extension) of the file that will + be used to save the results + extent: String + QGIS extent in the shape '{xmin},{xmax},{ymin},{ymax} [EPSG:epsgcode]' + resX: float + Resolution of the output raster along the X axis + resY: float + Resolution of the output raster along the Y axis + z_i: float + Height of the output wind field + colname: string + Name of the attribute column in teh vector table to convert to raster + tmp_dir: String + Path of the directory used for saving temporary results (should be unique) + + + Returns + _ _ _ _ _ _ _ _ _ _ + output_file_path: String + Path and name of the file containing the resulting raster""" # gdf = gpd.read_file(outputVectorFile) - + # coordinates = np.array([point.coords[0] for point in gdf.geometry]) # [x, y] # values = gdf[colname].values # The field 'V' to interpolate - + # # Define the raster grid (3m resolution) # resolution = min([resX, resY]) # xmin, ymin, xmax, ymax = gdf.total_bounds @@ -533,100 +685,164 @@ def interp_vec_to_rast(outputVectorFile, stacked_blocks, outputFilePathAndNameBa # ymin -= resolution / 2 # xmax -= resolution / 2 # ymax += resolution / 2 - + # # Create a grid of coordinates for interpolation # x_grid = np.arange(xmin, xmax, resolution) # y_grid = np.arange(ymin, ymax, resolution) # x_grid, y_grid = np.meshgrid(x_grid, y_grid) - + # # Perform bilinear interpolation on the grid # grid_values = griddata(coordinates, values, (x_grid, y_grid), method='linear') # grid_values = grid_values[::-1, :] - + # # Define raster metadata # transform = from_origin(xmin, ymax, resolution, resolution) # origin is top-left - + # # Save the interpolated grid to a raster file # interp_out = os.path.join(TEMPO_DIRECTORY,"interp_out.tif") - # with rasterio.open(interp_out, 'w', driver='GTiff', + # with rasterio.open(interp_out, 'w', driver='GTiff', # height=grid_values.shape[0], width=grid_values.shape[1], # count=1, dtype=grid_values.dtype, crs=gdf.crs, transform=transform) as dst: # dst.write(grid_values, 1) - + # Change the order of the points to make the TIN interpolation faster and working for all conditions - order_changed = processing.run("native:orderbyexpression", - {'INPUT':outputVectorFile, - 'EXPRESSION':'randf(0,1)', - 'ASCENDING':False, - 'NULLS_FIRST':False, - 'OUTPUT':os.path.join(tmp_dir, - f"order_changed_{colname}")})["OUTPUT"] - + order_changed = processing.run( + "native:orderbyexpression", + { + "INPUT": outputVectorFile, + "EXPRESSION": "randf(0,1)", + "ASCENDING": False, + "NULLS_FIRST": False, + "OUTPUT": os.path.join(tmp_dir, f"order_changed_{colname}"), + }, + )["OUTPUT"] + # Get the column number corresponding to the column name - colnb = gpd.read_file(order_changed, rows = slice(0,)).columns.get_loc(colname) + 1 - + colnb = ( + gpd.read_file( + order_changed, + rows=slice( + 0, + ), + ).columns.get_loc(colname) + + 1 + ) + # Interpolate the results without constraints - interp_out = processing.run("qgis:tininterpolation", - {'INTERPOLATION_DATA':f'{order_changed}::~::0::~::{colnb}::~::0', - 'METHOD':0, - 'EXTENT':extent, - 'PIXEL_SIZE':min(resX, resY), - 'OUTPUT':os.path.join(tmp_dir, - f"interp_out_{colname}.tif")})["OUTPUT"] - + interp_out = processing.run( + "qgis:tininterpolation", + { + "INTERPOLATION_DATA": f"{order_changed}::~::0::~::{colnb}::~::0", + "METHOD": 0, + "EXTENT": extent, + "PIXEL_SIZE": min(resX, resY), + "OUTPUT": os.path.join(tmp_dir, f"interp_out_{colname}.tif"), + }, + )["OUTPUT"] + # If there are buildings in the study area, need to set wind speed = 0 if os.stat(stacked_blocks).st_size > 0: - # Rasterize the stacked blocks keeping the value of each stacked block base - block_base = processing.run("gdal:rasterize", - {'INPUT':stacked_blocks, - 'FIELD':BASE_HEIGHT_FIELD,'BURN':0,'USE_Z':False, - 'UNITS':1,'WIDTH':resX,'HEIGHT':resY, - 'EXTENT':extent, - 'NODATA':None,'OPTIONS':'','DATA_TYPE':5, - 'INIT':-9999,'INVERT':False,'EXTRA':'', - 'OUTPUT':os.path.join(tmp_dir, - f"block_base_{colname}.tif")})["OUTPUT"] - - # Rasterize the stacked blocks keeping the value of each stacked block top - block_top = processing.run("gdal:rasterize", - {'INPUT':stacked_blocks, - 'FIELD':HEIGHT_FIELD,'BURN':0,'USE_Z':False, - 'UNITS':1,'WIDTH':resX,'HEIGHT':resY, - 'EXTENT':extent, - 'NODATA':None,'OPTIONS':'','DATA_TYPE':5, - 'INIT':-9999,'INVERT':False,'EXTRA':'', - 'OUTPUT':os.path.join(tmp_dir, - f"block_top_{colname}.tif")})["OUTPUT"] - + # Rasterize the stacked blocks keeping the value of each stacked block base + block_base = processing.run( + "gdal:rasterize", + { + "INPUT": stacked_blocks, + "FIELD": BASE_HEIGHT_FIELD, + "BURN": 0, + "USE_Z": False, + "UNITS": 1, + "WIDTH": resX, + "HEIGHT": resY, + "EXTENT": extent, + "NODATA": None, + "OPTIONS": "", + "DATA_TYPE": 5, + "INIT": -9999, + "INVERT": False, + "EXTRA": "", + "OUTPUT": os.path.join(tmp_dir, f"block_base_{colname}.tif"), + }, + )["OUTPUT"] + + # Rasterize the stacked blocks keeping the value of each stacked block top + block_top = processing.run( + "gdal:rasterize", + { + "INPUT": stacked_blocks, + "FIELD": HEIGHT_FIELD, + "BURN": 0, + "USE_Z": False, + "UNITS": 1, + "WIDTH": resX, + "HEIGHT": resY, + "EXTENT": extent, + "NODATA": None, + "OPTIONS": "", + "DATA_TYPE": 5, + "INIT": -9999, + "INVERT": False, + "EXTRA": "", + "OUTPUT": os.path.join(tmp_dir, f"block_top_{colname}.tif"), + }, + )["OUTPUT"] + # Keep the values only when there is no building at this position - output_file_path = processing.run("gdal:rastercalculator", - {'INPUT_A':block_base,'BAND_A':1, - 'INPUT_B':block_top,'BAND_B':1, - 'INPUT_C':interp_out,'BAND_C':1, - 'INPUT_D':None,'BAND_D':None, - 'INPUT_E':None,'BAND_E':None, - 'INPUT_F':None,'BAND_F':None, - 'FORMULA':f'((A == -9999) + (A < {z_i})) * ((B == -9999) + (B < {z_i})) * C', - 'NO_DATA':None,'EXTENT_OPT':0,'PROJWIN':None, - 'RTYPE':5,'OPTIONS':'','EXTRA':'', - 'OUTPUT':outputFilePathAndNameBaseRaster + OUTPUT_RASTER_EXTENSION})["OUTPUT"] + output_file_path = processing.run( + "gdal:rastercalculator", + { + "INPUT_A": block_base, + "BAND_A": 1, + "INPUT_B": block_top, + "BAND_B": 1, + "INPUT_C": interp_out, + "BAND_C": 1, + "INPUT_D": None, + "BAND_D": None, + "INPUT_E": None, + "BAND_E": None, + "INPUT_F": None, + "BAND_F": None, + "FORMULA": ( + f"((A == -9999) + (A < {z_i})) * ((B == -9999) + (B < {z_i})) * C" + ), + "NO_DATA": None, + "EXTENT_OPT": 0, + "PROJWIN": None, + "RTYPE": 5, + "OPTIONS": "", + "EXTRA": "", + "OUTPUT": ( + outputFilePathAndNameBaseRaster + OUTPUT_RASTER_EXTENSION + ), + }, + )["OUTPUT"] # Else directly save the result of the interpolation else: - output_file_path = outputFilePathAndNameBaseRaster + OUTPUT_RASTER_EXTENSION - shutil.copy2(src = interp_out, dst = output_file_path) - + output_file_path = ( + outputFilePathAndNameBaseRaster + OUTPUT_RASTER_EXTENSION + ) + shutil.copy2(src=interp_out, dst=output_file_path) + return output_file_path - -def saveRockleZones(cursor, outputDataAbs, dicOfBuildZoneGridPoint, dicOfVegZoneGridPoint, - gridPoint, rotationCenterCoordinates, windDirection): - """ Save the 2D Röckle zones (building and vegetation) as points. - + + +def saveRockleZones( + cursor, + outputDataAbs, + dicOfBuildZoneGridPoint, + dicOfVegZoneGridPoint, + gridPoint, + rotationCenterCoordinates, + windDirection, +): + """Save the 2D Röckle zones (building and vegetation) as points. + Parameters - _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ cursor: conn.cursor A cursor object, used to perform spatial SQL queries - outputDataAbs : Dictionary - Object containing the absolute path where should be saved the Röckle points + outputDataAbs : Dictionary + Object containing the absolute path where should be saved the Röckle points dicOfBuildZoneGridPoint: Dictionary Dictionary containing all building table names to be saved dicOfVegZoneGridPoint: Dictionary @@ -637,17 +853,18 @@ def saveRockleZones(cursor, outputDataAbs, dicOfBuildZoneGridPoint, dicOfVegZone x and y values of the point used as center of rotation windDirection: float, default None Wind direction used for calculation (° clock-wise from North) - - + + Returns - _ _ _ _ _ _ _ _ _ _ - None""" + _ _ _ _ _ _ _ _ _ _ + None""" # Creates a folder if not exist if not os.path.exists(outputDataAbs["point_2DRockleZone"]): os.mkdir(outputDataAbs["point_2DRockleZone"]) # Save Building Röckle zones for t in dicOfBuildZoneGridPoint: - cursor.execute(safe(""" + cursor.execute( + safe(""" DROP TABLE IF EXISTS point_Buildzone_{0}; {5}; {6}; @@ -656,26 +873,42 @@ def saveRockleZones(cursor, outputDataAbs, dicOfBuildZoneGridPoint, dicOfVegZone FROM {3} AS a RIGHT JOIN {4} AS b ON a.{1} = b.{1} WHERE b.{1} IS NOT NULL - """).format( t , ID_POINT, - GEOM_FIELD , gridPoint, - dicOfBuildZoneGridPoint[t] , createIndex(tableName=gridPoint, - fieldName=ID_POINT, - isSpatial=False), - createIndex(tableName=dicOfBuildZoneGridPoint[t], - fieldName=ID_POINT, - isSpatial=False))) - saveTable(cursor = cursor, - tableName = "point_Buildzone_"+t, - filedir = os.path.join(outputDataAbs["point_2DRockleZone"], t+".geojson"), - delete = True, - rotationCenterCoordinates = rotationCenterCoordinates, - rotateAngle = - windDirection) - + """).format( + t, + ID_POINT, + GEOM_FIELD, + gridPoint, + dicOfBuildZoneGridPoint[t], + createIndex( + tableName=gridPoint, fieldName=ID_POINT, isSpatial=False + ), + createIndex( + tableName=dicOfBuildZoneGridPoint[t], + fieldName=ID_POINT, + isSpatial=False, + ), + ) + ) + saveTable( + cursor=cursor, + tableName="point_Buildzone_" + t, + filedir=os.path.join( + outputDataAbs["point_2DRockleZone"], t + ".geojson" + ), + delete=True, + rotationCenterCoordinates=rotationCenterCoordinates, + rotateAngle=-windDirection, + ) + # Save vegetation Röckle zones for t in dicOfVegZoneGridPoint: - saveTable(cursor = cursor, - tableName = dicOfVegZoneGridPoint[t], - filedir = os.path.join(outputDataAbs["point_2DRockleZone"], t+".geojson"), - delete = True, - rotationCenterCoordinates = rotationCenterCoordinates, - rotateAngle = - windDirection) \ No newline at end of file + saveTable( + cursor=cursor, + tableName=dicOfVegZoneGridPoint[t], + filedir=os.path.join( + outputDataAbs["point_2DRockleZone"], t + ".geojson" + ), + delete=True, + rotationCenterCoordinates=rotationCenterCoordinates, + rotateAngle=-windDirection, + ) diff --git a/functions/URock/urock_analyser_functions.py b/functions/URock/urock_analyser_functions.py index d5db312..c863691 100644 --- a/functions/URock/urock_analyser_functions.py +++ b/functions/URock/urock_analyser_functions.py @@ -22,9 +22,21 @@ from .loadData import loadFile from . import DataUtil from .DataUtil import safe -from .GlobalVariables import WIND_GROUP, TEMPO_DIRECTORY,\ - RLON, RLAT, GEOM_FIELD, LON , LAT, WINDSPEED_X, WINDSPEED_Y, WINDSPEED_Z,\ - Z, HORIZ_WIND_SPEED, WIND_SPEED +from .GlobalVariables import ( + WIND_GROUP, + TEMPO_DIRECTORY, + RLON, + RLAT, + GEOM_FIELD, + LON, + LAT, + WINDSPEED_X, + WINDSPEED_Y, + WINDSPEED_Z, + Z, + HORIZ_WIND_SPEED, + WIND_SPEED, +) from .DataUtil import validate_sql_inputs idx = pd.IndexSlice @@ -37,15 +49,36 @@ WIDTH = 0.2 -def plotSectionalViews(pluginDirectory, inputWindFile, lines_file='', srid_lines=None, - idLines='', isStream = False, savePlot = False, - polygons_file='', srid_polygons=None, idPolygons='', - outputDirectory = None, simulationName = "", - fig = None, ax = None, scale = None, color = None, - feedback = None): +def plotSectionalViews( + pluginDirectory, + inputWindFile, + lines_file="", + srid_lines=None, + idLines="", + isStream=False, + savePlot=False, + polygons_file="", + srid_polygons=None, + idPolygons="", + outputDirectory=None, + simulationName="", + fig=None, + ax=None, + scale=None, + color=None, + feedback=None, +): # Validate inputs to prevent SQL injection - validate_sql_inputs(idLines, idPolygons, srid_lines, srid_polygons, None, lines_file, polygons_file) + validate_sql_inputs( + idLines, + idPolygons, + srid_lines, + srid_polygons, + None, + lines_file, + polygons_file, + ) if savePlot: plt.ioff() @@ -54,44 +87,51 @@ def plotSectionalViews(pluginDirectory, inputWindFile, lines_file='', srid_lines srid_all = srid_polygons else: srid_all = srid_lines - + # Create temporary table names (for tables that will be removed at the end of the process) allPointsTab = DataUtil.postfix("ALL_POINTS") linesTab = DataUtil.postfix("LINES") pointsIntersecTab = DataUtil.postfix("POINTS_INTERSECT") polygonsTab = DataUtil.postfix("POLYGONS") polygonsMeanTab = DataUtil.postfix("POLYGONS_MEAN") - + # Temporary files are declared pointsDir = os.path.join(TEMPO_DIRECTORY, "urock_allPoints.csv") outputPointsDir = os.path.join(TEMPO_DIRECTORY, "urock_selectedPoints.csv") - outputPolygonsDir = os.path.join(TEMPO_DIRECTORY, "urock_selectedPolygons.csv") - + outputPolygonsDir = os.path.join( + TEMPO_DIRECTORY, "urock_selectedPolygons.csv" + ) + if feedback: - feedback.setProgressText('Load NetCDF file in Python and save as csv file...') - + feedback.setProgressText( + "Load NetCDF file in Python and save as csv file..." + ) + # Load the group of the NetCDF file containing the wind speed field - ds = xr.open_dataset(inputWindFile, group = WIND_GROUP) - + ds = xr.open_dataset(inputWindFile, group=WIND_GROUP) + # Get the SRID that has been used in the URock processing calculation urock_srid = xr.open_dataset(inputWindFile).urock_srid - + # Validate urock_srid now that it's loaded - validate_sql_inputs(None, None, None, None, urock_srid, None, None) - + validate_sql_inputs(None, None, None, None, urock_srid, None, None) + # Send to a csv file - ds.to_dataframe().to_csv(pointsDir, index_label = ['rlat', 'rlon', 'zlev']) - + ds.to_dataframe().to_csv(pointsDir, index_label=["rlat", "rlon", "zlev"]) + if feedback: - feedback.setProgressText('Load csv file into H2GIS Database...') + feedback.setProgressText("Load csv file into H2GIS Database...") # Initialize an H2GIS database connection - dBDir = os.path.join(Path(pluginDirectory).parent, 'functions','URock') - cursor, conn, localH2InstanceDir = H2gisConnection.startH2gisInstance(dbDirectory = dBDir, - dbInstanceDir = TEMPO_DIRECTORY, - suffix = str(time.time()).replace(".", "_")) - + dBDir = os.path.join(Path(pluginDirectory).parent, "functions", "URock") + cursor, conn, localH2InstanceDir = H2gisConnection.startH2gisInstance( + dbDirectory=dBDir, + dbInstanceDir=TEMPO_DIRECTORY, + suffix=str(time.time()).replace(".", "_"), + ) + # Load coordinates in a H2GIS table - cursor.execute(safe(""" + cursor.execute( + safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0}(ID_POINT BIGINT AUTO_INCREMENT, {3} INTEGER, @@ -104,28 +144,42 @@ def plotSectionalViews(pluginDirectory, inputWindFile, lines_file='', srid_lines SELECT CAST((row_number() over()) as Integer) AS ID_POINT, {3}, {4}, {8}, {9}, {10}, {11}, ST_TRANSFORM(ST_SETSRID(ST_MakePoint({6}, {7}), 4326), {1}) AS {5} FROM CSVREAD('{2}') - """).format(allPointsTab , urock_srid, - pointsDir , RLON, - RLAT , GEOM_FIELD, - LON , LAT, - Z , WINDSPEED_X, - WINDSPEED_Y , WINDSPEED_Z)) - + """).format( + allPointsTab, + urock_srid, + pointsDir, + RLON, + RLAT, + GEOM_FIELD, + LON, + LAT, + Z, + WINDSPEED_X, + WINDSPEED_Y, + WINDSPEED_Z, + ) + ) + # DEAL WITH POLYGON (MEAN WIND PROFILES) fig_poly = None ax_poly = None if polygons_file and srid_polygons and idPolygons: if feedback: - feedback.setProgressText('Calculates average wind profile (within polygons)...') + feedback.setProgressText( + "Calculates average wind profile (within polygons)..." + ) # Load polygons - loadFile(cursor = cursor, - filePath = polygons_file, - tableName = polygonsTab, - srid = srid_polygons, - srid_repro = urock_srid) - + loadFile( + cursor=cursor, + filePath=polygons_file, + tableName=polygonsTab, + srid=srid_polygons, + srid_repro=urock_srid, + ) + # Calculates horizontal mean wind speed within each polygon - cursor.execute(safe(""" + cursor.execute( + safe(""" {0}{1}{2}{3} DROP TABLE IF EXISTS {4}; CREATE TABLE {4} @@ -141,80 +195,115 @@ def plotSectionalViews(pluginDirectory, inputWindFile, lines_file='', srid_lines a.{7} <> 0 AND a.{8} <> 0 AND a.{9} <> 0 GROUP BY a.{6}, b.{5}; CALL CSVWrite('{15}', 'SELECT * FROM {4}'); - """).format( DataUtil.createIndex(tableName=allPointsTab, - fieldName=GEOM_FIELD, - isSpatial=True), - DataUtil.createIndex(tableName=polygonsTab, - fieldName=GEOM_FIELD, - isSpatial=True), - DataUtil.createIndex(tableName=allPointsTab, - fieldName=Z, - isSpatial=False), - DataUtil.createIndex(tableName=polygonsTab, - fieldName=idPolygons, - isSpatial=False), - polygonsMeanTab , idPolygons, - Z , WINDSPEED_X, - WINDSPEED_Y , WINDSPEED_Z, - HORIZ_WIND_SPEED , WIND_SPEED, - allPointsTab , polygonsTab, - GEOM_FIELD , outputPolygonsDir)) - + """).format( + DataUtil.createIndex( + tableName=allPointsTab, + fieldName=GEOM_FIELD, + isSpatial=True, + ), + DataUtil.createIndex( + tableName=polygonsTab, fieldName=GEOM_FIELD, isSpatial=True + ), + DataUtil.createIndex( + tableName=allPointsTab, fieldName=Z, isSpatial=False + ), + DataUtil.createIndex( + tableName=polygonsTab, + fieldName=idPolygons, + isSpatial=False, + ), + polygonsMeanTab, + idPolygons, + Z, + WINDSPEED_X, + WINDSPEED_Y, + WINDSPEED_Z, + HORIZ_WIND_SPEED, + WIND_SPEED, + allPointsTab, + polygonsTab, + GEOM_FIELD, + outputPolygonsDir, + ) + ) + # Back to dataframe for the plotting the mean wind speed for each level - df_selectedPolygons = pd.read_csv(outputPolygonsDir, - index_col = None, header = 0) - windList = pd.Series(["Wind speed along x-axis (m/s)", - "Wind speed along y-axis (m/s)", - "Wind speed along z-axis (m/s)", - "Horizontal wind speed (m/s)", - "Wind speed (m/s)"], - index = [WINDSPEED_X, WINDSPEED_Y, WINDSPEED_Z, - HORIZ_WIND_SPEED, WIND_SPEED]) + df_selectedPolygons = pd.read_csv( + outputPolygonsDir, index_col=None, header=0 + ) + windList = pd.Series( + [ + "Wind speed along x-axis (m/s)", + "Wind speed along y-axis (m/s)", + "Wind speed along z-axis (m/s)", + "Horizontal wind speed (m/s)", + "Wind speed (m/s)", + ], + index=[ + WINDSPEED_X, + WINDSPEED_Y, + WINDSPEED_Z, + HORIZ_WIND_SPEED, + WIND_SPEED, + ], + ) polygonsList = df_selectedPolygons[idPolygons.upper()].unique() fig_poly = {} ax_poly = {} for w in windList.index: fig_poly[w], ax_poly[w] = plt.subplots() for p in polygonsList: - data2plot = df_selectedPolygons[df_selectedPolygons[idPolygons.upper()] == p].sort_values(Z) - ax_poly[w].plot(data2plot[w.upper()], data2plot[Z], label = "Polygon {0}".format(p)) + data2plot = df_selectedPolygons[ + df_selectedPolygons[idPolygons.upper()] == p + ].sort_values(Z) + ax_poly[w].plot( + data2plot[w.upper()], + data2plot[Z], + label="Polygon {0}".format(p), + ) ax_poly[w].set_xlabel(windList[w]), ax_poly[w].set_ylabel("Height above ground (m)") plt.legend() if savePlot: - fig_poly[w].savefig(os.path.join(outputDirectory, - simulationName + "_" + w + ".png")) - - + fig_poly[w].savefig( + os.path.join( + outputDirectory, simulationName + "_" + w + ".png" + ) + ) + # DEAL WITH LINES (ALONG LINE VERTICAL PROFILES) fig = None ax = None scale = None if lines_file and srid_lines and idLines: if feedback: - feedback.setProgressText('Calculates vertical sectional plot (along lines)...') + feedback.setProgressText( + "Calculates vertical sectional plot (along lines)..." + ) # Get the resolution of the wind speed data cursor.execute(safe(""" SELECT ST_DISTANCE(a.{0}, b.{0}) AS dist FROM {1} AS a, {1} AS b WHERE a.{2} = 0 AND a.{3} = 0 AND b.{2} = 1 AND b.{3} = 0 - """).format(GEOM_FIELD , allPointsTab, - RLON , RLAT)) + """).format(GEOM_FIELD, allPointsTab, RLON, RLAT)) horiz_res = round(cursor.fetchall()[0][0]) - dist_max = horiz_res * (2 ** 0.5) / 2 - + dist_max = horiz_res * (2**0.5) / 2 + # Load lines - loadFile(cursor = cursor, - filePath = lines_file, - tableName = linesTab, - srid = srid_lines, - srid_repro = urock_srid) - + loadFile( + cursor=cursor, + filePath=lines_file, + tableName=linesTab, + srid=srid_lines, + srid_repro=urock_srid, + ) + # Calculates a buffer of about half the horizontal resolution of the # wind data around the lines and project the points contained in this buffer # on the lines and calculate the distance to this point to the begining of the line - # NOTE : ONLY LINES HAVING TWO POINTS ARE USED (SEGMENTS) - cursor.execute(safe(""" + # NOTE : ONLY LINES HAVING TWO POINTS ARE USED (SEGMENTS) + cursor.execute( + safe(""" {7}{8} DROP TABLE IF EXISTS {0}; CREATE TABLE {0} @@ -229,52 +318,111 @@ def plotSectionalViews(pluginDirectory, inputWindFile, lines_file='', srid_lines FROM {3} AS a, {4} AS b WHERE ST_NPOINTS(b.{1}) = 2 AND a.{1} && ST_EXPAND(b.{1}, {5}) AND ST_DWITHIN(a.{1}, b.{1}, {5}); CALL CSVWrite('{6}', 'SELECT * FROM {0}'); - """).format(pointsIntersecTab , GEOM_FIELD, - idLines , allPointsTab, - linesTab , dist_max, - outputPointsDir , DataUtil.createIndex(tableName=allPointsTab, - fieldName=GEOM_FIELD, - isSpatial=True), - DataUtil.createIndex(tableName=linesTab, - fieldName=GEOM_FIELD, - isSpatial=True), - Z , WINDSPEED_X, - WINDSPEED_Y , WINDSPEED_Z)) - + """).format( + pointsIntersecTab, + GEOM_FIELD, + idLines, + allPointsTab, + linesTab, + dist_max, + outputPointsDir, + DataUtil.createIndex( + tableName=allPointsTab, + fieldName=GEOM_FIELD, + isSpatial=True, + ), + DataUtil.createIndex( + tableName=linesTab, fieldName=GEOM_FIELD, isSpatial=True + ), + Z, + WINDSPEED_X, + WINDSPEED_Y, + WINDSPEED_Z, + ) + ) + # Back to dataframe for the plotting of the wind speed for each level - df_selectedPoints = pd.read_csv(outputPointsDir, index_col = None, header = 0) - df_selectedPoints["PROJECTED_HORIZ_WIND"] = \ - df_selectedPoints[WINDSPEED_X.upper()] * np.cos(df_selectedPoints["AZIMUTH"] - np.pi / 2) +\ - df_selectedPoints[WINDSPEED_Y.upper()] * np.cos(df_selectedPoints["AZIMUTH"]) - + df_selectedPoints = pd.read_csv( + outputPointsDir, index_col=None, header=0 + ) + df_selectedPoints["PROJECTED_HORIZ_WIND"] = df_selectedPoints[ + WINDSPEED_X.upper() + ] * np.cos( + df_selectedPoints["AZIMUTH"] - np.pi / 2 + ) + df_selectedPoints[ + WINDSPEED_Y.upper() + ] * np.cos( + df_selectedPoints["AZIMUTH"] + ) + # Where wind speed equal to 0, we assume it is buildings - buildIndexAll = df_selectedPoints[(df_selectedPoints[WINDSPEED_X.upper()]==0) &\ - (df_selectedPoints[WINDSPEED_Y.upper()]==0) &\ - (df_selectedPoints[WINDSPEED_Z.upper()]==0)].index - df_selectedPoints.loc[buildIndexAll, [WINDSPEED_X.upper(), WINDSPEED_Y.upper(), WINDSPEED_Z.upper()]] = np.nan - + buildIndexAll = df_selectedPoints[ + (df_selectedPoints[WINDSPEED_X.upper()] == 0) + & (df_selectedPoints[WINDSPEED_Y.upper()] == 0) + & (df_selectedPoints[WINDSPEED_Z.upper()] == 0) + ].index + df_selectedPoints.loc[ + buildIndexAll, + [WINDSPEED_X.upper(), WINDSPEED_Y.upper(), WINDSPEED_Z.upper()], + ] = np.nan + uniques_z = pd.unique(df_selectedPoints[Z.upper()]) # Need to reindex regularly values for stream plot if isStream: uniques_z[uniques_z == 0] = 0 - float(horiz_res) / 2 - df_selectedPoints[Z.upper()] = df_selectedPoints[Z.upper()].replace(0, 0 - float(horiz_res) / 2) - dic_all = {id_line : {zval : df_selectedPoints[(df_selectedPoints[idLines.upper()] == id_line) & - (df_selectedPoints[Z.upper()] == zval)].groupby("DIST").mean() - for zval in uniques_z} - for id_line in pd.unique(df_selectedPoints[idLines.upper()])} - dic_all = {id_line : {zval : conditional_interpolate(dic_all[id_line][zval].reindex(pd.Index(np.arange(0, - dic_all[id_line][zval].index.max(), - horiz_res))\ - .union(dic_all[id_line][zval].index)).sort_index(), - cols = [WINDSPEED_X.upper(), - WINDSPEED_Y.upper(), - WINDSPEED_Z.upper()], - limit = 2).reindex(pd.Index(np.arange(0, - dic_all[id_line][zval].index.max(), - horiz_res))) - for zval in dic_all[id_line]} - for id_line in dic_all} - + df_selectedPoints[Z.upper()] = df_selectedPoints[ + Z.upper() + ].replace(0, 0 - float(horiz_res) / 2) + dic_all = { + id_line: { + zval: ( + df_selectedPoints[ + (df_selectedPoints[idLines.upper()] == id_line) + & (df_selectedPoints[Z.upper()] == zval) + ] + .groupby("DIST") + .mean() + ) + for zval in uniques_z + } + for id_line in pd.unique(df_selectedPoints[idLines.upper()]) + } + dic_all = { + id_line: { + zval: ( + conditional_interpolate( + dic_all[id_line][zval] + .reindex( + pd.Index( + np.arange( + 0, + dic_all[id_line][zval].index.max(), + horiz_res, + ) + ).union(dic_all[id_line][zval].index) + ) + .sort_index(), + cols=[ + WINDSPEED_X.upper(), + WINDSPEED_Y.upper(), + WINDSPEED_Z.upper(), + ], + limit=2, + ).reindex( + pd.Index( + np.arange( + 0, + dic_all[id_line][zval].index.max(), + horiz_res, + ) + ) + ) + ) + for zval in dic_all[id_line] + } + for id_line in dic_all + } + if not fig and not ax: fig = {} ax = {} @@ -282,47 +430,109 @@ def plotSectionalViews(pluginDirectory, inputWindFile, lines_file='', srid_lines scale = {} for line in sorted(set(df_selectedPoints[idLines.upper()])): if not fig.get(line) and not ax.get(line): - fig[line], ax[line] = plt.subplots(figsize = (15,7)) - df_plot = df_selectedPoints[df_selectedPoints[idLines.upper()] == line].groupby([Z.upper(), "DIST"]).mean() - + fig[line], ax[line] = plt.subplots(figsize=(15, 7)) + df_plot = ( + df_selectedPoints[df_selectedPoints[idLines.upper()] == line] + .groupby([Z.upper(), "DIST"]) + .mean() + ) + if isStream: uniques_dist = dic_all[line][uniques_z[0]].index.unique() D = np.array([[d for d in uniques_dist] for z in uniques_z]) z = np.array([[z for d in uniques_dist] for z in uniques_z]) - wind_d = np.array([[dic_all[line][zval].loc[d, "PROJECTED_HORIZ_WIND"] for d in uniques_dist] for zval in uniques_z]) - wind_z = np.array([[dic_all[line][zval].loc[d, WINDSPEED_Z.upper()] for d in uniques_dist] for zval in uniques_z]) - ax[line].streamplot(D, z, wind_d, wind_z, density = STREAM_DENSITY, - color = color) + wind_d = np.array( + [ + [ + dic_all[line][zval].loc[d, "PROJECTED_HORIZ_WIND"] + for d in uniques_dist + ] + for zval in uniques_z + ] + ) + wind_z = np.array( + [ + [ + dic_all[line][zval].loc[d, WINDSPEED_Z.upper()] + for d in uniques_dist + ] + for zval in uniques_z + ] + ) + ax[line].streamplot( + D, z, wind_d, wind_z, density=STREAM_DENSITY, color=color + ) else: - uniques_dist = df_plot.index.unique(level = "DIST") + uniques_dist = df_plot.index.unique(level="DIST") D = np.array([[d for d in uniques_dist] for z in uniques_z]) z = np.array([[z for d in uniques_dist] for z in uniques_z]) - wind_d = np.array([[df_plot.loc[zval, d]["PROJECTED_HORIZ_WIND"] for d in uniques_dist] for zval in uniques_z]) - wind_z = np.array([[df_plot.loc[zval, d][WINDSPEED_Z.upper()] for d in uniques_dist] for zval in uniques_z]) - + wind_d = np.array( + [ + [ + df_plot.loc[zval, d]["PROJECTED_HORIZ_WIND"] + for d in uniques_dist + ] + for zval in uniques_z + ] + ) + wind_z = np.array( + [ + [ + df_plot.loc[zval, d][WINDSPEED_Z.upper()] + for d in uniques_dist + ] + for zval in uniques_z + ] + ) + if not scale.get(line): if np.max(np.abs(wind_d)) > 3 * np.median(np.abs(wind_d)): - scale[line] = np.max(np.abs(wind_d)) / (1.5 * horiz_res) + scale[line] = np.max(np.abs(wind_d)) / ( + 1.5 * horiz_res + ) else: - scale[line] = np.median(np.abs(wind_d)) / (1.5 * horiz_res) - Q = ax[line].quiver(D, z, wind_d, wind_z, - units = 'xy', scale = scale[line], - headwidth = HEAD_WIDTH, headlength = HEAD_LENGTH, - headaxislength = HEAD_AXIS_LENGTH, - width = WIDTH, color = color, - edgecolor = "k", linewidth = 0.2) - ax[line].quiverkey(Q, 0.9, 0.9, 1, r'$1 \frac{m}{s}$', labelpos='E', - coordinates='figure', color = color) - - + scale[line] = np.median(np.abs(wind_d)) / ( + 1.5 * horiz_res + ) + Q = ax[line].quiver( + D, + z, + wind_d, + wind_z, + units="xy", + scale=scale[line], + headwidth=HEAD_WIDTH, + headlength=HEAD_LENGTH, + headaxislength=HEAD_AXIS_LENGTH, + width=WIDTH, + color=color, + edgecolor="k", + linewidth=0.2, + ) + ax[line].quiverkey( + Q, + 0.9, + 0.9, + 1, + r"$1 \frac{m}{s}$", + labelpos="E", + coordinates="figure", + color=color, + ) + # Set buildings using a given color - buildIndexes = df_plot[np.isnan(df_plot[WINDSPEED_X.upper()]) &\ - np.isnan(df_plot[WINDSPEED_Y.upper()]) &\ - np.isnan(df_plot[WINDSPEED_Z.upper()])].sort_index(axis = 0, - level = 1).index + buildIndexes = ( + df_plot[ + np.isnan(df_plot[WINDSPEED_X.upper()]) + & np.isnan(df_plot[WINDSPEED_Y.upper()]) + & np.isnan(df_plot[WINDSPEED_Z.upper()]) + ] + .sort_index(axis=0, level=1) + .index + ) # Sort distances from start of the line and z from ground - sorted_dist = pd.Index(np.sort(df_plot.index.unique(level = "DIST"))) - sorted_z = pd.Index(np.sort(z[:,0])) + sorted_dist = pd.Index(np.sort(df_plot.index.unique(level="DIST"))) + sorted_z = pd.Index(np.sort(z[:, 0])) rect = {} for i, bcell in enumerate(buildIndexes): # Get starting height and height of building rectangle @@ -332,43 +542,69 @@ def plotSectionalViews(pluginDirectory, inputWindFile, lines_file='', srid_lines rec_height = 0.5 * (sorted_z[loc_z + 1] - sorted_z[loc_z]) elif loc_z == sorted_z.size - 1: rec_z0 = 0.5 * (sorted_z[loc_z - 1] + sorted_z[loc_z]) - rec_height = sorted_z[loc_z] - sorted_z[loc_z - 1] + rec_height = sorted_z[loc_z] - sorted_z[loc_z - 1] else: - rec_z0 = sorted_z[loc_z] - 0.5 * (sorted_z[loc_z] - sorted_z[loc_z - 1]) - rec_height = 0.5 * (sorted_z[loc_z + 1] - sorted_z[loc_z - 1]) + rec_z0 = sorted_z[loc_z] - 0.5 * ( + sorted_z[loc_z] - sorted_z[loc_z - 1] + ) + rec_height = 0.5 * ( + sorted_z[loc_z + 1] - sorted_z[loc_z - 1] + ) # Get starting distance and width of building rectangle - loc_d = sorted_dist.get_loc(bcell[1]) + loc_d = sorted_dist.get_loc(bcell[1]) if loc_d == 0: - rec_d0 = sorted_dist[loc_d] - 0.5 * (sorted_dist[loc_d + 1] - sorted_dist[loc_d]) + rec_d0 = sorted_dist[loc_d] - 0.5 * ( + sorted_dist[loc_d + 1] - sorted_dist[loc_d] + ) rec_width = sorted_dist[loc_d + 1] - sorted_dist[loc_d] elif loc_d == sorted_dist.size - 1: - rec_d0 = 0.5 * (sorted_dist[loc_d-1] + sorted_dist[loc_d]) - rec_width = sorted_dist[loc_d] - sorted_dist[loc_d - 1] + rec_d0 = 0.5 * ( + sorted_dist[loc_d - 1] + sorted_dist[loc_d] + ) + rec_width = sorted_dist[loc_d] - sorted_dist[loc_d - 1] else: - rec_d0 = 0.5 * (sorted_dist[loc_d-1] + sorted_dist[loc_d]) - rec_width = 0.5 * (sorted_dist[loc_d + 1] - sorted_dist[loc_d - 1]) - + rec_d0 = 0.5 * ( + sorted_dist[loc_d - 1] + sorted_dist[loc_d] + ) + rec_width = 0.5 * ( + sorted_dist[loc_d + 1] - sorted_dist[loc_d - 1] + ) + # Define and plot the building rectangle - rect[i] = Rectangle((rec_d0, rec_z0), rec_width, rec_height, - color='grey') + rect[i] = Rectangle( + (rec_d0, rec_z0), rec_width, rec_height, color="grey" + ) ax[line].add_patch(rect[i]) if savePlot: - fig[line].savefig(os.path.join(outputDirectory, simulationName + "_line" + str(line) + ".png")) + fig[line].savefig( + os.path.join( + outputDirectory, + simulationName + "_line" + str(line) + ".png", + ) + ) else: ax[line].set_title("Line {0}".format(line)) # Close the Database connection and remove the file - H2gisConnection.closeAndRemoveH2gisInstance(localH2InstanceDir = localH2InstanceDir, - conn = conn, - cur = cursor) + H2gisConnection.closeAndRemoveH2gisInstance( + localH2InstanceDir=localH2InstanceDir, conn=conn, cur=cursor + ) return fig, ax, scale, fig_poly, ax_poly - -def conditional_interpolate(df, cols, limit = 2): - s = df[cols].isna().prod(axis = 1) == 0 + + +def conditional_interpolate(df, cols, limit=2): + s = df[cols].isna().prod(axis=1) == 0 s = s.ne(s.shift()).cumsum() - m = df[cols].groupby([s, df[cols].isna().prod(axis = 1) == 0])[cols[0]].transform('size').where(df[cols].isnull().prod(axis = 1).astype(bool)) - - result = df.interpolate(limit_area='inside', method='slinear').mask(m >= limit) - - return result \ No newline at end of file + m = ( + df[cols] + .groupby([s, df[cols].isna().prod(axis=1) == 0])[cols[0]] + .transform("size") + .where(df[cols].isnull().prod(axis=1).astype(bool)) + ) + + result = df.interpolate(limit_area="inside", method="slinear").mask( + m >= limit + ) + + return result diff --git a/functions/URock/urock_processing_algorithm_dep.py b/functions/URock/urock_processing_algorithm_dep.py index 468899e..63dcfd6 100644 --- a/functions/URock/urock_processing_algorithm_dep.py +++ b/functions/URock/urock_processing_algorithm_dep.py @@ -22,33 +22,35 @@ ***************************************************************************/ """ -__author__ = 'Jérémy Bernard / University of Gothenburg' -__date__ = '2021-10-04' -__copyright__ = '(C) 2021 by Jérémy Bernard / University of Gothenburg' +__author__ = "Jérémy Bernard / University of Gothenburg" +__date__ = "2021-10-04" +__copyright__ = "(C) 2021 by Jérémy Bernard / University of Gothenburg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" import os from qgis.PyQt.QtCore import QCoreApplication -from qgis.core import (QgsProcessing, - QgsProcessingAlgorithm, - QgsProcessingParameterField, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterNumber, - QgsProcessingParameterMatrix, - QgsProcessingParameterFolderDestination, - QgsProcessingParameterString, - QgsProcessingParameterRasterLayer, - QgsProcessingParameterBoolean, - QgsRasterLayer, - QgsVectorLayer, - QgsProject, - QgsProcessingContext, - QgsProcessingParameterEnum, - QgsProcessingParameterFile, - QgsProcessingException) +from qgis.core import ( + QgsProcessing, + QgsProcessingAlgorithm, + QgsProcessingParameterField, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterNumber, + QgsProcessingParameterMatrix, + QgsProcessingParameterFolderDestination, + QgsProcessingParameterString, + QgsProcessingParameterRasterLayer, + QgsProcessingParameterBoolean, + QgsRasterLayer, + QgsVectorLayer, + QgsProject, + QgsProcessingContext, + QgsProcessingParameterEnum, + QgsProcessingParameterFile, + QgsProcessingException, +) from qgis.PyQt.QtWidgets import QMessageBox from qgis.utils import iface from pathlib import Path @@ -57,12 +59,19 @@ import struct from . import DataUtil + try: path_pybin = DataUtil.locate_py() - subprocess.check_call([str(path_pybin), "-m", "pip", "install", "jaydebeapi"]) + subprocess.check_call( + [str(path_pybin), "-m", "pip", "install", "jaydebeapi"] + ) import jaydebeapi except Exception: - QMessageBox.critical(None, 'Error', "'jaydebeapi' Python package is missing, cannot connect to H2 Driver") + QMessageBox.critical( + None, + "Error", + "'jaydebeapi' Python package is missing, cannot connect to H2 Driver", + ) pass from . import MainCalculation @@ -71,7 +80,6 @@ from . import WriteMetadataURock - class URockAlgorithm(QgsProcessingAlgorithm): """ This is an example algorithm that takes a vector layer and @@ -92,14 +100,14 @@ class URockAlgorithm(QgsProcessingAlgorithm): # Input variables JAVA_PATH = "JAVA_PATH" - BUILDING_TABLE_NAME = 'BUILDINGS' + BUILDING_TABLE_NAME = "BUILDINGS" VEGETATION_TABLE_NAME = "VEGETATION" INPUT_WIND_HEIGHT = "INPUT_WIND_HEIGHT" INPUT_WIND_SPEED = "INPUT_WIND_SPEED" INPUT_WIND_DIRECTION = "INPUT_WIND_DIRECTION" HORIZONTAL_RESOLUTION = "HORIZONTAL_RESOLUTION" VERTICAL_RESOLUTION = "VERTICAL_RESOLUTION" - #ID_FIELD_BUILD = "ID_FIELD_BUILD" + # ID_FIELD_BUILD = "ID_FIELD_BUILD" HEIGHT_FIELD_BUILD = "HEIGHT_FIELD_BUILD" # ID_FIELD_VEG = "ID_FIELD_VEG" VEGETATION_CROWN_BASE_HEIGHT = "VEGETATION_CROWN_BASE_HEIGHT" @@ -110,16 +118,16 @@ class URockAlgorithm(QgsProcessingAlgorithm): RASTER_OUTPUT = "RASTER_OUTPUT" INPUT_PROFILE_TYPE = "INPUT_PROFILE_TYPE" INPUT_PROFILE_FILE = "INPUT_PROFILE_FILE" - LIST_OF_PROFILES = pd.Series(['power', 'urban', 'user']) + LIST_OF_PROFILES = pd.Series(["power", "urban", "user"]) - # Output variables + # Output variables OUTPUT_DIRECTORY = "UROCK_OUTPUT" OUTPUT_FILENAME = "OUTPUT_FILENAME" SAVE_RASTER = "SAVE_RASTER" SAVE_VECTOR = "SAVE_VECTOR" SAVE_NETCDF = "SAVE_NETCDF" LOAD_OUTPUT = "LOAD_OUTPUT" - + def initAlgorithm(self, config): """ Here we define the inputs and output of the algorithm, along @@ -127,38 +135,49 @@ def initAlgorithm(self, config): """ # Get the plugin directory to save some useful files plugin_directory = self.plugin_dir = os.path.dirname(__file__) - + # Get the default value of the Java environment path if already exists - javaDirDefault = getJavaDir(plugin_directory) - - if not javaDirDefault: # Raise an error if could not find a Java installation - raise QgsProcessingException("No Java installation found") - elif ("Program Files (x86)" in javaDirDefault) and (struct.calcsize("P") * 8 != 32): + javaDirDefault = getJavaDir(plugin_directory) + + if ( + not javaDirDefault + ): # Raise an error if could not find a Java installation + raise QgsProcessingException("No Java installation found") + elif ("Program Files (x86)" in javaDirDefault) and ( + struct.calcsize("P") * 8 != 32 + ): # Raise an error if Java is 32 bits but Python 64 bits - raise QgsProcessingException('Only a 32 bits version of Java has been'+ - 'found while your Python installation is 64 bits.'+ - 'Consider installing a 64 bits Java version.') - else: # Set a Java dir if not exist and save it into a file in the plugin repository + raise QgsProcessingException( + "Only a 32 bits version of Java has been" + + "found while your Python installation is 64 bits." + + "Consider installing a 64 bits Java version." + ) + else: # Set a Java dir if not exist and save it into a file in the plugin repository setJavaDir(javaDirDefault) - saveJavaDir(javaPath = javaDirDefault, - pluginDirectory = plugin_directory) - + saveJavaDir( + javaPath=javaDirDefault, pluginDirectory=plugin_directory + ) + # We add the input parameters # First the layers used as input and output self.addParameter( QgsProcessingParameterFeatureSource( self.BUILDING_TABLE_NAME, - self.tr('Building polygons'), + self.tr("Building polygons"), [QgsProcessing.SourceType.TypeVectorPolygon], - optional = True)) + optional=True, + ) + ) self.addParameter( QgsProcessingParameterField( self.HEIGHT_FIELD_BUILD, - self.tr('Building height field'), + self.tr("Building height field"), None, self.BUILDING_TABLE_NAME, QgsProcessingParameterField.DataType.Numeric, - optional = True)) + optional=True, + ) + ) # self.addParameter( # QgsProcessingParameterField( # self.ID_FIELD_BUILD, @@ -170,33 +189,41 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterFeatureSource( self.VEGETATION_TABLE_NAME, - self.tr('Vegetation polygons'), + self.tr("Vegetation polygons"), [QgsProcessing.SourceType.TypeVectorPolygon], - optional=True)) + optional=True, + ) + ) self.addParameter( QgsProcessingParameterField( self.VEGETATION_CROWN_TOP_HEIGHT, - self.tr('Vegetation crown top height field'), + self.tr("Vegetation crown top height field"), None, self.VEGETATION_TABLE_NAME, QgsProcessingParameterField.DataType.Numeric, - optional = True)) + optional=True, + ) + ) self.addParameter( QgsProcessingParameterField( self.VEGETATION_CROWN_BASE_HEIGHT, - self.tr('Vegetation crown base height field'), + self.tr("Vegetation crown base height field"), None, self.VEGETATION_TABLE_NAME, QgsProcessingParameterField.DataType.Numeric, - optional = True)) + optional=True, + ) + ) self.addParameter( QgsProcessingParameterField( self.ATTENUATION_FIELD, - self.tr('Vegetation wind attenuation factor'), + self.tr("Vegetation wind attenuation factor"), None, self.VEGETATION_TABLE_NAME, QgsProcessingParameterField.DataType.Numeric, - optional = True)) + optional=True, + ) + ) # self.addParameter( # QgsProcessingParameterField( # self.ID_FIELD_VEG, @@ -206,103 +233,132 @@ def initAlgorithm(self, config): # QgsProcessingParameterField.Numeric, # optional = True)) - # Then the informations related to calculation self.addParameter( QgsProcessingParameterFile( self.INPUT_PROFILE_FILE, - self.tr('Vertical wind profile file (.csv)'), - defaultValue = '', - extension='csv', - optional = True)) + self.tr("Vertical wind profile file (.csv)"), + defaultValue="", + extension="csv", + optional=True, + ) + ) self.addParameter( - QgsProcessingParameterEnum( - self.INPUT_PROFILE_TYPE, - self.tr('Vertical wind profile type'), - self.LIST_OF_PROFILES.values, - defaultValue=0, - optional = True)) + QgsProcessingParameterEnum( + self.INPUT_PROFILE_TYPE, + self.tr("Vertical wind profile type"), + self.LIST_OF_PROFILES.values, + defaultValue=0, + optional=True, + ) + ) self.addParameter( QgsProcessingParameterNumber( self.INPUT_WIND_HEIGHT, - self.tr('Height of the reference wind speed (m)'), + self.tr("Height of the reference wind speed (m)"), QgsProcessingParameterNumber.Type.Double, 10, - True)) + True, + ) + ) self.addParameter( QgsProcessingParameterNumber( self.INPUT_WIND_SPEED, - self.tr('Wind speed at the reference height (m/s)'), + self.tr("Wind speed at the reference height (m/s)"), QgsProcessingParameterNumber.Type.Double, 2, - True)) + True, + ) + ) self.addParameter( QgsProcessingParameterNumber( self.INPUT_WIND_DIRECTION, - self.tr('Wind direction (° clock-wise from North)'), + self.tr("Wind direction (° clock-wise from North)"), QgsProcessingParameterNumber.Type.Double, 45, - False)) + False, + ) + ) self.addParameter( QgsProcessingParameterRasterLayer( self.RASTER_OUTPUT, - self.tr('Raster to use as output'), + self.tr("Raster to use as output"), None, - optional = True)) + optional=True, + ) + ) self.addParameter( QgsProcessingParameterNumber( self.HORIZONTAL_RESOLUTION, - self.tr('Horizontal resolution (m)'), + self.tr("Horizontal resolution (m)"), QgsProcessingParameterNumber.Type.Integer, 2, - optional = True)) + optional=True, + ) + ) self.addParameter( QgsProcessingParameterNumber( self.VERTICAL_RESOLUTION, - self.tr('Vertical resolution (m)'), + self.tr("Vertical resolution (m)"), QgsProcessingParameterNumber.Type.Integer, 2, - False)) - + False, + ) + ) # We add several output parameters self.addParameter( QgsProcessingParameterString( self.WIND_HEIGHT, - self.tr('Output wind height(s) (m) - if several values, separated by ","'), - defaultValue = "1.5", - optional = False)) + self.tr( + 'Output wind height(s) (m) - if several values, separated by ","' + ), + defaultValue="1.5", + optional=False, + ) + ) self.addParameter( QgsProcessingParameterFolderDestination( - self.OUTPUT_DIRECTORY, - self.tr('Directory to save the outputs'))) + self.OUTPUT_DIRECTORY, self.tr("Directory to save the outputs") + ) + ) self.addParameter( QgsProcessingParameterString( self.OUTPUT_FILENAME, - self.tr('String used as output file base name'), + self.tr("String used as output file base name"), "urock_output", - False)) + False, + ) + ) self.addParameter( QgsProcessingParameterBoolean( self.SAVE_RASTER, self.tr("Save 2D wind speed as raster file(s)"), - defaultValue=False)) + defaultValue=False, + ) + ) self.addParameter( QgsProcessingParameterBoolean( self.SAVE_VECTOR, self.tr("Save 2D wind field as vector file(s)"), - defaultValue=False)) + defaultValue=False, + ) + ) self.addParameter( QgsProcessingParameterBoolean( self.SAVE_NETCDF, self.tr("Save 3D wind field in a NetCDF file"), - defaultValue=False)) + defaultValue=False, + ) + ) self.addParameter( QgsProcessingParameterBoolean( self.LOAD_OUTPUT, self.tr("Open output file after running algorithm"), - defaultValue=False)) - + defaultValue=False, + ) + ) + # Optional parameters # self.addParameter( # QgsProcessingParameterString( @@ -314,10 +370,12 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterString( self.JAVA_PATH, - self.tr('Java environment path (should be set automatically)'), + self.tr("Java environment path (should be set automatically)"), javaDirDefault, False, - False)) + False, + ) + ) def processAlgorithm(self, parameters, context, feedback): """ @@ -325,178 +383,285 @@ def processAlgorithm(self, parameters, context, feedback): """ # Get the plugin directory to save some useful files plugin_directory = self.plugin_dir = os.path.dirname(__file__) - + # Defines inputs - javaEnvVar = self.parameterAsString(parameters, self.JAVA_PATH, context) - z_ref = self.parameterAsDouble(parameters, self.INPUT_WIND_HEIGHT, context) - v_ref = self.parameterAsDouble(parameters, self.INPUT_WIND_SPEED, context) - windDirection = self.parameterAsDouble(parameters, self.INPUT_WIND_DIRECTION, context) - meshSize = self.parameterAsInt(parameters, self.HORIZONTAL_RESOLUTION, context) + javaEnvVar = self.parameterAsString( + parameters, self.JAVA_PATH, context + ) + z_ref = self.parameterAsDouble( + parameters, self.INPUT_WIND_HEIGHT, context + ) + v_ref = self.parameterAsDouble( + parameters, self.INPUT_WIND_SPEED, context + ) + windDirection = self.parameterAsDouble( + parameters, self.INPUT_WIND_DIRECTION, context + ) + meshSize = self.parameterAsInt( + parameters, self.HORIZONTAL_RESOLUTION, context + ) dz = self.parameterAsInt(parameters, self.VERTICAL_RESOLUTION, context) - profileType = self.LIST_OF_PROFILES.loc[self.parameterAsInt(parameters, self.INPUT_PROFILE_TYPE, context)] - profileFile = self.parameterAsString(parameters, self.INPUT_PROFILE_FILE, context) - + profileType = self.LIST_OF_PROFILES.loc[ + self.parameterAsInt(parameters, self.INPUT_PROFILE_TYPE, context) + ] + profileFile = self.parameterAsString( + parameters, self.INPUT_PROFILE_FILE, context + ) + # Get building layer and then file directory - inputBuildinglayer = self.parameterAsVectorLayer(parameters, self.BUILDING_TABLE_NAME, context) - heightBuild = self.parameterAsString(parameters, self.HEIGHT_FIELD_BUILD, context) + inputBuildinglayer = self.parameterAsVectorLayer( + parameters, self.BUILDING_TABLE_NAME, context + ) + heightBuild = self.parameterAsString( + parameters, self.HEIGHT_FIELD_BUILD, context + ) if inputBuildinglayer: build_file = str(inputBuildinglayer.dataProvider().dataSourceUri()) if build_file.count("|layername") == 1: build_file = build_file.split("|layername")[0] srid_build = inputBuildinglayer.crs().postgisSrid() if not heightBuild: - raise QgsProcessingException("A building height attribute should be defined") + raise QgsProcessingException( + "A building height attribute should be defined" + ) else: build_file = None srid_build = None # Get vegetation layer if exists, check that it has the same SRID as building layer # and then get the file directory of the layer - inputVegetationlayer = self.parameterAsVectorLayer(parameters, self.VEGETATION_TABLE_NAME, context) - topHeightVeg = self.parameterAsString(parameters, self.VEGETATION_CROWN_TOP_HEIGHT, context) + inputVegetationlayer = self.parameterAsVectorLayer( + parameters, self.VEGETATION_TABLE_NAME, context + ) + topHeightVeg = self.parameterAsString( + parameters, self.VEGETATION_CROWN_TOP_HEIGHT, context + ) if inputVegetationlayer: veg_file = str(inputVegetationlayer.dataProvider().dataSourceUri()) if veg_file.count("|layername") == 1: veg_file = veg_file.split("|layername")[0] srid_veg = inputVegetationlayer.crs().postgisSrid() if srid_build and (srid_build != srid_veg): - feedback.pushWarning('Coordinate system of input building layer and vegetation layer differ!') + feedback.pushWarning( + "Coordinate system of input building layer and vegetation layer differ!" + ) if not topHeightVeg: - raise QgsProcessingException("A vegetation crown top height attribute should be defined") + raise QgsProcessingException( + "A vegetation crown top height attribute should be defined" + ) else: veg_file = None srid_veg = None - + if not veg_file and not build_file: - raise QgsProcessingException("Either building or vegetation file should be provided") - - outputRaster = self.parameterAsRasterLayer(parameters, self.RASTER_OUTPUT, context) - #idBuild = self.parameterAsString(parameters, self.ID_FIELD_BUILD, context) - #idVeg = self.parameterAsString(parameters, self.ID_FIELD_VEG, context) - baseHeightVeg = self.parameterAsString(parameters, self.VEGETATION_CROWN_BASE_HEIGHT, context) - attenuationVeg = self.parameterAsString(parameters, self.ATTENUATION_FIELD, context) - #prefix = self.parameterAsString(parameters, self.PREFIX, context) - + raise QgsProcessingException( + "Either building or vegetation file should be provided" + ) + + outputRaster = self.parameterAsRasterLayer( + parameters, self.RASTER_OUTPUT, context + ) + # idBuild = self.parameterAsString(parameters, self.ID_FIELD_BUILD, context) + # idVeg = self.parameterAsString(parameters, self.ID_FIELD_VEG, context) + baseHeightVeg = self.parameterAsString( + parameters, self.VEGETATION_CROWN_BASE_HEIGHT, context + ) + attenuationVeg = self.parameterAsString( + parameters, self.ATTENUATION_FIELD, context + ) + # prefix = self.parameterAsString(parameters, self.PREFIX, context) + # Defines outputs - z_out = self.parameterAsString(parameters, self.WIND_HEIGHT, context).split(",") + z_out = self.parameterAsString( + parameters, self.WIND_HEIGHT, context + ).split(",") z_out = [float(i) for i in z_out] - outputDirectory = self.parameterAsString(parameters, self.OUTPUT_DIRECTORY, context) - outputFilename = self.parameterAsString(parameters, self.OUTPUT_FILENAME, context) - saveRaster = self.parameterAsBool(parameters, self.SAVE_RASTER, context) - saveVector = self.parameterAsBool(parameters, self.SAVE_VECTOR, context) - saveNetcdf = self.parameterAsBool(parameters, self.SAVE_NETCDF, context) - loadOutput = self.parameterAsBool(parameters, self.LOAD_OUTPUT, context) + outputDirectory = self.parameterAsString( + parameters, self.OUTPUT_DIRECTORY, context + ) + outputFilename = self.parameterAsString( + parameters, self.OUTPUT_FILENAME, context + ) + saveRaster = self.parameterAsBool( + parameters, self.SAVE_RASTER, context + ) + saveVector = self.parameterAsBool( + parameters, self.SAVE_VECTOR, context + ) + saveNetcdf = self.parameterAsBool( + parameters, self.SAVE_NETCDF, context + ) + loadOutput = self.parameterAsBool( + parameters, self.LOAD_OUTPUT, context + ) # Creates the output folder if it does not exist if not os.path.exists(outputDirectory): if os.path.exists(Path(outputDirectory).parent.absolute()): os.mkdir(outputDirectory) else: - raise QgsProcessingException('The output directory does not exist, neither its parent directory') + raise QgsProcessingException( + "The output directory does not exist, neither its parent directory" + ) # If there is an output raster, need to get some of its parameters if outputRaster: - if inputBuildinglayer.crs().postgisSrid() != outputRaster.crs().postgisSrid(): - feedback.pushWarning('Coordinate system of input building layer and output Raster layer differ!') - xres = (outputRaster.extent().xMaximum() - outputRaster.extent().xMinimum()) / outputRaster.width() - yres = (outputRaster.extent().yMaximum() - outputRaster.extent().yMinimum()) / outputRaster.height() + if ( + inputBuildinglayer.crs().postgisSrid() + != outputRaster.crs().postgisSrid() + ): + feedback.pushWarning( + "Coordinate system of input building layer and output Raster layer differ!" + ) + xres = ( + outputRaster.extent().xMaximum() + - outputRaster.extent().xMinimum() + ) / outputRaster.width() + yres = ( + outputRaster.extent().yMaximum() + - outputRaster.extent().yMinimum() + ) / outputRaster.height() # If there is a raster and no meshSize, take the mean of x and y raster resolution if not meshSize: meshSize = float(xres + yres) / 2 elif not meshSize: - raise QgsProcessingException('You should either specify an output raster or a horizontal mesh size') - + raise QgsProcessingException( + "You should either specify an output raster or a horizontal mesh size" + ) + if feedback: - feedback.setProgressText("Writing settings for this model run to specified output folder (Filename: RunInfoURock_YYYY_DOY_HHMM.txt)") - WriteMetadataURock.writeRunInfo(outputDirectory, build_file, veg_file, attenuationVeg) + feedback.setProgressText( + "Writing settings for this model run to specified output folder (Filename: RunInfoURock_YYYY_DOY_HHMM.txt)" + ) + WriteMetadataURock.writeRunInfo( + outputDirectory, build_file, veg_file, attenuationVeg + ) # Make the calculations - u, v, w, u0, v0, w0, x, y, z, buildingCoordinates, cursor, gridName,\ - rotationCenterCoordinates, verticalWindProfile, dicVectorTables,\ - netcdf_path, net_cdf_path_ini = \ - MainCalculation.main(javaEnvironmentPath = javaEnvVar, - pluginDirectory = plugin_directory, - outputFilePath = outputDirectory, - outputFilename = outputFilename, - buildingFilePath = build_file, - vegetationFilePath = veg_file, - srid = srid_build, - z_ref = z_ref, - v_ref = v_ref, - windDirection = windDirection, - prefix = '', #prefix, - meshSize = meshSize, - dz = dz, - alongWindZoneExtend = ALONG_WIND_ZONE_EXTEND, - crossWindZoneExtend = CROSS_WIND_ZONE_EXTEND, - verticalExtend = VERTICAL_EXTEND, - cadTriangles = "", - cadTreesIntersection = "", - tempoDirectory = TEMPO_DIRECTORY, - onlyInitialization = ONLY_INITIALIZATION, - maxIterations = MAX_ITERATIONS, - thresholdIterations = THRESHOLD_ITERATIONS, - idFieldBuild = None, # idBuild, - buildingHeightField = heightBuild, - vegetationBaseHeight = baseHeightVeg, - vegetationTopHeight = topHeightVeg, - idVegetation = None, #idVeg, - vegetationAttenuationFactor = attenuationVeg, - saveRockleZones = SAVE_ROCKLE_ZONES, - outputRaster = outputRaster, - feedback = feedback, - saveRaster = saveRaster, - saveVector = saveVector, - saveNetcdf = saveNetcdf, - z_out = z_out, - debug = DEBUG, - profileType = profileType, - verticalProfileFile = profileFile) - + ( + u, + v, + w, + u0, + v0, + w0, + x, + y, + z, + buildingCoordinates, + cursor, + gridName, + rotationCenterCoordinates, + verticalWindProfile, + dicVectorTables, + netcdf_path, + net_cdf_path_ini, + ) = MainCalculation.main( + javaEnvironmentPath=javaEnvVar, + pluginDirectory=plugin_directory, + outputFilePath=outputDirectory, + outputFilename=outputFilename, + buildingFilePath=build_file, + vegetationFilePath=veg_file, + srid=srid_build, + z_ref=z_ref, + v_ref=v_ref, + windDirection=windDirection, + prefix="", # prefix, + meshSize=meshSize, + dz=dz, + alongWindZoneExtend=ALONG_WIND_ZONE_EXTEND, + crossWindZoneExtend=CROSS_WIND_ZONE_EXTEND, + verticalExtend=VERTICAL_EXTEND, + cadTriangles="", + cadTreesIntersection="", + tempoDirectory=TEMPO_DIRECTORY, + onlyInitialization=ONLY_INITIALIZATION, + maxIterations=MAX_ITERATIONS, + thresholdIterations=THRESHOLD_ITERATIONS, + idFieldBuild=None, # idBuild, + buildingHeightField=heightBuild, + vegetationBaseHeight=baseHeightVeg, + vegetationTopHeight=topHeightVeg, + idVegetation=None, # idVeg, + vegetationAttenuationFactor=attenuationVeg, + saveRockleZones=SAVE_ROCKLE_ZONES, + outputRaster=outputRaster, + feedback=feedback, + saveRaster=saveRaster, + saveVector=saveVector, + saveNetcdf=saveNetcdf, + z_out=z_out, + debug=DEBUG, + profileType=profileType, + verticalProfileFile=profileFile, + ) + # Load files into QGIS if user set it if loadOutput: for z_i in z_out: if saveVector: - loadedVector = \ - QgsVectorLayer(os.path.join(outputDirectory, - "z{0}".format(str(z_i).replace(".","_")), - outputFilename\ - + OUTPUT_VECTOR_EXTENSION), - "Wind at {0} m".format(z_i), - "ogr") + loadedVector = QgsVectorLayer( + os.path.join( + outputDirectory, + "z{0}".format(str(z_i).replace(".", "_")), + outputFilename + OUTPUT_VECTOR_EXTENSION, + ), + "Wind at {0} m".format(z_i), + "ogr", + ) if not loadedVector.isValid(): feedback.pushWarning("Vector layer failed to load!") break else: - loadedVector.loadNamedStyle(os.path.join(plugin_directory,\ - "Resources", - VECTOR_STYLE_FILENAME), True) - context.addLayerToLoadOnCompletion(loadedVector.id(), - QgsProcessingContext.LayerDetails("Wind at {0} m".format(z_i), - QgsProject.instance(), - '')) + loadedVector.loadNamedStyle( + os.path.join( + plugin_directory, + "Resources", + VECTOR_STYLE_FILENAME, + ), + True, + ) + context.addLayerToLoadOnCompletion( + loadedVector.id(), + QgsProcessingContext.LayerDetails( + "Wind at {0} m".format(z_i), + QgsProject.instance(), + "", + ), + ) context.temporaryLayerStore().addMapLayer(loadedVector) - + if saveRaster: - loadedRaster = \ - QgsRasterLayer(os.path.join(outputDirectory, - "z{0}".format(str(z_i).replace(".","_")), - outputFilename\ - + WIND_SPEED + OUTPUT_RASTER_EXTENSION), - "Wind speed at {0} m".format(z_i), - "gdal") + loadedRaster = QgsRasterLayer( + os.path.join( + outputDirectory, + "z{0}".format(str(z_i).replace(".", "_")), + outputFilename + + WIND_SPEED + + OUTPUT_RASTER_EXTENSION, + ), + "Wind speed at {0} m".format(z_i), + "gdal", + ) if not loadedRaster.isValid(): feedback.pushWarning("Raster layer failed to load!") break else: - context.addLayerToLoadOnCompletion(loadedRaster.id(), - QgsProcessingContext.LayerDetails("Wind speed at {0} m".format(z_i), - QgsProject.instance(), - '')) + context.addLayerToLoadOnCompletion( + loadedRaster.id(), + QgsProcessingContext.LayerDetails( + "Wind speed at {0} m".format(z_i), + QgsProject.instance(), + "", + ), + ) context.temporaryLayerStore().addMapLayer(loadedRaster) # Return the output file names - return {self.OUTPUT_DIRECTORY: outputDirectory, - self.OUTPUT_FILENAME: outputFilename} + return { + self.OUTPUT_DIRECTORY: outputDirectory, + self.OUTPUT_FILENAME: outputFilename, + } def name(self): """ @@ -506,7 +671,7 @@ def name(self): lowercase alphanumeric characters only and no spaces or other formatting characters. """ - return 'URock' + return "URock" def displayName(self): """ @@ -530,23 +695,25 @@ def groupId(self): contain lowercase alphanumeric characters only and no spaces or other formatting characters. """ - return '' + return "" def tr(self, string): - return QCoreApplication.translate('Processing', string) - + return QCoreApplication.translate("Processing", string) + def shortHelpString(self): - return self.tr('The URock plugin can be used to calculate '+\ - 'spatial variations of wind speed and wind direction'+ - ' in 3 dimensions using 2.5D building and vegetation data.\n'+ - 'At least one of building or vegetation file should '+ - 'be provided by the user. Minimum attribute column'+ - ' for building file is "roof height" '+ - '(note that roofs are considered flats in the current version)'+ - 'Minimum attribute column for vegetation file is "vegetation crown top height".' - '\n' - '---------------\n' - 'Full manual available via the Help-button.') + return self.tr( + "The URock plugin can be used to calculate " + + "spatial variations of wind speed and wind direction" + + " in 3 dimensions using 2.5D building and vegetation data.\n" + + "At least one of building or vegetation file should " + + "be provided by the user. Minimum attribute column" + + ' for building file is "roof height" ' + + "(note that roofs are considered flats in the current version)" + + 'Minimum attribute column for vegetation file is "vegetation crown top height".' + "\n" + "---------------\n" + "Full manual available via the Help-button." + ) def helpUrl(self): url = "https://github.com/j3r3m1/UMEP-Docs/blob/master/docs/source/processor/Wind%20model%20URock.rst" diff --git a/functions/URock/urock_processing_dep.py b/functions/URock/urock_processing_dep.py index 21bf4cf..c361855 100644 --- a/functions/URock/urock_processing_dep.py +++ b/functions/URock/urock_processing_dep.py @@ -22,13 +22,13 @@ ***************************************************************************/ """ -__author__ = 'Jérémy Bernard / University of Gothenburg' -__date__ = '2021-10-04' -__copyright__ = '(C) 2021 by Jérémy Bernard / University of Gothenburg' +__author__ = "Jérémy Bernard / University of Gothenburg" +__date__ = "2021-10-04" +__copyright__ = "(C) 2021 by Jérémy Bernard / University of Gothenburg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" import os import sys diff --git a/functions/URock/urock_processing_provider_dep.py b/functions/URock/urock_processing_provider_dep.py index 5ebb711..baa6524 100644 --- a/functions/URock/urock_processing_provider_dep.py +++ b/functions/URock/urock_processing_provider_dep.py @@ -22,13 +22,13 @@ ***************************************************************************/ """ -__author__ = 'Jérémy Bernard / University of Gothenburg' -__date__ = '2021-10-04' -__copyright__ = '(C) 2021 by Jérémy Bernard / University of Gothenburg' +__author__ = "Jérémy Bernard / University of Gothenburg" +__date__ = "2021-10-04" +__copyright__ = "(C) 2021 by Jérémy Bernard / University of Gothenburg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.core import QgsProcessingProvider from .urock_processing_algorithm import URockAlgorithm @@ -63,7 +63,7 @@ def id(self): string should be a unique, short, character only string, eg "qgis" or "gdal". This string should not be localised. """ - return 'urock' + return "urock" def name(self): """ @@ -72,7 +72,7 @@ def name(self): This string should be short (e.g. "Lastools") and localised. """ - return self.tr('URock') + return self.tr("URock") def icon(self): """ diff --git a/functions/dailyshading.py b/functions/dailyshading.py index 6b321cd..ec20cd9 100644 --- a/functions/dailyshading.py +++ b/functions/dailyshading.py @@ -2,14 +2,40 @@ from builtins import range from ..util import shadowingfunctions as shadow -from ..util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_13 import shadowingfunction_wallheight_13 -from ..util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_23 import shadowingfunction_wallheight_23 +from ..util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_13 import ( + shadowingfunction_wallheight_13, +) +from ..util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_23 import ( + shadowingfunction_wallheight_23, +) from ..util.misc import saveraster from ..util.SEBESOLWEIGCommonFiles import sun_position as sp import numpy as np -def dailyshading(dsm, vegdsm, vegdsm2, scale, lon, lat, sizex, sizey, tv, UTC, usevegdem, timeInterval, onetime, feedback, folder, gdal_data, trans, dst, wallshadow, wheight, waspect): +def dailyshading( + dsm, + vegdsm, + vegdsm2, + scale, + lon, + lat, + sizex, + sizey, + tv, + UTC, + usevegdem, + timeInterval, + onetime, + feedback, + folder, + gdal_data, + trans, + dst, + wallshadow, + wheight, + waspect, +): # lon = lonlat[0] # lat = lonlat[1] @@ -18,7 +44,7 @@ def dailyshading(dsm, vegdsm, vegdsm2, scale, lon, lat, sizex, sizey, tv, UTC, u day = tv[2] alt = np.median(dsm) - location = {'longitude': lon, 'latitude': lat, 'altitude': alt} + location = {"longitude": lon, "latitude": lat, "altitude": alt} if usevegdem == 1: psi = trans # amaxvalue @@ -33,7 +59,7 @@ def dailyshading(dsm, vegdsm, vegdsm2, scale, lon, lat, sizex, sizey, tv, UTC, u vegdem2[vegdem2 == dsm] = 0 # Bush separation - bush = np.logical_not((vegdem2*vegdem))*vegdem + bush = np.logical_not((vegdem2 * vegdem)) * vegdem shtot = np.zeros((sizex, sizey)) @@ -47,19 +73,19 @@ def dailyshading(dsm, vegdsm, vegdsm2, scale, lon, lat, sizex, sizey, tv, UTC, u hour = int(0) index = 0 time = dict() - time['UTC'] = UTC + time["UTC"] = UTC if wallshadow == 1: walls = wheight dirwalls = waspect - else: + else: walls = np.zeros((sizex, sizey)) dirwalls = np.zeros((sizex, sizey)) for i in range(0, itera): if feedback.isCanceled(): - feedback.setProgressText("Calculation cancelled") - break + feedback.setProgressText("Calculation cancelled") + break if onetime == 0: minu = int(timeInterval * i) if minu >= 60: @@ -71,7 +97,13 @@ def dailyshading(dsm, vegdsm, vegdsm2, scale, lon, lat, sizex, sizey, tv, UTC, u doy = day_of_year(year, month, day) - ut_time = doy - 1. + ((hour - dst) / 24.0) + (minu / (60. * 24.0)) + (0. / (60. * 60. * 24.0)) + ut_time = ( + doy + - 1.0 + + ((hour - dst) / 24.0) + + (minu / (60.0 * 24.0)) + + (0.0 / (60.0 * 60.0 * 24.0)) + ) if ut_time < 0: year = year - 1 @@ -82,64 +114,107 @@ def dailyshading(dsm, vegdsm, vegdsm2, scale, lon, lat, sizex, sizey, tv, UTC, u HHMMSS = dectime_to_timevec(ut_time) # feedback.setProgressText('HHMMSS:' + str(HHMMSS)) - time['year'] = year - time['month'] = month - time['day'] = day - time['hour'] = HHMMSS[0] - time['min'] = HHMMSS[1] - time['sec'] = HHMMSS[2] + time["year"] = year + time["month"] = month + time["day"] = day + time["hour"] = HHMMSS[0] + time["min"] = HHMMSS[1] + time["sec"] = HHMMSS[2] sun = sp.sun_position(time, location) - alt[i] = 90. - sun['zenith'] - azi[i] = sun['azimuth'] - - if time['sec'] == 59: #issue 228 and 256 - time['sec'] = 0 - time['min'] = time['min'] + 1 - if time['min'] == 60: - time['min'] = 0 - time['hour'] = time['hour'] + 1 - if time['hour'] == 24: - time['hour'] = 0 - - time_vector = dt.datetime(year, month, day, time['hour'], time['min'], time['sec']) + alt[i] = 90.0 - sun["zenith"] + azi[i] = sun["azimuth"] + + if time["sec"] == 59: # issue 228 and 256 + time["sec"] = 0 + time["min"] = time["min"] + 1 + if time["min"] == 60: + time["min"] = 0 + time["hour"] = time["hour"] + 1 + if time["hour"] == 24: + time["hour"] = 0 + + time_vector = dt.datetime( + year, month, day, time["hour"], time["min"], time["sec"] + ) timestr = time_vector.strftime("%Y%m%d_%H%M") # feedback.setProgressText('timestr:' + str(timestr)) if alt[i] > 0: - if wallshadow == 1: # Include wall shadows (Issue #121) + if wallshadow == 1: # Include wall shadows (Issue #121) if usevegdem == 1: - vegsh, sh, _, wallsh, _, wallshve, _, _ = shadowingfunction_wallheight_23(dsm, vegdem, vegdem2, - azi[i], alt[i], scale, amaxvalue, bush, walls, dirwalls * np.pi / 180.) + vegsh, sh, _, wallsh, _, wallshve, _, _ = ( + shadowingfunction_wallheight_23( + dsm, + vegdem, + vegdem2, + azi[i], + alt[i], + scale, + amaxvalue, + bush, + walls, + dirwalls * np.pi / 180.0, + ) + ) sh = sh - (1 - vegsh) * (1 - psi) if onetime == 0: - filenamewallshve = folder + '/Facadeshadow_fromvegetation_' + timestr + '_LST.tif' + filenamewallshve = ( + folder + + "/Facadeshadow_fromvegetation_" + + timestr + + "_LST.tif" + ) saveraster(gdal_data, filenamewallshve, wallshve) else: - sh, wallsh, _, _, _ = shadowingfunction_wallheight_13(dsm, azi[i], alt[i], scale, - walls, dirwalls * np.pi / 180.) + sh, wallsh, _, _, _ = shadowingfunction_wallheight_13( + dsm, + azi[i], + alt[i], + scale, + walls, + dirwalls * np.pi / 180.0, + ) # shtot = shtot + sh - + if onetime == 0: - filename = folder + '/Shadow_ground_' + timestr + '_LST.tif' + filename = ( + folder + "/Shadow_ground_" + timestr + "_LST.tif" + ) saveraster(gdal_data, filename, sh) - filenamewallsh = folder + '/Facadeshadow_frombuilding_' + timestr + '_LST.tif' + filenamewallsh = ( + folder + + "/Facadeshadow_frombuilding_" + + timestr + + "_LST.tif" + ) saveraster(gdal_data, filenamewallsh, wallsh) - else: if usevegdem == 0: - sh = shadow.shadowingfunctionglobalradiation(dsm, azi[i], alt[i], scale, feedback, 0) + sh = shadow.shadowingfunctionglobalradiation( + dsm, azi[i], alt[i], scale, feedback, 0 + ) # shtot = shtot + sh else: - shadowresult = shadow.shadowingfunction_20(dsm, vegdem, vegdem2, azi[i], alt[i], scale, amaxvalue, - bush, feedback, 0) + shadowresult = shadow.shadowingfunction_20( + dsm, + vegdem, + vegdem2, + azi[i], + alt[i], + scale, + amaxvalue, + bush, + feedback, + 0, + ) vegsh = shadowresult["vegsh"] sh = shadowresult["sh"] - sh = sh - (1-vegsh)*(1-psi) + sh = sh - (1 - vegsh) * (1 - psi) # vegshtot = vegshtot + sh if onetime == 0: - filename = folder + '/Shadow_' + timestr + '_LST.tif' + filename = folder + "/Shadow_" + timestr + "_LST.tif" saveraster(gdal_data, filename, sh) shtot = shtot + sh @@ -149,16 +224,24 @@ def dailyshading(dsm, vegdsm, vegdsm2, scale, lon, lat, sizex, sizey, tv, UTC, u if wallshadow == 1: if onetime == 1: - filenamewallsh = folder + '/Facadeshadow_frombuilding_' + timestr + '_LST.tif' + filenamewallsh = ( + folder + "/Facadeshadow_frombuilding_" + timestr + "_LST.tif" + ) saveraster(gdal_data, filenamewallsh, wallsh) if usevegdem == 1: - filenamewallshve = folder + '/Facadeshadow_fromvegetation_' + timestr + '_LST.tif' + filenamewallshve = ( + folder + + "/Facadeshadow_fromvegetation_" + + timestr + + "_LST.tif" + ) saveraster(gdal_data, filenamewallshve, wallshve) - shadowresult = {'shfinal': shfinal, 'time_vector': time_vector} + shadowresult = {"shfinal": shfinal, "time_vector": time_vector} return shadowresult + def day_of_year(yy, month, day): if (yy % 4) == 0: if (yy % 100) == 0: @@ -176,7 +259,7 @@ def day_of_year(yy, month, day): else: dayspermonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] - doy = np.sum(dayspermonth[0:month-1]) + day + doy = np.sum(dayspermonth[0 : month - 1]) + day return doy @@ -186,11 +269,11 @@ def dectime_to_timevec(dectime): doy = np.floor(dectime) - DH = dectime-doy + DH = dectime - doy HOURS = int(24 * DH) - DM=24*DH - HOURS - MINS=int(60 * DM) + DM = 24 * DH - HOURS + MINS = int(60 * DM) DS = 60 * DM - MINS SECS = int(60 * DS) diff --git a/functions/svf_for_voxels.py b/functions/svf_for_voxels.py index 340e8cd..c069545 100644 --- a/functions/svf_for_voxels.py +++ b/functions/svf_for_voxels.py @@ -8,11 +8,16 @@ from ..functions import svf_functions as svf from ..functions import wallalgorithms as wa + def wallscheme_prepare(dsm, scale, pixel_resolution, feedback): - total = 100. / (int(dsm.shape[0] * dsm.shape[1])) - walls = wa.findwalls_sp(dsm, 2, np.array([[1, 1, 1], [1, 0, 1], [1, 1, 1]])) + total = 100.0 / (int(dsm.shape[0] * dsm.shape[1])) + walls = wa.findwalls_sp( + dsm, 2, np.array([[1, 1, 1], [1, 0, 1], [1, 1, 1]]) + ) walls_copy = np.copy(walls) - aspect = wa.filter1Goodwin_as_aspect_v3(walls_copy, scale, dsm, feedback, 100) + aspect = wa.filter1Goodwin_as_aspect_v3( + walls_copy, scale, dsm, feedback, 100 + ) # Copy to keep exact height values walls_exact = walls.copy() @@ -33,10 +38,12 @@ def wallscheme_prepare(dsm, scale, pixel_resolution, feedback): uniqueWallIDs = np.zeros((dsm.shape[0], dsm.shape[1])) for i in np.arange(wall_rows.shape[0]): uniqueWallIDs[wall_rows[i], wall_cols[i]] = index - number_of_voxels = int(walls_round[wall_rows[i], wall_cols[i]] / pixel_resolution) - #temp_aspect = wallAspect[wall_rows[i], wall_cols[i]] + number_of_voxels = int( + walls_round[wall_rows[i], wall_cols[i]] / pixel_resolution + ) + # temp_aspect = wallAspect[wall_rows[i], wall_cols[i]] voxel_index = 1 - for j in range(1, number_of_voxels+1): + for j in range(1, number_of_voxels + 1): # wall_id.append(voxel_index) wall2d_id.append(index) voxel_height.append(j * pixel_resolution) @@ -44,11 +51,11 @@ def wallscheme_prepare(dsm, scale, pixel_resolution, feedback): wall_height_exact.append(walls_exact[wall_rows[i], wall_cols[i]]) y_position.append(wall_rows[i]) x_position.append(wall_cols[i]) - #wall_aspect.append(temp_aspect) + # wall_aspect.append(temp_aspect) voxel_index += 1 - index += 1 + index += 1 wall2d_id.append(0) voxel_height.append(0) @@ -64,39 +71,81 @@ def wallscheme_prepare(dsm, scale, pixel_resolution, feedback): # saveraster(dataSet, output_uniquewallid, uniqueWallIDs) # Unique IDs for each voxel - voxelId_list = np.arange(1, wall2d_id.__len__()+1) + voxelId_list = np.arange(1, wall2d_id.__len__() + 1) # Table with unique voxel ID, height of voxel, total height of wall, unique ID of wall (based on 2D-location in raster) and y and x coordinates - voxelTable = np.column_stack([voxelId_list, voxel_height, wall_height, wall_height_exact, wall2d_id, y_position, x_position]) - - return voxelTable, voxelId_list, wall_dict, walls, aspect, uniqueWallIDs, wall2d_id, voxel_height - -def svf_for_voxels(dsm, dem, vegdsm, vegdsm2, transVeg, scale, usevegdem, pixel_resolution, voxelTable, - svf_height, svf_array, svfbu_array, svfveg_array, svfaveg_array, svf_height_array, feedback): - - '''This function calculates sky view factor at all voxel levels''' + voxelTable = np.column_stack( + [ + voxelId_list, + voxel_height, + wall_height, + wall_height_exact, + wall2d_id, + y_position, + x_position, + ] + ) + + return ( + voxelTable, + voxelId_list, + wall_dict, + walls, + aspect, + uniqueWallIDs, + wall2d_id, + voxel_height, + ) + + +def svf_for_voxels( + dsm, + dem, + vegdsm, + vegdsm2, + transVeg, + scale, + usevegdem, + pixel_resolution, + voxelTable, + svf_height, + svf_array, + svfbu_array, + svfveg_array, + svfaveg_array, + svf_height_array, + feedback, +): + """This function calculates sky view factor at all voxel levels""" # Calculate where there are buildings and not. Used to elevate dem. ground = dsm - dem # Ground == 1 = ground - ground[ground < 2] = 1. + ground[ground < 2] = 1.0 # Ground == 0 = buildings - ground[ground >= 2] = 0. + ground[ground >= 2] = 0.0 # Find maximum wall height, used to estimate how many iterations of svf_calc that are required - maxWallHeight = np.max(voxelTable[:,2]) - svf_height + maxWallHeight = np.max(voxelTable[:, 2]) - svf_height # Counter to feedback current iteration counter = 1 # How many iterations are required to calculate svf for all voxels loop_range = np.arange(svf_height, maxWallHeight + svf_height, svf_height) - + # Loop for svf calculations of all voxel heights for i in loop_range: - - feedback.setProgressText('SVF calculation number ' + str(int(counter)) + ' of ' + str(int(loop_range.shape[0]))) - - feedback.setProgressText('Increasing ground level with ' + str(i) + ' meters.') + + feedback.setProgressText( + "SVF calculation number " + + str(int(counter)) + + " of " + + str(int(loop_range.shape[0])) + ) + + feedback.setProgressText( + "Increasing ground level with " + str(i) + " meters." + ) # Elevate ground in dsm temp_dsm = ((dsm + i) * ground) + (dsm * (1 - ground)) @@ -109,55 +158,102 @@ def svf_for_voxels(dsm, dem, vegdsm, vegdsm2, transVeg, scale, usevegdem, pixel_ temp_cdsm2 = vegdsm2 - i temp_cdsm2[temp_cdsm2 < 0] = 0 else: - temp_cdsm = dsm * 0. - temp_cdsm2 = dsm * 0. + temp_cdsm = dsm * 0.0 + temp_cdsm2 = dsm * 0.0 # Calculate svf. wallScheme set to 0 as only svf is estimated and nothing on the location of voxels, etc. wallScheme = 0 - ret_ = svf.svfForProcessing153(temp_dsm, temp_cdsm, temp_cdsm2, scale, usevegdem, pixel_resolution, wallScheme, dem, feedback) + ret_ = svf.svfForProcessing153( + temp_dsm, + temp_cdsm, + temp_cdsm2, + scale, + usevegdem, + pixel_resolution, + wallScheme, + dem, + feedback, + ) svfbu = ret_["svf"] if usevegdem == 0: svftotal = svfbu - svfveg = ret_['svfveg'] - svfaveg = ret_['svfaveg'] + svfveg = ret_["svfveg"] + svfaveg = ret_["svfaveg"] else: svfveg = ret_["svfveg"] svfaveg = ret_["svfaveg"] trans = transVeg / 100.0 - svftotal = (svfbu - (1 - svfveg) * (1 - trans)) + svftotal = svfbu - (1 - svfveg) * (1 - trans) # Get svf for each voxel - voxel_y = np.where(voxelTable[:, 1] == i + svf_height)# +svf_height) + voxel_y = np.where(voxelTable[:, 1] == i + svf_height) # +svf_height) for temp_y in voxel_y[0]: - svf_array[temp_y] = svftotal[int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6])] - svfbu_array[temp_y] = svfbu[int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6])] - svfveg_array[temp_y] = svfveg[int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6])] - svfaveg_array[temp_y] = svfaveg[int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6])] - svf_height_array[temp_y] = i + svf_height# +svf_height + svf_array[temp_y] = svftotal[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] + svfbu_array[temp_y] = svfbu[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] + svfveg_array[temp_y] = svfveg[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] + svfaveg_array[temp_y] = svfaveg[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] + svf_height_array[temp_y] = i + svf_height # +svf_height if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") break counter += 1 - + svf_05 = svf_array.copy() svf_05[svf_05 > 0.5] = 0.5 # Add svf arrays as volumns to voxelTable - voxelTable = np.column_stack([voxelTable, svf_height_array, svf_array, svf_05, svfbu_array, svfveg_array, svfaveg_array]) + voxelTable = np.column_stack( + [ + voxelTable, + svf_height_array, + svf_array, + svf_05, + svfbu_array, + svfveg_array, + svfaveg_array, + ] + ) return voxelTable -def svf_kmeans(dsm, dem, vegdsm, vegdsm2, wallHeights, transVeg, scale, usevegdem, pixel_resolution, voxelTable, clusters, - svf_height, svf_array, svfbu_array, svfveg_array, svfaveg_array, svf_height_array, feedback): - + +def svf_kmeans( + dsm, + dem, + vegdsm, + vegdsm2, + wallHeights, + transVeg, + scale, + usevegdem, + pixel_resolution, + voxelTable, + clusters, + svf_height, + svf_array, + svfbu_array, + svfveg_array, + svfaveg_array, + svf_height_array, + feedback, +): + # Calculate where there are buildings and not. Used to elevate dem. ground = dsm - dem # Ground == 1 = ground - ground[ground < 2] = 1. + ground[ground < 2] = 1.0 # Ground == 0 = buildings - ground[ground >= 2] = 0. + ground[ground >= 2] = 0.0 # building_heights = dsm - dem @@ -183,9 +279,11 @@ def svf_kmeans(dsm, dem, vegdsm, vegdsm2, wallHeights, transVeg, scale, usevegde counter = 0 for i in cluster_range: # cluster_heights[counter] = np.round(building_heights[kmeans_clusters == i].mean()) - cluster_heights[counter] = np.round(wallHeights[kmeans_clusters == i].mean()) - svf_height # Remove svf_height which is the voxel size to be below the top of the wall + cluster_heights[counter] = ( + np.round(wallHeights[kmeans_clusters == i].mean()) - svf_height + ) # Remove svf_height which is the voxel size to be below the top of the wall counter += 1 - + # Unique heights based on mean height of clusters, sorted from min to max cluster_heights = np.unique(cluster_heights) cluster_heights = cluster_heights[cluster_heights > 0] @@ -195,9 +293,21 @@ def svf_kmeans(dsm, dem, vegdsm, vegdsm2, wallHeights, transVeg, scale, usevegde for i in cluster_heights: if cluster_heights.shape[0] > 1: - feedback.setProgressText('SVF calculation based on K-means. Calculation ' + str(int(counter + 1)) + ' of ' + str(int(cluster_heights.shape[0])) + ' clusters.') - feedback.setProgressText('Mean wall height of cluster is ' + str(int(i + svf_height)) + ' meters. Increasing ground level with ' + str(int(i)) + ' meters.') - + feedback.setProgressText( + "SVF calculation based on K-means. Calculation " + + str(int(counter + 1)) + + " of " + + str(int(cluster_heights.shape[0])) + + " clusters." + ) + feedback.setProgressText( + "Mean wall height of cluster is " + + str(int(i + svf_height)) + + " meters. Increasing ground level with " + + str(int(i)) + + " meters." + ) + # Elevate ground in dsm temp_dsm = ((dsm + i) * ground) + (dsm * (1 - ground)) # temp_dsm = dsm[ground == 1] + temp_mean @@ -210,64 +320,138 @@ def svf_kmeans(dsm, dem, vegdsm, vegdsm2, wallHeights, transVeg, scale, usevegde temp_cdsm2 = vegdsm2 - i temp_cdsm2[temp_cdsm2 < 0] = 0 else: - temp_cdsm = dsm * 0. - temp_cdsm2 = dsm * 0. - + temp_cdsm = dsm * 0.0 + temp_cdsm2 = dsm * 0.0 + # Calculate svf. wallScheme set to 0 as only svf is estimated and nothing on the location of voxels, etc. wallScheme = 0 - ret_ = svf.svfForProcessing153(temp_dsm, temp_cdsm, temp_cdsm2, scale, usevegdem, pixel_resolution, wallScheme, dem, feedback) + ret_ = svf.svfForProcessing153( + temp_dsm, + temp_cdsm, + temp_cdsm2, + scale, + usevegdem, + pixel_resolution, + wallScheme, + dem, + feedback, + ) svfbu = ret_["svf"] if usevegdem == 0: svftotal = svfbu - svfveg = ret_['svfveg'] - svfaveg = ret_['svfaveg'] + svfveg = ret_["svfveg"] + svfaveg = ret_["svfaveg"] else: svfveg = ret_["svfveg"] svfaveg = ret_["svfaveg"] trans = transVeg / 100.0 - svftotal = (svfbu - (1 - svfveg) * (1 - trans)) + svftotal = svfbu - (1 - svfveg) * (1 - trans) # Get svf for each voxel - voxel_y = np.where(voxelTable[:, 1] == i + svf_height)# +svf_height) + voxel_y = np.where(voxelTable[:, 1] == i + svf_height) # +svf_height) for temp_y in voxel_y[0]: - svf_array[temp_y] = svftotal[int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6])] - svfbu_array[temp_y] = svfbu[int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6])] - svfveg_array[temp_y] = svfveg[int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6])] - svfaveg_array[temp_y] = svfaveg[int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6])] + svf_array[temp_y] = svftotal[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] + svfbu_array[temp_y] = svfbu[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] + svfveg_array[temp_y] = svfveg[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] + svfaveg_array[temp_y] = svfaveg[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] svf_height_array[temp_y] = i + svf_height if i == cluster_heights[-1]: - temp_data = voxelTable[voxelTable[:, 2] > i, :] # Get all walls that are taller than the mean of the lowest cluster - unique_walls = np.unique(temp_data[:, 4]) # Get their unique wall ids for slicing - for unique_wall in unique_walls: # Loop over all unique walls lower than lowest cluster - temp_wall = temp_data[temp_data[:, 4] == unique_wall, :][:, 1].max() # Max height of highest voxel in unique_wall - temp_y = np.where((voxelTable[:, 4] == unique_wall) & (voxelTable[:, 1] == temp_wall))[0] # Get row of unique_wall and highest voxel in voxelTable - - svf_array[temp_y] = 0.5 # Set svf to 0.5 as these are the highest voxels and nothing or little should obstruct it, i.e. svf = 0.5 - svfbu_array[temp_y] = svfbu[int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6])] # save svfbu for current wall pixel - svfveg_array[temp_y] = svfveg[int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6])] # save svfveg for current wall pixel - svfaveg_array[temp_y] = svfaveg[int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6])] # save svfaveg for current wall pixel - svf_height_array[temp_y] = temp_wall # set svf_height to highest voxel for current wall + temp_data = voxelTable[ + voxelTable[:, 2] > i, : + ] # Get all walls that are taller than the mean of the lowest cluster + unique_walls = np.unique( + temp_data[:, 4] + ) # Get their unique wall ids for slicing + for ( + unique_wall + ) in ( + unique_walls + ): # Loop over all unique walls lower than lowest cluster + temp_wall = temp_data[temp_data[:, 4] == unique_wall, :][ + :, 1 + ].max() # Max height of highest voxel in unique_wall + temp_y = np.where( + (voxelTable[:, 4] == unique_wall) + & (voxelTable[:, 1] == temp_wall) + )[ + 0 + ] # Get row of unique_wall and highest voxel in voxelTable + + svf_array[temp_y] = ( + 0.5 # Set svf to 0.5 as these are the highest voxels and nothing or little should obstruct it, i.e. svf = 0.5 + ) + svfbu_array[temp_y] = svfbu[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] # save svfbu for current wall pixel + svfveg_array[temp_y] = svfveg[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] # save svfveg for current wall pixel + svfaveg_array[temp_y] = svfaveg[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] # save svfaveg for current wall pixel + svf_height_array[temp_y] = ( + temp_wall # set svf_height to highest voxel for current wall + ) # Add 0.5 to highest voxel on walls that are lower than cluster with lowest mean height else: if counter == 0: - temp_data = voxelTable[voxelTable[:, 2] < i, :] # Get all walls that are lower than the mean of the lowest cluster + temp_data = voxelTable[ + voxelTable[:, 2] < i, : + ] # Get all walls that are lower than the mean of the lowest cluster else: - temp_data = voxelTable[(voxelTable[:, 2] > cluster_heights[counter - 1]) & (voxelTable[:, 2] < i), :] - unique_walls = np.unique(temp_data[:, 4]) # Get their unique wall ids for slicing - for unique_wall in unique_walls: # Loop over all unique walls lower than lowest cluster - temp_wall = temp_data[temp_data[:, 4] == unique_wall, :][:, 1].max() # Max height of highest voxel in unique_wall - temp_y = np.where((voxelTable[:, 4] == unique_wall) & (voxelTable[:, 1] == temp_wall))[0] # Get row of unique_wall and highest voxel in voxelTable - temp_svf = svftotal[int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6])] # get the calculated svf for the wall pixel to check if it is higher or lower than 0.5 - if temp_svf < 0.5: # if current wall pixel is lower than 0.5, although it is estimated above the wall, save it at the highest voxel and use for interpolation + temp_data = voxelTable[ + (voxelTable[:, 2] > cluster_heights[counter - 1]) + & (voxelTable[:, 2] < i), + :, + ] + unique_walls = np.unique( + temp_data[:, 4] + ) # Get their unique wall ids for slicing + for ( + unique_wall + ) in ( + unique_walls + ): # Loop over all unique walls lower than lowest cluster + temp_wall = temp_data[temp_data[:, 4] == unique_wall, :][ + :, 1 + ].max() # Max height of highest voxel in unique_wall + temp_y = np.where( + (voxelTable[:, 4] == unique_wall) + & (voxelTable[:, 1] == temp_wall) + )[ + 0 + ] # Get row of unique_wall and highest voxel in voxelTable + temp_svf = svftotal[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] # get the calculated svf for the wall pixel to check if it is higher or lower than 0.5 + if ( + temp_svf < 0.5 + ): # if current wall pixel is lower than 0.5, although it is estimated above the wall, save it at the highest voxel and use for interpolation svf_array[temp_y] = temp_svf - else: # else, give highest voxel a value of 0.5 + else: # else, give highest voxel a value of 0.5 svf_array[temp_y] = 0.5 - svfbu_array[temp_y] = svfbu[int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6])] # save svfbu for current wall pixel - svfveg_array[temp_y] = svfveg[int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6])] # save svfveg for current wall pixel - svfaveg_array[temp_y] = svfaveg[int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6])] # save svfaveg for current wall pixel - svf_height_array[temp_y] = temp_wall # set svf_height to highest voxel for current wall + svfbu_array[temp_y] = svfbu[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] # save svfbu for current wall pixel + svfveg_array[temp_y] = svfveg[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] # save svfveg for current wall pixel + svfaveg_array[temp_y] = svfaveg[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] # save svfaveg for current wall pixel + svf_height_array[temp_y] = ( + temp_wall # set svf_height to highest voxel for current wall + ) if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") @@ -279,25 +463,45 @@ def svf_kmeans(dsm, dem, vegdsm, vegdsm2, wallHeights, transVeg, scale, usevegde svf_05[svf_05 > 0.5] = 0.5 # Add svf arrays as volumns to voxelTable - voxelTable = np.column_stack([voxelTable, svf_height_array, svf_array, svf_05, svfbu_array, svfveg_array, svfaveg_array]) + voxelTable = np.column_stack( + [ + voxelTable, + svf_height_array, + svf_array, + svf_05, + svfbu_array, + svfveg_array, + svfaveg_array, + ] + ) return voxelTable, cluster_heights + def interpolate_svf(voxelTable, cluster_heights, kmeans): unique_wall_pixels = np.unique(voxelTable[:, 4]) unique_wall_pixels = unique_wall_pixels[unique_wall_pixels != 0] for unique_wall in unique_wall_pixels: - temp_data = voxelTable[voxelTable[:, 4] == unique_wall, :] # All data for current wall pixel - temp_heights = temp_data[temp_data[:, -1] != 0, 1] # Voxel heights for current wall pixel where svf has been calculated - temp_svf = temp_data[temp_data[:, -1] != 0, -4] # SVF at voxel heights where svf has been calculated + temp_data = voxelTable[ + voxelTable[:, 4] == unique_wall, : + ] # All data for current wall pixel + temp_heights = temp_data[ + temp_data[:, -1] != 0, 1 + ] # Voxel heights for current wall pixel where svf has been calculated + temp_svf = temp_data[ + temp_data[:, -1] != 0, -4 + ] # SVF at voxel heights where svf has been calculated if temp_heights.size == 1: new_svf = temp_data[temp_data[:, -4] != 0, -4] new_svf[new_svf == 0] = new_svf[new_svf != 0] - elif temp_heights.size > 1: # Interpolate - new_svf = np.interp(temp_data[:, 1], temp_heights, temp_svf) # SVF for all voxels from interpolated values of calculated SVF at different heights (depend on svf_height) - - voxelTable[voxelTable[:, 4] == unique_wall, -4] = new_svf # Add the new SVFs to table + elif temp_heights.size > 1: # Interpolate + new_svf = np.interp( + temp_data[:, 1], temp_heights, temp_svf + ) # SVF for all voxels from interpolated values of calculated SVF at different heights (depend on svf_height) - return voxelTable + voxelTable[voxelTable[:, 4] == unique_wall, -4] = ( + new_svf # Add the new SVFs to table + ) + return voxelTable diff --git a/functions/svf_functions.py b/functions/svf_functions.py index b1305f2..5ce4016 100644 --- a/functions/svf_functions.py +++ b/functions/svf_functions.py @@ -1,56 +1,126 @@ import numpy as np from ..util import shadowingfunctions as shadow from ..util.SEBESOLWEIGCommonFiles.create_patches import create_patches + # from ..functions.wallalgorithms import findwalls from ..functions import wallalgorithms as wa from ..functions import svf_for_voxels as svfv -from ..util.SEBESOLWEIGCommonFiles import shadowingfunction_wallheight_13 as shb -from ..util.SEBESOLWEIGCommonFiles import shadowingfunction_wallheight_23 as shbv +from ..util.SEBESOLWEIGCommonFiles import ( + shadowingfunction_wallheight_13 as shb, +) +from ..util.SEBESOLWEIGCommonFiles import ( + shadowingfunction_wallheight_23 as shbv, +) + # remove from ..util.misc import saveraster from osgeo.gdalconst import * from osgeo import gdal, osr + def annulus_weight(altitude, aziinterval): - n = 90. - steprad = (360./aziinterval) * (np.pi/180.) - annulus = 91.-altitude - w = (1./(2.*np.pi)) * np.sin(np.pi / (2.*n)) * np.sin((np.pi * (2. * annulus - 1.)) / (2. * n)) + n = 90.0 + steprad = (360.0 / aziinterval) * (np.pi / 180.0) + annulus = 91.0 - altitude + w = ( + (1.0 / (2.0 * np.pi)) + * np.sin(np.pi / (2.0 * n)) + * np.sin((np.pi * (2.0 * annulus - 1.0)) / (2.0 * n)) + ) weight = steprad * w return weight + def svf_angles_100121(): - azi1 = np.arange(1., 360., 360./16.) #%22.5 - azi2 = np.arange(12., 360., 360./16.) #%22.5 - azi3 = np.arange(5., 360., 360./32.) #%11.25 - azi4 = np.arange(2., 360., 360./32.) #%11.25 - azi5 = np.arange(4., 360., 360./40.) #%9 - azi6 = np.arange(7., 360., 360./48.) #%7.50 - azi7 = np.arange(6., 360., 360./48.) #%7.50 - azi8 = np.arange(1., 360., 360./48.) #%7.50 - azi9 = np.arange(4., 359., 360./52.) #%6.9231 - azi10 = np.arange(5., 360., 360./52.) #%6.9231 - azi11 = np.arange(1., 360., 360./48.) #%7.50 - azi12 = np.arange(0., 359., 360./44.) #%8.1818 - azi13 = np.arange(3., 360., 360./44.) #%8.1818 - azi14 = np.arange(2., 360., 360./40.) #%9 - azi15 = np.arange(7., 360., 360./32.) #%10 - azi16 = np.arange(3., 360., 360./24.) #%11.25 - azi17 = np.arange(10., 360., 360./16.) #%15 - azi18 = np.arange(19., 360., 360./12.) #%22.5 - azi19 = np.arange(17., 360., 360./8.) #%45 - azi20 = 0. #%360 - iazimuth = np.array(np.hstack((azi1, azi2, azi3, azi4, azi5, azi6, azi7, azi8, azi9, azi10, azi11, azi12, azi13, - azi14, azi15, azi16, azi17, azi18, azi19, azi20))) - aziinterval = np.array(np.hstack((16., 16., 32., 32., 40., 48., 48., 48., 52., 52., 48., 44., 44., 40., 32., 24., - 16., 12., 8., 1.))) - angleresult = {'iazimuth': iazimuth, 'aziinterval': aziinterval} + azi1 = np.arange(1.0, 360.0, 360.0 / 16.0) # %22.5 + azi2 = np.arange(12.0, 360.0, 360.0 / 16.0) # %22.5 + azi3 = np.arange(5.0, 360.0, 360.0 / 32.0) # %11.25 + azi4 = np.arange(2.0, 360.0, 360.0 / 32.0) # %11.25 + azi5 = np.arange(4.0, 360.0, 360.0 / 40.0) # %9 + azi6 = np.arange(7.0, 360.0, 360.0 / 48.0) # %7.50 + azi7 = np.arange(6.0, 360.0, 360.0 / 48.0) # %7.50 + azi8 = np.arange(1.0, 360.0, 360.0 / 48.0) # %7.50 + azi9 = np.arange(4.0, 359.0, 360.0 / 52.0) # %6.9231 + azi10 = np.arange(5.0, 360.0, 360.0 / 52.0) # %6.9231 + azi11 = np.arange(1.0, 360.0, 360.0 / 48.0) # %7.50 + azi12 = np.arange(0.0, 359.0, 360.0 / 44.0) # %8.1818 + azi13 = np.arange(3.0, 360.0, 360.0 / 44.0) # %8.1818 + azi14 = np.arange(2.0, 360.0, 360.0 / 40.0) # %9 + azi15 = np.arange(7.0, 360.0, 360.0 / 32.0) # %10 + azi16 = np.arange(3.0, 360.0, 360.0 / 24.0) # %11.25 + azi17 = np.arange(10.0, 360.0, 360.0 / 16.0) # %15 + azi18 = np.arange(19.0, 360.0, 360.0 / 12.0) # %22.5 + azi19 = np.arange(17.0, 360.0, 360.0 / 8.0) # %45 + azi20 = 0.0 # %360 + iazimuth = np.array( + np.hstack( + ( + azi1, + azi2, + azi3, + azi4, + azi5, + azi6, + azi7, + azi8, + azi9, + azi10, + azi11, + azi12, + azi13, + azi14, + azi15, + azi16, + azi17, + azi18, + azi19, + azi20, + ) + ) + ) + aziinterval = np.array( + np.hstack( + ( + 16.0, + 16.0, + 32.0, + 32.0, + 40.0, + 48.0, + 48.0, + 48.0, + 52.0, + 52.0, + 48.0, + 44.0, + 44.0, + 40.0, + 32.0, + 24.0, + 16.0, + 12.0, + 8.0, + 1.0, + ) + ) + ) + angleresult = {"iazimuth": iazimuth, "aziinterval": aziinterval} return angleresult -def svfForProcessing153(dsm, vegdem, vegdem2, scale, usevegdem, pixel_resolution, wallScheme, demlayer, feedback): +def svfForProcessing153( + dsm, + vegdem, + vegdem2, + scale, + usevegdem, + pixel_resolution, + wallScheme, + demlayer, + feedback, +): rows = dsm.shape[0] cols = dsm.shape[1] svf = np.zeros([rows, cols]) @@ -82,18 +152,26 @@ def svfForProcessing153(dsm, vegdem, vegdem2, scale, usevegdem, pixel_resolution # % Bush separation bush = np.logical_not((vegdem2 * vegdem)) * vegdem - #index = int(0) + # index = int(0) # patch_option = 1 # 145 patches - patch_option = 2 # 153 patches + patch_option = 2 # 153 patches # patch_option = 3 # 306 patches # patch_option = 4 # 612 patches - - # Create patches based on patch_option - skyvaultalt, skyvaultazi, annulino, skyvaultaltint, aziinterval, skyvaultaziint, azistart = create_patches(patch_option) - skyvaultaziint = np.array([360/patches for patches in aziinterval]) - iazimuth = np.hstack(np.zeros((1, np.sum(aziinterval)))) # Nils + # Create patches based on patch_option + ( + skyvaultalt, + skyvaultazi, + annulino, + skyvaultaltint, + aziinterval, + skyvaultaziint, + azistart, + ) = create_patches(patch_option) + + skyvaultaziint = np.array([360 / patches for patches in aziinterval]) + iazimuth = np.hstack(np.zeros((1, np.sum(aziinterval)))) # Nils shmat = np.zeros((rows, cols, np.sum(aziinterval))) vegshmat = np.zeros((rows, cols, np.sum(aziinterval))) @@ -102,7 +180,16 @@ def svfForProcessing153(dsm, vegdem, vegdem2, scale, usevegdem, pixel_resolution # Preparations for wall temperature scheme if wallScheme: feedback.setProgressText("Estimating view factors for wall voxels") - voxelTable, voxelId_list, wall_dict, walls, aspect, uniqueWallIDs, wall2d_id, voxel_height = svfv.wallscheme_prepare(dsm, scale, pixel_resolution, feedback) + ( + voxelTable, + voxelId_list, + wall_dict, + walls, + aspect, + uniqueWallIDs, + wall2d_id, + voxel_height, + ) = svfv.wallscheme_prepare(dsm, scale, pixel_resolution, feedback) # Rasters to fill with values in loop all_buildIDSeen = np.zeros((rows, cols, skyvaultalt.shape[0])) @@ -119,8 +206,8 @@ def svfForProcessing153(dsm, vegdem, vegdem2, scale, usevegdem, pixel_resolution for j in range(0, skyvaultaltint.shape[0]): for k in range(0, int(360 / skyvaultaziint[j])): iazimuth[index] = k * skyvaultaziint[j] + azistart[j] - if iazimuth[index] > 360.: - iazimuth[index] = iazimuth[index] - 360. + if iazimuth[index] > 360.0: + iazimuth[index] = iazimuth[index] - 360.0 index = index + 1 aziintervalaniso = np.ceil(aziinterval / 2.0) index = int(0) @@ -135,36 +222,99 @@ def svfForProcessing153(dsm, vegdem, vegdem2, scale, usevegdem, pixel_resolution # Casting shadow if wallScheme: if usevegdem == 1: - vegsh, sh, vbshvegsh, wallsh, wallsun, wallshve, facesh, facesun = shbv.shadowingfunction_wallheight_23(dsm, vegdem, vegdem2, azimuth, altitude, scale, amaxvalue, bush, walls, aspect * np.pi / 180) + ( + vegsh, + sh, + vbshvegsh, + wallsh, + wallsun, + wallshve, + facesh, + facesun, + ) = shbv.shadowingfunction_wallheight_23( + dsm, + vegdem, + vegdem2, + azimuth, + altitude, + scale, + amaxvalue, + bush, + walls, + aspect * np.pi / 180, + ) vegshmat[:, :, index] = vegsh vbshvegshmat[:, :, index] = vbshvegsh else: - sh, wallsh, wallsun, facesh, facesun = shb.shadowingfunction_wallheight_13(dsm, azimuth, altitude, scale, walls, aspect * np.pi / 180.) + sh, wallsh, wallsun, facesh, facesun = ( + shb.shadowingfunction_wallheight_13( + dsm, + azimuth, + altitude, + scale, + walls, + aspect * np.pi / 180.0, + ) + ) vegsh = np.ones((sh.shape[0], sh.shape[1])).astype(float) - vbshvegsh = np.ones((sh.shape[0], sh.shape[1])).astype(float) + vbshvegsh = np.ones((sh.shape[0], sh.shape[1])).astype( + float + ) vegshmat[:, :, index] = vegsh vbshvegshmat[:, :, index] = vbshvegsh else: if usevegdem == 1: - shadowresult = shadow.shadowingfunction_20(dsm, vegdem, vegdem2, azimuth, altitude, - scale, amaxvalue, bush, feedback, 1) + shadowresult = shadow.shadowingfunction_20( + dsm, + vegdem, + vegdem2, + azimuth, + altitude, + scale, + amaxvalue, + bush, + feedback, + 1, + ) vegsh = shadowresult["vegsh"] vbshvegsh = shadowresult["vbshvegsh"] vegshmat[:, :, index] = vegsh vbshvegshmat[:, :, index] = vbshvegsh sh = shadowresult["sh"] else: - sh = shadow.shadowingfunctionglobalradiation(dsm, azimuth, altitude, scale, feedback, 1) + sh = shadow.shadowingfunctionglobalradiation( + dsm, azimuth, altitude, scale, feedback, 1 + ) shmat[:, :, index] = sh - + # Wall temperature scheme, i.e. finding out which voxel is seen from each pixel, where direction is patch azimuth and altitude if wallScheme: - all_buildIDSeen[:,:, index], all_voxelHeight[:,:, index], all_voxelId[:,:, index] = shadow.shadowingfunction_findwallID(dsm, azimuth, altitude, scale, walls, uniqueWallIDs, demlayer, wall2d_id, voxel_height, voxelId_list, facesh, wall_dict, sh) + ( + all_buildIDSeen[:, :, index], + all_voxelHeight[:, :, index], + all_voxelId[:, :, index], + ) = shadow.shadowingfunction_findwallID( + dsm, + azimuth, + altitude, + scale, + walls, + uniqueWallIDs, + demlayer, + wall2d_id, + voxel_height, + voxelId_list, + facesh, + wall_dict, + sh, + ) # Calculate svfs - for k in np.arange(annulino[int(i)]+1, (annulino[int(i+1.)])+1): - weight = annulus_weight(k, aziinterval[i])*sh + for k in np.arange( + annulino[int(i)] + 1, (annulino[int(i + 1.0)]) + 1 + ): + weight = annulus_weight(k, aziinterval[i]) * sh svf = svf + weight weight = annulus_weight(k, aziintervalaniso[i]) * sh if (azimuth >= 0) and (azimuth < 180): @@ -177,7 +327,9 @@ def svfForProcessing153(dsm, vegdem, vegdem2, scale, usevegdem, pixel_resolution svfN = svfN + weight if usevegdem == 1: - for k in np.arange(annulino[int(i)] + 1, (annulino[int(i + 1.)]) + 1): + for k in np.arange( + annulino[int(i)] + 1, (annulino[int(i + 1.0)]) + 1 + ): # % changed to include 90 weight = annulus_weight(k, aziinterval[i]) svfveg = svfveg + weight * vegsh @@ -197,45 +349,63 @@ def svfForProcessing153(dsm, vegdem, vegdem2, scale, usevegdem, pixel_resolution svfNaveg = svfNaveg + weight * vbshvegsh index += 1 - feedback.setProgress(int(index * (100. / np.sum(aziinterval)))) + feedback.setProgress(int(index * (100.0 / np.sum(aziinterval)))) svfS = svfS + 3.0459e-004 svfW = svfW + 3.0459e-004 # % Last azimuth is 90. Hence, manual add of last annuli for svfS and SVFW # %Forcing svf not be greater than 1 (some MATLAB crazyness) - svf[(svf > 1.)] = 1. - svfE[(svfE > 1.)] = 1. - svfS[(svfS > 1.)] = 1. - svfW[(svfW > 1.)] = 1. - svfN[(svfN > 1.)] = 1. + svf[(svf > 1.0)] = 1.0 + svfE[(svfE > 1.0)] = 1.0 + svfS[(svfS > 1.0)] = 1.0 + svfW[(svfW > 1.0)] = 1.0 + svfN[(svfN > 1.0)] = 1.0 if usevegdem == 1: last = np.zeros((rows, cols)) - last[(vegdem2 == 0.)] = 3.0459e-004 + last[(vegdem2 == 0.0)] = 3.0459e-004 svfSveg = svfSveg + last svfWveg = svfWveg + last svfSaveg = svfSaveg + last svfWaveg = svfWaveg + last # %Forcing svf not be greater than 1 (some MATLAB crazyness) - svfveg[(svfveg > 1.)] = 1. - svfEveg[(svfEveg > 1.)] = 1. - svfSveg[(svfSveg > 1.)] = 1. - svfWveg[(svfWveg > 1.)] = 1. - svfNveg[(svfNveg > 1.)] = 1. - svfaveg[(svfaveg > 1.)] = 1. - svfEaveg[(svfEaveg > 1.)] = 1. - svfSaveg[(svfSaveg > 1.)] = 1. - svfWaveg[(svfWaveg > 1.)] = 1. - svfNaveg[(svfNaveg > 1.)] = 1. - - svfresult = {'svf': svf, 'svfE': svfE, 'svfS': svfS, 'svfW': svfW, 'svfN': svfN, - 'svfveg': svfveg, 'svfEveg': svfEveg, 'svfSveg': svfSveg, 'svfWveg': svfWveg, - 'svfNveg': svfNveg, 'svfaveg': svfaveg, 'svfEaveg': svfEaveg, 'svfSaveg': svfSaveg, - 'svfWaveg': svfWaveg, 'svfNaveg': svfNaveg, 'shmat': shmat, 'vegshmat': vegshmat, 'vbshvegshmat': vbshvegshmat, - 'voxelIds': all_voxelId, 'voxelTable': voxelTable, 'walls': walls} - # , - # 'vbshvegshmat': vbshvegshmat, 'wallshmat': wallshmat, 'wallsunmat': wallsunmat, - # 'wallshvemat': wallshvemat, 'facesunmat': facesunmat} + svfveg[(svfveg > 1.0)] = 1.0 + svfEveg[(svfEveg > 1.0)] = 1.0 + svfSveg[(svfSveg > 1.0)] = 1.0 + svfWveg[(svfWveg > 1.0)] = 1.0 + svfNveg[(svfNveg > 1.0)] = 1.0 + svfaveg[(svfaveg > 1.0)] = 1.0 + svfEaveg[(svfEaveg > 1.0)] = 1.0 + svfSaveg[(svfSaveg > 1.0)] = 1.0 + svfWaveg[(svfWaveg > 1.0)] = 1.0 + svfNaveg[(svfNaveg > 1.0)] = 1.0 + + svfresult = { + "svf": svf, + "svfE": svfE, + "svfS": svfS, + "svfW": svfW, + "svfN": svfN, + "svfveg": svfveg, + "svfEveg": svfEveg, + "svfSveg": svfSveg, + "svfWveg": svfWveg, + "svfNveg": svfNveg, + "svfaveg": svfaveg, + "svfEaveg": svfEaveg, + "svfSaveg": svfSaveg, + "svfWaveg": svfWaveg, + "svfNaveg": svfNaveg, + "shmat": shmat, + "vegshmat": vegshmat, + "vbshvegshmat": vbshvegshmat, + "voxelIds": all_voxelId, + "voxelTable": voxelTable, + "walls": walls, + } + # , + # 'vbshvegshmat': vbshvegshmat, 'wallshmat': wallshmat, 'wallsunmat': wallsunmat, + # 'wallshvemat': wallshvemat, 'facesunmat': facesunmat} return svfresult @@ -274,38 +444,54 @@ def svfForProcessing655(dsm, vegdem, vegdem2, scale, usevegdem, feedback): # shmat = np.zeros((rows, cols, 145)) # vegshmat = np.zeros((rows, cols, 145)) - noa = 19. - #% No. of anglesteps minus 1 - step = 89./noa - iangle = np.array(np.hstack((np.arange(step/2., 89., step), 90.))) - annulino = np.array(np.hstack((np.round(np.arange(0., 89., step)), 90.))) + noa = 19.0 + # % No. of anglesteps minus 1 + step = 89.0 / noa + iangle = np.array(np.hstack((np.arange(step / 2.0, 89.0, step), 90.0))) + annulino = np.array( + np.hstack((np.round(np.arange(0.0, 89.0, step)), 90.0)) + ) angleresult = svf_angles_100121() aziinterval = angleresult["aziinterval"] iazimuth = angleresult["iazimuth"] - aziintervalaniso = np.ceil((aziinterval/2.)) - index = 1. + aziintervalaniso = np.ceil((aziinterval / 2.0)) + index = 1.0 - for i in np.arange(0, iangle.shape[0]-1): + for i in np.arange(0, iangle.shape[0] - 1): for j in np.arange(0, (aziinterval[int(i)])): if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") break altitude = iangle[int(i)] - azimuth = iazimuth[int(index)-1] + azimuth = iazimuth[int(index) - 1] # Casting shadow if usevegdem == 1: - shadowresult = shadow.shadowingfunction_20(dsm, vegdem, vegdem2, azimuth, altitude, - scale, amaxvalue, bush, feedback, 1) + shadowresult = shadow.shadowingfunction_20( + dsm, + vegdem, + vegdem2, + azimuth, + altitude, + scale, + amaxvalue, + bush, + feedback, + 1, + ) vegsh = shadowresult["vegsh"] vbshvegsh = shadowresult["vbshvegsh"] sh = shadowresult["sh"] else: - sh = shadow.shadowingfunctionglobalradiation(dsm, azimuth, altitude, scale, feedback, 1) + sh = shadow.shadowingfunctionglobalradiation( + dsm, azimuth, altitude, scale, feedback, 1 + ) # Calculate svfs - for k in np.arange(annulino[int(i)]+1, (annulino[int(i+1.)])+1): - weight = annulus_weight(k, aziinterval[i])*sh + for k in np.arange( + annulino[int(i)] + 1, (annulino[int(i + 1.0)]) + 1 + ): + weight = annulus_weight(k, aziinterval[i]) * sh svf = svf + weight weight = annulus_weight(k, aziintervalaniso[i]) * sh if (azimuth >= 0) and (azimuth < 180): @@ -318,7 +504,9 @@ def svfForProcessing655(dsm, vegdem, vegdem2, scale, usevegdem, feedback): svfN = svfN + weight if usevegdem == 1: - for k in np.arange(annulino[int(i)] + 1, (annulino[int(i + 1.)]) + 1): + for k in np.arange( + annulino[int(i)] + 1, (annulino[int(i + 1.0)]) + 1 + ): # % changed to include 90 weight = annulus_weight(k, aziinterval[i]) svfveg = svfveg + weight * vegsh @@ -338,40 +526,53 @@ def svfForProcessing655(dsm, vegdem, vegdem2, scale, usevegdem, feedback): svfNaveg = svfNaveg + weight * vbshvegsh index += 1 - feedback.setProgress(int(index * (100. / 655.))) + feedback.setProgress(int(index * (100.0 / 655.0))) svfS = svfS + 3.0459e-004 svfW = svfW + 3.0459e-004 # % Last azimuth is 90. Hence, manual add of last annuli for svfS and SVFW # %Forcing svf not be greater than 1 (some MATLAB crazyness) - svf[(svf > 1.)] = 1. - svfE[(svfE > 1.)] = 1. - svfS[(svfS > 1.)] = 1. - svfW[(svfW > 1.)] = 1. - svfN[(svfN > 1.)] = 1. + svf[(svf > 1.0)] = 1.0 + svfE[(svfE > 1.0)] = 1.0 + svfS[(svfS > 1.0)] = 1.0 + svfW[(svfW > 1.0)] = 1.0 + svfN[(svfN > 1.0)] = 1.0 if usevegdem == 1: last = np.zeros((rows, cols)) - last[(vegdem2 == 0.)] = 3.0459e-004 + last[(vegdem2 == 0.0)] = 3.0459e-004 svfSveg = svfSveg + last svfWveg = svfWveg + last svfSaveg = svfSaveg + last svfWaveg = svfWaveg + last # %Forcing svf not be greater than 1 (some MATLAB crazyness) - svfveg[(svfveg > 1.)] = 1. - svfEveg[(svfEveg > 1.)] = 1. - svfSveg[(svfSveg > 1.)] = 1. - svfWveg[(svfWveg > 1.)] = 1. - svfNveg[(svfNveg > 1.)] = 1. - svfaveg[(svfaveg > 1.)] = 1. - svfEaveg[(svfEaveg > 1.)] = 1. - svfSaveg[(svfSaveg > 1.)] = 1. - svfWaveg[(svfWaveg > 1.)] = 1. - svfNaveg[(svfNaveg > 1.)] = 1. - - svfresult = {'svf': svf, 'svfE': svfE, 'svfS': svfS, 'svfW': svfW, 'svfN': svfN, - 'svfveg': svfveg, 'svfEveg': svfEveg, 'svfSveg': svfSveg, 'svfWveg': svfWveg, - 'svfNveg': svfNveg, 'svfaveg': svfaveg, 'svfEaveg': svfEaveg, 'svfSaveg': svfSaveg, - 'svfWaveg': svfWaveg, 'svfNaveg': svfNaveg} + svfveg[(svfveg > 1.0)] = 1.0 + svfEveg[(svfEveg > 1.0)] = 1.0 + svfSveg[(svfSveg > 1.0)] = 1.0 + svfWveg[(svfWveg > 1.0)] = 1.0 + svfNveg[(svfNveg > 1.0)] = 1.0 + svfaveg[(svfaveg > 1.0)] = 1.0 + svfEaveg[(svfEaveg > 1.0)] = 1.0 + svfSaveg[(svfSaveg > 1.0)] = 1.0 + svfWaveg[(svfWaveg > 1.0)] = 1.0 + svfNaveg[(svfNaveg > 1.0)] = 1.0 + + svfresult = { + "svf": svf, + "svfE": svfE, + "svfS": svfS, + "svfW": svfW, + "svfN": svfN, + "svfveg": svfveg, + "svfEveg": svfEveg, + "svfSveg": svfSveg, + "svfWveg": svfWveg, + "svfNveg": svfNveg, + "svfaveg": svfaveg, + "svfEaveg": svfEaveg, + "svfSaveg": svfSaveg, + "svfWaveg": svfWaveg, + "svfNaveg": svfNaveg, + } return svfresult diff --git a/functions/wallalgorithms.py b/functions/wallalgorithms.py index 4ce469b..a37e12a 100644 --- a/functions/wallalgorithms.py +++ b/functions/wallalgorithms.py @@ -1,15 +1,18 @@ from builtins import range + # -*- coding: utf-8 -*- -__author__ = 'xlinfr' +__author__ = "xlinfr" import math import numpy as np + # import scipy.misc as sc import scipy.ndimage as sc from scipy.ndimage import maximum_filter -def findwalls_sp(arr_dsm, walllimit, footprint = False): - # This function identifies walls based on a DSM and a wall height limit. + +def findwalls_sp(arr_dsm, walllimit, footprint=False): + # This function identifies walls based on a DSM and a wall height limit. # arr_dsm = DSM # walllimit = wall height limit # footprint = footprint for maximum filter, default = np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) @@ -17,33 +20,34 @@ def findwalls_sp(arr_dsm, walllimit, footprint = False): # Get the shape of the input array col, row = arr_dsm.shape walls = np.zeros((col, row)) - + # Create a padded version of the array - padded_a = np.pad(arr_dsm, pad_width=1, mode='edge') - + padded_a = np.pad(arr_dsm, pad_width=1, mode="edge") + # Default footprint for cardinal points if footprint is False: - footprint = np.array([[0, 1, 0], - [1, 0, 1], - [0, 1, 0]]) - + footprint = np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) + # Use maximum_filter with the custom footprint - max_neighbors = maximum_filter(padded_a, footprint=footprint, mode='constant', cval=0) - + max_neighbors = maximum_filter( + padded_a, footprint=footprint, mode="constant", cval=0 + ) + # Identify wall pixels: walls are where the max neighbors are greater than the original DSM walls = max_neighbors[1:-1, 1:-1] - arr_dsm - + # Apply wall height limit walls[walls < walllimit] = 0 - + # Set the edges to zero - walls[0:walls.shape[0], 0] = 0 - walls[0:walls.shape[0], walls.shape[1] - 1] = 0 - walls[0, 0:walls.shape[1]] = 0 - walls[walls.shape[0] - 1, 0:walls.shape[1]] = 0 - + walls[0 : walls.shape[0], 0] = 0 + walls[0 : walls.shape[0], walls.shape[1] - 1] = 0 + walls[0, 0 : walls.shape[1]] = 0 + walls[walls.shape[0] - 1, 0 : walls.shape[1]] = 0 + return walls + def findwalls(a, walllimit, feedback, total): # This function identifies walls based on a DSM and a wall-height limit # Walls are represented by outer pixels within building footprints @@ -57,12 +61,12 @@ def findwalls(a, walllimit, feedback, total): walls = np.zeros((col, row)) domain = np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) index = 0 - for i in np.arange(1, row-1): + for i in np.arange(1, row - 1): if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") break - for j in np.arange(1, col-1): - dom = a[j-1:j+2, i-1:i+2] + for j in np.arange(1, col - 1): + dom = a[j - 1 : j + 2, i - 1 : i + 2] walls[j, i] = np.max(dom[np.where(domain == 1)]) # new 20171006 index = index + 1 feedback.setProgress(int(index * total)) @@ -70,10 +74,10 @@ def findwalls(a, walllimit, feedback, total): walls = np.copy(walls - a) # new 20171006 walls[(walls < walllimit)] = 0 - walls[0:walls.shape[0], 0] = 0 - walls[0:walls.shape[0], walls.shape[1] - 1] = 0 - walls[0, 0:walls.shape[0]] = 0 - walls[walls.shape[0] - 1, 0:walls.shape[1]] = 0 + walls[0 : walls.shape[0], 0] = 0 + walls[0 : walls.shape[0], walls.shape[1] - 1] = 0 + walls[0, 0 : walls.shape[0]] = 0 + walls[walls.shape[0] - 1, 0 : walls.shape[1]] = 0 return walls @@ -107,8 +111,8 @@ def filter1Goodwin_as_aspect_v3(walls_for_dir, scale, a, feedback, total): if filtersize % 2 == 0: filtersize = filtersize + 1 - filthalveceil = int(np.ceil(filtersize / 2.)) - filthalvefloor = int(np.floor(filtersize / 2.)) + filthalveceil = int(np.ceil(filtersize / 2.0)) + filthalvefloor = int(np.floor(filtersize / 2.0)) filtmatrix = np.zeros((int(filtersize), int(filtersize))) buildfilt = np.zeros((int(filtersize), int(filtersize))) @@ -116,25 +120,31 @@ def filter1Goodwin_as_aspect_v3(walls_for_dir, scale, a, feedback, total): filtmatrix[:, filthalveceil - 1] = 1 n = filtmatrix.shape[0] - 1 buildfilt[filthalveceil - 1, 0:filthalvefloor] = 1 - buildfilt[filthalveceil - 1, filthalveceil: int(filtersize)] = 2 + buildfilt[filthalveceil - 1, filthalveceil : int(filtersize)] = 2 y = np.zeros((row, col)) # final direction z = np.zeros((row, col)) # temporary direction x = np.zeros((row, col)) # building side walls[walls > 0] = 1 - for h in range(0, 180): # =0:1:180 #%increased resolution to 1 deg 20140911 + for h in range( + 0, 180 + ): # =0:1:180 #%increased resolution to 1 deg 20140911 if feedback is not None: feedback.setProgress(int(h * total)) if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") break - filtmatrix1temp = sc.rotate(filtmatrix, h, order=1, reshape=False, mode='nearest') # bilinear + filtmatrix1temp = sc.rotate( + filtmatrix, h, order=1, reshape=False, mode="nearest" + ) # bilinear filtmatrix1 = np.round(filtmatrix1temp) # filtmatrix1temp = sc.imrotate(filtmatrix, h, 'bilinear') # filtmatrix1 = np.round(filtmatrix1temp / 255.) # filtmatrixbuildtemp = sc.imrotate(buildfilt, h, 'nearest') - filtmatrixbuildtemp = sc.rotate(buildfilt, h, order=0, reshape=False, mode='nearest') # Nearest neighbor + filtmatrixbuildtemp = sc.rotate( + buildfilt, h, order=0, reshape=False, mode="nearest" + ) # Nearest neighbor # filtmatrixbuild = np.round(filtmatrixbuildtemp / 127.) filtmatrixbuild = np.round(filtmatrixbuildtemp) index = 270 - h @@ -151,15 +161,29 @@ def filter1Goodwin_as_aspect_v3(walls_for_dir, scale, a, feedback, total): filtmatrix1[0, n] = 1 filtmatrix1[n, 0] = 1 - for i in range(int(filthalveceil) - 1, row - int(filthalveceil) - 1): # i=filthalveceil:sizey-filthalveceil - for j in range(int(filthalveceil) - 1, col - int(filthalveceil) - 1): # (j=filthalveceil:sizex-filthalveceil + for i in range( + int(filthalveceil) - 1, row - int(filthalveceil) - 1 + ): # i=filthalveceil:sizey-filthalveceil + for j in range( + int(filthalveceil) - 1, col - int(filthalveceil) - 1 + ): # (j=filthalveceil:sizex-filthalveceil if walls[i, j] == 1: - wallscut = walls[i - filthalvefloor:i + filthalvefloor + 1, - j - filthalvefloor:j + filthalvefloor + 1] * filtmatrix1 - dsmcut = a[i - filthalvefloor:i + filthalvefloor + 1, j - filthalvefloor:j + filthalvefloor + 1] + wallscut = ( + walls[ + i - filthalvefloor : i + filthalvefloor + 1, + j - filthalvefloor : j + filthalvefloor + 1, + ] + * filtmatrix1 + ) + dsmcut = a[ + i - filthalvefloor : i + filthalvefloor + 1, + j - filthalvefloor : j + filthalvefloor + 1, + ] if z[i, j] < wallscut.sum(): # sum(sum(wallscut)) z[i, j] = wallscut.sum() # sum(sum(wallscut)); - if np.sum(dsmcut[filtmatrixbuild == 1]) > np.sum(dsmcut[filtmatrixbuild == 2]): + if np.sum(dsmcut[filtmatrixbuild == 1]) > np.sum( + dsmcut[filtmatrixbuild == 2] + ): x[i, j] = 1 else: x[i, j] = 2 @@ -171,28 +195,28 @@ def filter1Goodwin_as_aspect_v3(walls_for_dir, scale, a, feedback, total): grad, asp = get_ders(a, scale) - y = y + ((walls == 1) * 1) * ((y == 0) * 1) * (asp / (math.pi / 180.)) + y = y + ((walls == 1) * 1) * ((y == 0) * 1) * (asp / (math.pi / 180.0)) dirwalls = y return dirwalls -def cart2pol(x, y, units='deg'): +def cart2pol(x, y, units="deg"): radius = np.sqrt(x**2 + y**2) theta = np.arctan2(y, x) - if units in ['deg', 'degs']: + if units in ["deg", "degs"]: theta = theta * 180 / np.pi return theta, radius def get_ders(dsm, scale): # dem,_,_=read_dem_grid(dem_file) - dx = 1/scale + dx = 1 / scale # dx=0.5 fy, fx = np.gradient(dsm, dx, dx) - asp, grad = cart2pol(fy, fx, 'rad') + asp, grad = cart2pol(fy, fx, "rad") grad = np.arctan(grad) asp = asp * -1 asp = asp + (asp < 0) * (np.pi * 2) - return grad, asp \ No newline at end of file + return grad, asp diff --git a/plugin_upload.py b/plugin_upload.py index 2d8cddf..a2a2b96 100644 --- a/plugin_upload.py +++ b/plugin_upload.py @@ -1,25 +1,26 @@ #!/usr/bin/env python # coding=utf-8 """This script uploads a plugin package to the plugin repository. - Authors: A. Pasotti, V. Picavet - git sha : $TemplateVCSFormat +Authors: A. Pasotti, V. Picavet +git sha : $TemplateVCSFormat """ import defusedxml.xmlrpc -defusedxml.xmlrpc.monkey_patch() # + +defusedxml.xmlrpc.monkey_patch() # import sys import getpass -import xmlrpc.client # nosec B411 +import xmlrpc.client # nosec B411 from optparse import OptionParser standard_library.install_aliases() # Configuration -PROTOCOL = 'https' -SERVER = 'plugins.qgis.org' -PORT = '443' -ENDPOINT = '/plugins/RPC2/' +PROTOCOL = "https" +SERVER = "plugins.qgis.org" +PORT = "443" +ENDPOINT = "/plugins/RPC2/" VERBOSE = False @@ -29,21 +30,25 @@ def main(parameters, arguments): :param parameters: Command line parameters. :param arguments: Command line arguments. """ - address = "{protocol}://{username}:{password}@{server}:{port}{endpoint}".format( - protocol=PROTOCOL, - username=parameters.username, - password=parameters.password, - server=parameters.server, - port=parameters.port, - endpoint=ENDPOINT) + address = ( + "{protocol}://{username}:{password}@{server}:{port}{endpoint}".format( + protocol=PROTOCOL, + username=parameters.username, + password=parameters.password, + server=parameters.server, + port=parameters.port, + endpoint=ENDPOINT, + ) + ) print("Connecting to: %s" % hide_password(address)) server = xmlrpc.client.ServerProxy(address, verbose=VERBOSE) try: - with open(arguments[0], 'rb') as handle: + with open(arguments[0], "rb") as handle: plugin_id, version_id = server.plugin.upload( - xmlrpc.client.Binary(handle.read())) + xmlrpc.client.Binary(handle.read()) + ) print("Plugin ID: %s" % plugin_id) print("Version ID: %s" % version_id) except xmlrpc.client.ProtocolError as err: @@ -67,28 +72,45 @@ def hide_password(url, start=6): :param start: Position of start of password. :type start: int """ - start_position = url.find(':', start) + 1 - end_position = url.find('@') + start_position = url.find(":", start) + 1 + end_position = url.find("@") return "%s%s%s" % ( url[:start_position], - '*' * (end_position - start_position), - url[end_position:]) + "*" * (end_position - start_position), + url[end_position:], + ) if __name__ == "__main__": parser = OptionParser(usage="%prog [options] plugin.zip") parser.add_option( - "-w", "--password", dest="password", - help="Password for plugin site", metavar="******") + "-w", + "--password", + dest="password", + help="Password for plugin site", + metavar="******", + ) parser.add_option( - "-u", "--username", dest="username", - help="Username of plugin site", metavar="user") + "-u", + "--username", + dest="username", + help="Username of plugin site", + metavar="user", + ) parser.add_option( - "-p", "--port", dest="port", - help="Server port to connect to", metavar="80") + "-p", + "--port", + dest="port", + help="Server port to connect to", + metavar="80", + ) parser.add_option( - "-s", "--server", dest="server", - help="Specify server name", metavar="plugins.qgis.org") + "-s", + "--server", + dest="server", + help="Specify server name", + metavar="plugins.qgis.org", + ) options, args = parser.parse_args() if len(args) != 1: print("Please specify zip file.\n") @@ -101,7 +123,7 @@ def hide_password(url, start=6): if not options.username: # interactive mode username = getpass.getuser() - print("Please enter user name [%s] :" % username, end=' ') + print("Please enter user name [%s] :" % username, end=" ") res = input() if res != "": diff --git a/postprocessor/params_dict.py b/postprocessor/params_dict.py index 112acc9..92d677a 100644 --- a/postprocessor/params_dict.py +++ b/postprocessor/params_dict.py @@ -1,105 +1,515 @@ params_dict = { - 'Kdown': {'description': 'Incoming shortwave radiation', 'unit': 'W m-2', 'format': 'f10.4', 'type': 'A'}, - 'Kup': {'description': 'Outgoing shortwave radiation', 'unit': 'W m-2', 'format': 'f10.4', 'type': 'A'}, - 'Ldown': {'description': 'Incoming longwave radiation', 'unit': 'W m-2', 'format': 'f10.4', 'type': 'A'}, - 'Lup': {'description': 'Outgoing longwave radiation', 'unit': 'W m-2', 'format': 'f10.4', 'type': 'A'}, - 'Tsurf': {'description': 'Bulk surface temperature', 'unit': 'degC', 'format': 'f10.4', 'type': 'A'}, - 'QN': {'description': 'Net all-wave radiation', 'unit': 'W m-2', 'format': 'f10.4', 'type': 'A'}, - 'QF': {'description': 'Anthropogenic heat flux', 'unit': 'W m-2', 'format': 'f10.4', 'type': 'A'}, - 'QS': {'description': 'Net storage heat flux', 'unit': 'W m-2', 'format': 'f10.4', 'type': 'A'}, - 'QH': {'description': 'Sensible heat flux', 'unit': 'W m-2', 'format': 'f10.4', 'type': 'A'}, - 'QE': {'description': 'Latent heat flux', 'unit': 'W m-2', 'format': 'f10.4', 'type': 'A'}, - 'QHlumps': {'description': 'Sensible heat flux (using LUMPS)', 'unit': 'W m-2', 'format': 'f10.4', 'type': 'A'}, - 'QElumps': {'description': 'Latent heat flux (using LUMPS)', 'unit': 'W m-2', 'format': 'f10.4', 'type': 'A'}, - 'QHresis': {'description': 'Sensible heat flux (resistance method)', 'unit': 'W m-2', 'format': 'f10.4', 'type': 'A'}, - 'Rain': {'description': 'Rain', 'unit': 'mm', 'format': 'f10.4', 'type': 'S'}, - 'Irr': {'description': 'Irrigation', 'unit': 'mm', 'format': 'f10.4', 'type': 'S'}, - 'Evap': {'description': 'Evaporation', 'unit': 'mm', 'format': 'f10.4', 'type': 'S'}, - 'RO': {'description': 'Runoff', 'unit': 'mm', 'format': 'f10.4', 'type': 'S'}, - 'TotCh': {'description': 'Surface and soil moisture change', 'unit': 'mm', 'format': 'f14.6', 'type': 'S'}, - 'SurfCh': {'description': 'Surface moisture change', 'unit': 'mm', 'format': 'f14.6', 'type': 'S'}, - 'State': {'description': 'Surface Wetness State', 'unit': 'mm', 'format': 'f10.4', 'type': 'L'}, - 'NWtrState': {'description': 'Surface wetness state (non-water surfaces)', 'unit': 'mm', 'format': 'f10.4', 'type': 'L'}, - 'Drainage': {'description': 'Drainage', 'unit': 'mm', 'format': 'f10.4', 'type': 'S'}, - 'SMD': {'description': 'Soil Moisture Deficit', 'unit': 'mm', 'format': 'f09.4', 'type': 'L'}, - 'FlowCh': {'description': 'Additional flow into water body', 'unit': 'mm', 'format': 'f10.4', 'type': 'S'}, - 'AddWater': {'description': 'Addtional water from other grids', 'unit': 'mm', 'format': 'f10.4', 'type': 'S'}, - 'ROSoil': {'description': 'Runoff to soil', 'unit': 'mm', 'format': 'f10.4', 'type': 'S'}, - 'ROPipe': {'description': 'Runoff to pipes', 'unit': 'mm', 'format': 'f10.4', 'type': 'S'}, - 'ROImp': {'description': 'Runoff over impervious surfaces', 'unit': 'mm', 'format': 'f10.4', 'type': 'S'}, - 'ROVeg': {'description': 'Runoff over vegetated surfaces', 'unit': 'mm', 'format': 'f10.4', 'type': 'S'}, - 'ROWater': {'description': 'Runoff for water surface', 'unit': 'mm', 'format': 'f10.4', 'type': 'S'}, - 'WUInt': {'description': 'InternalWaterUse', 'unit': 'mm', 'format': 'f09.4', 'type': 'S'}, - 'WUEveTr': {'description': 'Water use for evergreen trees', 'unit': 'mm', 'format': 'f09.4', 'type': 'S'}, - 'WUDecTr': {'description': 'Water use for deciduous trees', 'unit': 'mm', 'format': 'f09.4', 'type': 'S'}, - 'WUGrass': {'description': 'Water use for grass', 'unit': 'mm', 'format': 'f09.4', 'type': 'S'}, - 'SMDPaved': {'description': 'Soil moisture deficit for paved surface', 'unit': 'mm', 'format': 'f09.4', 'type': 'L'}, - 'SMDBldgs': {'description': 'Soil moisture deficit for building surface', 'unit': 'mm', 'format': 'f09.4', 'type': 'L'}, - 'SMDEveTr': {'description': 'Soil moisture deficit for evergreen tree surface', 'unit': 'mm', 'format': 'f09.4', 'type': 'L'}, - 'SMDDecTr': {'description': 'Soil moisture deficit for deciduous tree surface', 'unit': 'mm', 'format': 'f09.4', 'type': 'L'}, - 'SMDGrass': {'description': 'Soil moisture deficit for grass surface', 'unit': 'mm', 'format': 'f09.4', 'type': 'L'}, - 'SMDBSoil': {'description': 'Soil moisture deficit for bare soil surface', 'unit': 'mm', 'format': 'f09.4', 'type': 'L'}, - 'StPaved': {'description': 'Surface wetness state for paved surface', 'unit': 'mm', 'format': 'f09.4', 'type': 'L'}, - 'StBldgs': {'description': 'Surface wetness state for building surface', 'unit': 'mm', 'format': 'f09.4', 'type': 'L'}, - 'StEveTr': {'description': 'Surface wetness state for evergreen tree surface', 'unit': 'mm', 'format': 'f09.4', 'type': 'L'}, - 'StDecTr': {'description': 'Surface wetness state for deciduous tree surface', 'unit': 'mm', 'format': 'f09.4', 'type': 'L'}, - 'StGrass': {'description': 'Surface wetness state for grass surface', 'unit': 'mm', 'format': 'f09.4', 'type': 'L'}, - 'StBSoil': {'description': 'Surface wetness state for bare soil surface', 'unit': 'mm', 'format': 'f09.4', 'type': 'L'}, - 'StWater': {'description': 'Surface wetness state for water surface', 'unit': 'mm', 'format': 'f10.4', 'type': 'L'}, - 'Zenith': {'description': 'Solar zenith angle', 'unit': 'degree', 'format': 'f10.4', 'type': 'L'}, - 'Azimuth': {'description': 'Solar azimuth angle', 'unit': 'degree', 'format': 'f09.4', 'type': 'L'}, - 'AlbBulk': {'description': 'Bulk albedo', 'unit': '1', 'format': 'f09.4', 'type': 'A'}, - 'Fcld': {'description': 'Cloud fraction', 'unit': '1', 'format': 'f09.4', 'type': 'A'}, - 'LAI': {'description': 'Leaf area index', 'unit': 'm2 m-2', 'format': 'f09.4', 'type': 'A'}, - 'z0m': {'description': 'Roughness length for momentum', 'unit': 'm', 'format': 'f09.4', 'type': 'A'}, - 'zdm': {'description': 'Zero-plane displacement height', 'unit': 'm', 'format': 'f09.4', 'type': 'A'}, - 'UStar': {'description': 'Friction velocity', 'unit': 'm s-1', 'format': 'f09.4', 'type': 'A'}, - 'Lob': {'description': 'Obukhov length', 'unit': 'm', 'format': 'f14.6', 'type': 'A'}, - 'RA': {'description': 'Aerodynamic resistance', 'unit': 's m-1', 'format': 'f10.4', 'type': 'A'}, - 'RS': {'description': 'Surface resistance', 'unit': 's m-1', 'format': 'f10.4', 'type': 'A'}, - 'Fc': {'description': 'CO2 flux', 'unit': 'umol m-2 s-1', 'format': 'f09.4', 'type': 'A'}, - 'FcPhoto': {'description': 'CO2 flux from photosynthesis', 'unit': 'umol m-2 s-1', 'format': 'f09.4', 'type': 'A'}, - 'FcRespi': {'description': 'CO2 flux from respiration', 'unit': 'umol m-2 s-1', 'format': 'f09.4', 'type': 'A'}, - 'FcMetab': {'description': 'CO2 flux from metabolism', 'unit': 'umol m-2 s-1', 'format': 'f09.4', 'type': 'A'}, - 'FcTraff': {'description': 'CO2 flux from traffic', 'unit': 'umol m-2 s-1', 'format': 'f09.4', 'type': 'A'}, - 'FcBuild': {'description': 'CO2 flux from buildings', 'unit': 'umol m-2 s-1', 'format': 'f09.4', 'type': 'A'}, - 'FcPoint': {'description': 'CO2 flux from point source', 'unit': 'umol m-2 s-1', 'format': 'f09.4', 'type': 'A'}, - 'QNSnowFr': {'description': 'Net all-wave radiation for non-snow area', 'unit': 'W m-2', 'format': 'f09.4', 'type': 'A'}, - 'QNSnow': {'description': 'Net all-wave radiation for snow area', 'unit': 'W m-2', 'format': 'f09.4', 'type': 'A'}, - 'AlbSnow': {'description': 'Snow albedo', 'unit': '-', 'format': 'f09.4', 'type': 'A'}, - 'QM': {'description': 'Snow-related heat exchange', 'unit': 'W m-2', 'format': 'f10.6', 'type': 'A'}, - 'QMFreeze': {'description': 'Internal energy change', 'unit': 'W m-2', 'format': 'f14.6', 'type': 'A'}, - 'QMRain': {'description': 'Heat released by rain on snow', 'unit': 'W m-2', 'format': 'f10.6', 'type': 'A'}, - 'SWE': {'description': 'Snow water equivalent', 'unit': 'mm', 'format': 'f10.4', 'type': 'A'}, - 'MeltWater': {'description': 'Meltwater', 'unit': 'mm', 'format': 'f10.4', 'type': 'A'}, - 'MeltWStore': {'description': 'Meltwater store', 'unit': 'mm', 'format': 'f10.4', 'type': 'A'}, - 'SnowCh': {'description': 'Change in snow pack', 'unit': 'mm', 'format': 'f10.4', 'type': 'S'}, - 'SnowRPaved': {'description': 'Snow removed from paved surface', 'unit': 'mm', 'format': 'f09.4', 'type': 'S'}, - 'SnowRBldgs': {'description': 'Snow removed from building surface', 'unit': 'mm', 'format': 'f09.4', 'type': 'S'}, - 'Ts': {'description': 'Skin temperature', 'unit': 'degC', 'format': 'f09.4', 'type': 'A'}, - 'T2': {'description': 'Air temperature at 2 m', 'unit': 'degC', 'format': 'f09.4', 'type': 'A'}, - 'Q2': {'description': 'Specific humidity at 2 m', 'unit': 'g kg-1', 'format': 'f09.4', 'type': 'A'}, - 'U10': {'description': 'Wind speed at 10 m', 'unit': 'm s-1', 'format': 'f09.4', 'type': 'A'}, - 'RH2': {'description': 'Relative humidity at 2 m', 'unit': '%', 'format': 'f09.4', 'type': 'A'} + "Kdown": { + "description": "Incoming shortwave radiation", + "unit": "W m-2", + "format": "f10.4", + "type": "A", + }, + "Kup": { + "description": "Outgoing shortwave radiation", + "unit": "W m-2", + "format": "f10.4", + "type": "A", + }, + "Ldown": { + "description": "Incoming longwave radiation", + "unit": "W m-2", + "format": "f10.4", + "type": "A", + }, + "Lup": { + "description": "Outgoing longwave radiation", + "unit": "W m-2", + "format": "f10.4", + "type": "A", + }, + "Tsurf": { + "description": "Bulk surface temperature", + "unit": "degC", + "format": "f10.4", + "type": "A", + }, + "QN": { + "description": "Net all-wave radiation", + "unit": "W m-2", + "format": "f10.4", + "type": "A", + }, + "QF": { + "description": "Anthropogenic heat flux", + "unit": "W m-2", + "format": "f10.4", + "type": "A", + }, + "QS": { + "description": "Net storage heat flux", + "unit": "W m-2", + "format": "f10.4", + "type": "A", + }, + "QH": { + "description": "Sensible heat flux", + "unit": "W m-2", + "format": "f10.4", + "type": "A", + }, + "QE": { + "description": "Latent heat flux", + "unit": "W m-2", + "format": "f10.4", + "type": "A", + }, + "QHlumps": { + "description": "Sensible heat flux (using LUMPS)", + "unit": "W m-2", + "format": "f10.4", + "type": "A", + }, + "QElumps": { + "description": "Latent heat flux (using LUMPS)", + "unit": "W m-2", + "format": "f10.4", + "type": "A", + }, + "QHresis": { + "description": "Sensible heat flux (resistance method)", + "unit": "W m-2", + "format": "f10.4", + "type": "A", + }, + "Rain": { + "description": "Rain", + "unit": "mm", + "format": "f10.4", + "type": "S", + }, + "Irr": { + "description": "Irrigation", + "unit": "mm", + "format": "f10.4", + "type": "S", + }, + "Evap": { + "description": "Evaporation", + "unit": "mm", + "format": "f10.4", + "type": "S", + }, + "RO": { + "description": "Runoff", + "unit": "mm", + "format": "f10.4", + "type": "S", + }, + "TotCh": { + "description": "Surface and soil moisture change", + "unit": "mm", + "format": "f14.6", + "type": "S", + }, + "SurfCh": { + "description": "Surface moisture change", + "unit": "mm", + "format": "f14.6", + "type": "S", + }, + "State": { + "description": "Surface Wetness State", + "unit": "mm", + "format": "f10.4", + "type": "L", + }, + "NWtrState": { + "description": "Surface wetness state (non-water surfaces)", + "unit": "mm", + "format": "f10.4", + "type": "L", + }, + "Drainage": { + "description": "Drainage", + "unit": "mm", + "format": "f10.4", + "type": "S", + }, + "SMD": { + "description": "Soil Moisture Deficit", + "unit": "mm", + "format": "f09.4", + "type": "L", + }, + "FlowCh": { + "description": "Additional flow into water body", + "unit": "mm", + "format": "f10.4", + "type": "S", + }, + "AddWater": { + "description": "Addtional water from other grids", + "unit": "mm", + "format": "f10.4", + "type": "S", + }, + "ROSoil": { + "description": "Runoff to soil", + "unit": "mm", + "format": "f10.4", + "type": "S", + }, + "ROPipe": { + "description": "Runoff to pipes", + "unit": "mm", + "format": "f10.4", + "type": "S", + }, + "ROImp": { + "description": "Runoff over impervious surfaces", + "unit": "mm", + "format": "f10.4", + "type": "S", + }, + "ROVeg": { + "description": "Runoff over vegetated surfaces", + "unit": "mm", + "format": "f10.4", + "type": "S", + }, + "ROWater": { + "description": "Runoff for water surface", + "unit": "mm", + "format": "f10.4", + "type": "S", + }, + "WUInt": { + "description": "InternalWaterUse", + "unit": "mm", + "format": "f09.4", + "type": "S", + }, + "WUEveTr": { + "description": "Water use for evergreen trees", + "unit": "mm", + "format": "f09.4", + "type": "S", + }, + "WUDecTr": { + "description": "Water use for deciduous trees", + "unit": "mm", + "format": "f09.4", + "type": "S", + }, + "WUGrass": { + "description": "Water use for grass", + "unit": "mm", + "format": "f09.4", + "type": "S", + }, + "SMDPaved": { + "description": "Soil moisture deficit for paved surface", + "unit": "mm", + "format": "f09.4", + "type": "L", + }, + "SMDBldgs": { + "description": "Soil moisture deficit for building surface", + "unit": "mm", + "format": "f09.4", + "type": "L", + }, + "SMDEveTr": { + "description": "Soil moisture deficit for evergreen tree surface", + "unit": "mm", + "format": "f09.4", + "type": "L", + }, + "SMDDecTr": { + "description": "Soil moisture deficit for deciduous tree surface", + "unit": "mm", + "format": "f09.4", + "type": "L", + }, + "SMDGrass": { + "description": "Soil moisture deficit for grass surface", + "unit": "mm", + "format": "f09.4", + "type": "L", + }, + "SMDBSoil": { + "description": "Soil moisture deficit for bare soil surface", + "unit": "mm", + "format": "f09.4", + "type": "L", + }, + "StPaved": { + "description": "Surface wetness state for paved surface", + "unit": "mm", + "format": "f09.4", + "type": "L", + }, + "StBldgs": { + "description": "Surface wetness state for building surface", + "unit": "mm", + "format": "f09.4", + "type": "L", + }, + "StEveTr": { + "description": "Surface wetness state for evergreen tree surface", + "unit": "mm", + "format": "f09.4", + "type": "L", + }, + "StDecTr": { + "description": "Surface wetness state for deciduous tree surface", + "unit": "mm", + "format": "f09.4", + "type": "L", + }, + "StGrass": { + "description": "Surface wetness state for grass surface", + "unit": "mm", + "format": "f09.4", + "type": "L", + }, + "StBSoil": { + "description": "Surface wetness state for bare soil surface", + "unit": "mm", + "format": "f09.4", + "type": "L", + }, + "StWater": { + "description": "Surface wetness state for water surface", + "unit": "mm", + "format": "f10.4", + "type": "L", + }, + "Zenith": { + "description": "Solar zenith angle", + "unit": "degree", + "format": "f10.4", + "type": "L", + }, + "Azimuth": { + "description": "Solar azimuth angle", + "unit": "degree", + "format": "f09.4", + "type": "L", + }, + "AlbBulk": { + "description": "Bulk albedo", + "unit": "1", + "format": "f09.4", + "type": "A", + }, + "Fcld": { + "description": "Cloud fraction", + "unit": "1", + "format": "f09.4", + "type": "A", + }, + "LAI": { + "description": "Leaf area index", + "unit": "m2 m-2", + "format": "f09.4", + "type": "A", + }, + "z0m": { + "description": "Roughness length for momentum", + "unit": "m", + "format": "f09.4", + "type": "A", + }, + "zdm": { + "description": "Zero-plane displacement height", + "unit": "m", + "format": "f09.4", + "type": "A", + }, + "UStar": { + "description": "Friction velocity", + "unit": "m s-1", + "format": "f09.4", + "type": "A", + }, + "Lob": { + "description": "Obukhov length", + "unit": "m", + "format": "f14.6", + "type": "A", + }, + "RA": { + "description": "Aerodynamic resistance", + "unit": "s m-1", + "format": "f10.4", + "type": "A", + }, + "RS": { + "description": "Surface resistance", + "unit": "s m-1", + "format": "f10.4", + "type": "A", + }, + "Fc": { + "description": "CO2 flux", + "unit": "umol m-2 s-1", + "format": "f09.4", + "type": "A", + }, + "FcPhoto": { + "description": "CO2 flux from photosynthesis", + "unit": "umol m-2 s-1", + "format": "f09.4", + "type": "A", + }, + "FcRespi": { + "description": "CO2 flux from respiration", + "unit": "umol m-2 s-1", + "format": "f09.4", + "type": "A", + }, + "FcMetab": { + "description": "CO2 flux from metabolism", + "unit": "umol m-2 s-1", + "format": "f09.4", + "type": "A", + }, + "FcTraff": { + "description": "CO2 flux from traffic", + "unit": "umol m-2 s-1", + "format": "f09.4", + "type": "A", + }, + "FcBuild": { + "description": "CO2 flux from buildings", + "unit": "umol m-2 s-1", + "format": "f09.4", + "type": "A", + }, + "FcPoint": { + "description": "CO2 flux from point source", + "unit": "umol m-2 s-1", + "format": "f09.4", + "type": "A", + }, + "QNSnowFr": { + "description": "Net all-wave radiation for non-snow area", + "unit": "W m-2", + "format": "f09.4", + "type": "A", + }, + "QNSnow": { + "description": "Net all-wave radiation for snow area", + "unit": "W m-2", + "format": "f09.4", + "type": "A", + }, + "AlbSnow": { + "description": "Snow albedo", + "unit": "-", + "format": "f09.4", + "type": "A", + }, + "QM": { + "description": "Snow-related heat exchange", + "unit": "W m-2", + "format": "f10.6", + "type": "A", + }, + "QMFreeze": { + "description": "Internal energy change", + "unit": "W m-2", + "format": "f14.6", + "type": "A", + }, + "QMRain": { + "description": "Heat released by rain on snow", + "unit": "W m-2", + "format": "f10.6", + "type": "A", + }, + "SWE": { + "description": "Snow water equivalent", + "unit": "mm", + "format": "f10.4", + "type": "A", + }, + "MeltWater": { + "description": "Meltwater", + "unit": "mm", + "format": "f10.4", + "type": "A", + }, + "MeltWStore": { + "description": "Meltwater store", + "unit": "mm", + "format": "f10.4", + "type": "A", + }, + "SnowCh": { + "description": "Change in snow pack", + "unit": "mm", + "format": "f10.4", + "type": "S", + }, + "SnowRPaved": { + "description": "Snow removed from paved surface", + "unit": "mm", + "format": "f09.4", + "type": "S", + }, + "SnowRBldgs": { + "description": "Snow removed from building surface", + "unit": "mm", + "format": "f09.4", + "type": "S", + }, + "Ts": { + "description": "Skin temperature", + "unit": "degC", + "format": "f09.4", + "type": "A", + }, + "T2": { + "description": "Air temperature at 2 m", + "unit": "degC", + "format": "f09.4", + "type": "A", + }, + "Q2": { + "description": "Specific humidity at 2 m", + "unit": "g kg-1", + "format": "f09.4", + "type": "A", + }, + "U10": { + "description": "Wind speed at 10 m", + "unit": "m s-1", + "format": "f09.4", + "type": "A", + }, + "RH2": { + "description": "Relative humidity at 2 m", + "unit": "%", + "format": "f09.4", + "type": "A", + }, } unit_dict = { - 'W m-2': r'$W$ $m^{-2}$', - 'mm': r'$mm$', - 'degC': r'$^{o}C$', - 'deg': r'$Degrees(^{o})$', - '-': r'$-$', - 'm2 m-2': r'$m^{2}$ $m^{-2}$', - 'm': r'$m$', - 'm s-1': r'$m$ $s^{-1}$', - 'umol m-2 s-1': r'$umol^{2}$ $m^{-2}$ $s^{-1}$', - 'YYYY': r'$Year$', - 'DOY': r'$Day\ of\ Year$', - 'HH': r'$Hour$', - 'day': r'$Decimal\ Time$', - '%': r'$\,\%$', - '1': r'$1$', - 'degree': r'$^\circ$', - 'g kg-1': r'$g$ $kg^{-1}$', - 's m-1': r'$s$ $m^{-1}$', -} \ No newline at end of file + "W m-2": r"$W$ $m^{-2}$", + "mm": r"$mm$", + "degC": r"$^{o}C$", + "deg": r"$Degrees(^{o})$", + "-": r"$-$", + "m2 m-2": r"$m^{2}$ $m^{-2}$", + "m": r"$m$", + "m s-1": r"$m$ $s^{-1}$", + "umol m-2 s-1": r"$umol^{2}$ $m^{-2}$ $s^{-1}$", + "YYYY": r"$Year$", + "DOY": r"$Day\ of\ Year$", + "HH": r"$Hour$", + "day": r"$Decimal\ Time$", + "%": r"$\,\%$", + "1": r"$1$", + "degree": r"$^\circ$", + "g kg-1": r"$g$ $kg^{-1}$", + "s m-1": r"$s$ $m^{-1}$", +} diff --git a/postprocessor/solwieganalyzer_algorithm.py b/postprocessor/solwieganalyzer_algorithm.py index bc3d3bb..d524270 100644 --- a/postprocessor/solwieganalyzer_algorithm.py +++ b/postprocessor/solwieganalyzer_algorithm.py @@ -1,22 +1,24 @@ # -*- coding: utf-8 -*- -__author__ = 'Fredrik Lindberg' -__date__ = '2021-02-05' -__copyright__ = '(C) 2021 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2021-02-05" +__copyright__ = "(C) 2021 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QVariant -from qgis.core import (QgsProcessingAlgorithm, - QgsProcessingParameterNumber, - QgsProcessingParameterRasterDestination, - QgsProcessingParameterRasterLayer, - QgsProcessingParameterEnum, - QgsProcessingException, - QgsProcessingParameterFile,) +from qgis.core import ( + QgsProcessingAlgorithm, + QgsProcessingParameterNumber, + QgsProcessingParameterRasterDestination, + QgsProcessingParameterRasterLayer, + QgsProcessingParameterEnum, + QgsProcessingException, + QgsProcessingParameterFile, +) from qgis.PyQt.QtGui import QIcon from osgeo import gdal from osgeo.gdalconst import * @@ -31,85 +33,137 @@ class ProcessingSolweigAnalyzerAlgorithm(QgsProcessingAlgorithm): """ This class is a processing version of SOLWEIGAnalyzer but only for generating aggregated grids """ - SOLWEIG_DIR = 'SOLWEIG_DIR' - - VARIA_IN = 'VARIA_IN' - BUILDINGS = 'BUILDINGS' - STAT_TYPE = 'STAT_TYPE' + + SOLWEIG_DIR = "SOLWEIG_DIR" + + VARIA_IN = "VARIA_IN" + BUILDINGS = "BUILDINGS" + STAT_TYPE = "STAT_TYPE" # SPECTIME_AV = 'SPECTIME_AV' # SPECTIME_MIN = 'SPECTIME_MIN' # SPECTIME_MAX = 'SPECTIME_MAX' - THRES_TYPE = 'THRES_TYPE' - TMRT_THRES_NUM = 'TMRT_THRES_NUM' + THRES_TYPE = "THRES_TYPE" + TMRT_THRES_NUM = "TMRT_THRES_NUM" # Output - STAT_OUT = 'STAT_OUT' - TMRT_STAT_OUT = 'TMRT_STAT_OUT' - + STAT_OUT = "STAT_OUT" + TMRT_STAT_OUT = "TMRT_STAT_OUT" def initAlgorithm(self, config): - self.addParameter(QgsProcessingParameterFile(self.SOLWEIG_DIR, - self.tr('Path to SOLWEIG output folder'), - QgsProcessingParameterFile.Behavior.Folder)) - self.addParameter(QgsProcessingParameterRasterLayer(self.BUILDINGS, - self.tr('Raster to exclude building pixels from analysis'), - '', - optional=True)) - self.varType = ((self.tr('Mean Radiant Temperature (Tmrt)'), '0'), - (self.tr('Incoming Longwave radiation (Ldown)'), '1'), - (self.tr('Outgoing Longwave radiation (Lup)'), '2'), - (self.tr('Incoming Shortwave radiation (Kdown)'), '3'), - (self.tr('Outgoing Shortwave radiation (Kup)'), '4'), - (self.tr('Ground Shadow'), '5')) - self.addParameter(QgsProcessingParameterEnum(self.VARIA_IN, - self.tr('Variable to post-process (must be availabe in the SOLWEIG output folder)'), - options=[i[0] for i in self.varType], - defaultValue=False)) - self.statType = ((self.tr('Diurnal Average'), '0'), - (self.tr('Daytime average'), '1'), - (self.tr('Nighttime average'), '2'), - (self.tr('Maximum'), '3'), - (self.tr('Minimun'), '4')) - self.addParameter(QgsProcessingParameterEnum(self.STAT_TYPE, - self.tr('Statistic measure'), - options=[i[0] for i in self.statType], - defaultValue=1)) - - self.thresType = ((self.tr(' '), '0'), - (self.tr('Above'), '1'), - (self.tr('Below'), '2')) - self.addParameter(QgsProcessingParameterEnum(self.THRES_TYPE, - self.tr('Calculate Percent of time above/below Tmrt theshold'), - options=[i[0] for i in self.thresType], - defaultValue=False, - optional=True)) - self.addParameter(QgsProcessingParameterNumber(self.TMRT_THRES_NUM, - self.tr('Theshold (degC)'), - QgsProcessingParameterNumber.Type.Double, - QVariant(55), - False)) + self.addParameter( + QgsProcessingParameterFile( + self.SOLWEIG_DIR, + self.tr("Path to SOLWEIG output folder"), + QgsProcessingParameterFile.Behavior.Folder, + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.BUILDINGS, + self.tr("Raster to exclude building pixels from analysis"), + "", + optional=True, + ) + ) + self.varType = ( + (self.tr("Mean Radiant Temperature (Tmrt)"), "0"), + (self.tr("Incoming Longwave radiation (Ldown)"), "1"), + (self.tr("Outgoing Longwave radiation (Lup)"), "2"), + (self.tr("Incoming Shortwave radiation (Kdown)"), "3"), + (self.tr("Outgoing Shortwave radiation (Kup)"), "4"), + (self.tr("Ground Shadow"), "5"), + ) + self.addParameter( + QgsProcessingParameterEnum( + self.VARIA_IN, + self.tr( + "Variable to post-process (must be availabe in the SOLWEIG output folder)" + ), + options=[i[0] for i in self.varType], + defaultValue=False, + ) + ) + self.statType = ( + (self.tr("Diurnal Average"), "0"), + (self.tr("Daytime average"), "1"), + (self.tr("Nighttime average"), "2"), + (self.tr("Maximum"), "3"), + (self.tr("Minimun"), "4"), + ) + self.addParameter( + QgsProcessingParameterEnum( + self.STAT_TYPE, + self.tr("Statistic measure"), + options=[i[0] for i in self.statType], + defaultValue=1, + ) + ) + + self.thresType = ( + (self.tr(" "), "0"), + (self.tr("Above"), "1"), + (self.tr("Below"), "2"), + ) + self.addParameter( + QgsProcessingParameterEnum( + self.THRES_TYPE, + self.tr("Calculate Percent of time above/below Tmrt theshold"), + options=[i[0] for i in self.thresType], + defaultValue=False, + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.TMRT_THRES_NUM, + self.tr("Theshold (degC)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(55), + False, + ) + ) # Output - self.addParameter(QgsProcessingParameterRasterDestination(self.STAT_OUT, - self.tr("Output raster from statistical analysis"), - None, - optional=False)) - self.addParameter(QgsProcessingParameterRasterDestination(self.TMRT_STAT_OUT, - self.tr("Output raster from Tmrt theshold analysis"), - None, - optional=True)) + self.addParameter( + QgsProcessingParameterRasterDestination( + self.STAT_OUT, + self.tr("Output raster from statistical analysis"), + None, + optional=False, + ) + ) + self.addParameter( + QgsProcessingParameterRasterDestination( + self.TMRT_STAT_OUT, + self.tr("Output raster from Tmrt theshold analysis"), + None, + optional=True, + ) + ) def processAlgorithm(self, parameters, context, feedback): - + # InputParameters - solweigDir = self.parameterAsString(parameters, self.SOLWEIG_DIR, context) + solweigDir = self.parameterAsString( + parameters, self.SOLWEIG_DIR, context + ) variaIn = self.parameterAsString(parameters, self.VARIA_IN, context) - buildings = self.parameterAsRasterLayer(parameters, self.BUILDINGS, context) - statTypeStr = self.parameterAsString(parameters, self.STAT_TYPE, context) - thresTypeStr = self.parameterAsString(parameters, self.THRES_TYPE, context) - thresNum = self.parameterAsDouble(parameters, self.TMRT_THRES_NUM, context) - outputStat = self.parameterAsOutputLayer(parameters, self.STAT_OUT, context) + buildings = self.parameterAsRasterLayer( + parameters, self.BUILDINGS, context + ) + statTypeStr = self.parameterAsString( + parameters, self.STAT_TYPE, context + ) + thresTypeStr = self.parameterAsString( + parameters, self.THRES_TYPE, context + ) + thresNum = self.parameterAsDouble( + parameters, self.TMRT_THRES_NUM, context + ) + outputStat = self.parameterAsOutputLayer( + parameters, self.STAT_OUT, context + ) outputTMRT = None feedback.setProgressText("Initializing...") @@ -126,32 +180,36 @@ def processAlgorithm(self, parameters, context, feedback): # SOLWEIGANALYZER CODE self.l = os.listdir(solweigDir) - - if variaIn == '0': - self.var = 'Tmrt' - elif variaIn == '1': - self.var = 'Ldown' - elif variaIn == '2': - self.var = 'Lup' - elif variaIn == '3': - self.var = 'Kdown' - elif variaIn == '4': - self.var = 'Kup' - elif variaIn == '5': - self.var = 'Shadow' + + if variaIn == "0": + self.var = "Tmrt" + elif variaIn == "1": + self.var = "Ldown" + elif variaIn == "2": + self.var = "Lup" + elif variaIn == "3": + self.var = "Kdown" + elif variaIn == "4": + self.var = "Kup" + elif variaIn == "5": + self.var = "Shadow" if not self.var in str(self.l): - raise QgsProcessingException('Filename starting with "' + self.var + '" is not found in SOLWEIG output folder.') + raise QgsProcessingException( + 'Filename starting with "' + + self.var + + '" is not found in SOLWEIG output folder.' + ) index = 0 for file in self.l: - if file.startswith(self.var + '_'): - if not file.endswith('_average.tif'): - if not file.endswith('.xml'): # response to issue #196 + if file.startswith(self.var + "_"): + if not file.endswith("_average.tif"): + if not file.endswith(".xml"): # response to issue #196 self.posAll.append(index) - if file.endswith('D.tif'): + if file.endswith("D.tif"): self.posDay.append(index) - if file.endswith('N.tif'): + if file.endswith("N.tif"): self.posNight.append(index) # if file[-9:-5] == self.dlg.comboBoxSpecificMean.currentText(): # self.posSpecMean.append(index) @@ -163,7 +221,7 @@ def processAlgorithm(self, parameters, context, feedback): # Exclude buildings if buildings is None: - feedback.setProgressText("No building raster loaded.") + feedback.setProgressText("No building raster loaded.") else: provider = buildings.dataProvider() filepath_dsm = str(provider.dataSourceUri()) @@ -171,13 +229,15 @@ def processAlgorithm(self, parameters, context, feedback): self.build = self.gdal_dsm.ReadAsArray().astype(float) geotransform = self.gdal_dsm.GetGeoTransform() self.scale = 1 / geotransform[1] - + # Diurnal mean if statType == 0: - feedback.setProgressText('Calculating ' + self.var + ' diurnal mean.') + feedback.setProgressText( + "Calculating " + self.var + " diurnal mean." + ) index = 0 for i in self.posAll: - gdal_dsm = gdal.Open(solweigDir + '/' + self.l[i]) + gdal_dsm = gdal.Open(solweigDir + "/" + self.l[i]) grid = gdal_dsm.ReadAsArray().astype(float) if index == 0: sizex = grid.shape[0] @@ -195,10 +255,12 @@ def processAlgorithm(self, parameters, context, feedback): # Daytime mean if statType == 1: - feedback.setProgressText('Calculating ' + self.var + ' daytime mean.') + feedback.setProgressText( + "Calculating " + self.var + " daytime mean." + ) index = 0 for i in self.posDay: - gdal_dsm = gdal.Open(solweigDir + '/' + self.l[i]) + gdal_dsm = gdal.Open(solweigDir + "/" + self.l[i]) grid = gdal_dsm.ReadAsArray().astype(float) if index == 0: sizex = grid.shape[0] @@ -216,10 +278,12 @@ def processAlgorithm(self, parameters, context, feedback): # Nighttime mean if statType == 2: - feedback.setProgressText('Calculating ' + self.var + ' nighttime mean.') + feedback.setProgressText( + "Calculating " + self.var + " nighttime mean." + ) index = 0 for i in self.posNight: - gdal_dsm = gdal.Open(solweigDir + '/' + self.l[i]) + gdal_dsm = gdal.Open(solweigDir + "/" + self.l[i]) grid = gdal_dsm.ReadAsArray().astype(float) if index == 0: sizex = grid.shape[0] @@ -237,15 +301,15 @@ def processAlgorithm(self, parameters, context, feedback): # Max if statType == 3: - feedback.setProgressText('Calculating ' + self.var + ' max.') + feedback.setProgressText("Calculating " + self.var + " max.") index = 0 for i in self.posAll: - gdal_dsm = gdal.Open(solweigDir + '/' + self.l[i]) + gdal_dsm = gdal.Open(solweigDir + "/" + self.l[i]) grid = gdal_dsm.ReadAsArray().astype(float) if index == 0: sizex = grid.shape[0] sizey = grid.shape[1] - gridall = np.zeros((sizex, sizey)) - 100. + gridall = np.zeros((sizex, sizey)) - 100.0 gridall = np.maximum(gridall, grid) index += 1 @@ -256,15 +320,15 @@ def processAlgorithm(self, parameters, context, feedback): # Min if statType == 4: - feedback.setProgressText('Calculating ' + self.var + ' min.') + feedback.setProgressText("Calculating " + self.var + " min.") index = 0 for i in self.posAll: - gdal_dsm = gdal.Open(solweigDir + '/' + self.l[i]) + gdal_dsm = gdal.Open(solweigDir + "/" + self.l[i]) grid = gdal_dsm.ReadAsArray().astype(float) if index == 0: sizex = grid.shape[0] sizey = grid.shape[1] - gridall = np.zeros((sizex, sizey)) + 100. + gridall = np.zeros((sizex, sizey)) + 100.0 gridall = np.minimum(gridall, grid) index += 1 @@ -342,18 +406,24 @@ def processAlgorithm(self, parameters, context, feedback): # Tmrt threshold above if thresType == 1: - feedback.setProgressText('Calculating Tmrt percent time above ' + str(thresNum) + ' degC.') - outputTMRT = self.parameterAsOutputLayer(parameters, self.TMRT_STAT_OUT, context) + feedback.setProgressText( + "Calculating Tmrt percent time above " + + str(thresNum) + + " degC." + ) + outputTMRT = self.parameterAsOutputLayer( + parameters, self.TMRT_STAT_OUT, context + ) index = 0 for i in self.posAll: - gdal_dsm = gdal.Open(solweigDir + '/' + self.l[i]) + gdal_dsm = gdal.Open(solweigDir + "/" + self.l[i]) grid = gdal_dsm.ReadAsArray().astype(float) if index == 0: sizex = grid.shape[0] sizey = grid.shape[1] daymean = np.zeros((sizex, sizey)) - tempgrid = (grid >= thresNum) + tempgrid = grid >= thresNum daymean = daymean + tempgrid index += 1 @@ -362,21 +432,27 @@ def processAlgorithm(self, parameters, context, feedback): if buildings is not None: daymean[self.build == 0] = -9999 - saveraster(gdal_dsm, outputTMRT, daymean) # response to issue #218 + saveraster(gdal_dsm, outputTMRT, daymean) # response to issue #218 # Tmrt threshold below if thresType == 2: - feedback.setProgressText('Calculating Tmrt percent time below ' + str(thresNum) + ' degC.') - outputTMRT = self.parameterAsOutputLayer(parameters, self.TMRT_STAT_OUT, context) + feedback.setProgressText( + "Calculating Tmrt percent time below " + + str(thresNum) + + " degC." + ) + outputTMRT = self.parameterAsOutputLayer( + parameters, self.TMRT_STAT_OUT, context + ) index = 0 for i in self.posAll: - gdal_dsm = gdal.Open(solweigDir + '/' + self.l[i]) + gdal_dsm = gdal.Open(solweigDir + "/" + self.l[i]) grid = gdal_dsm.ReadAsArray().astype(float) if index == 0: sizex = grid.shape[0] sizey = grid.shape[1] daymean = np.zeros((sizex, sizey)) - tempgrid = (grid < thresNum) + tempgrid = grid < thresNum daymean = daymean + tempgrid index += 1 @@ -389,13 +465,12 @@ def processAlgorithm(self, parameters, context, feedback): del self.posAll[:] - feedback.setProgressText("Processing finished.") return {self.STAT_OUT: outputTMRT, self.TMRT_STAT_OUT: outputTMRT} - + def name(self): - return 'Outdoor Thermal Comfort: SOLWEIG Analyzer' + return "Outdoor Thermal Comfort: SOLWEIG Analyzer" def displayName(self): return self.tr(self.name()) @@ -404,23 +479,27 @@ def group(self): return self.tr(self.groupId()) def groupId(self): - return 'Post-Processor' + return "Post-Processor" def shortHelpString(self): - return self.tr('The SOLWEIG Analyzer plugin can be used to make basic grid analysis of model results generated by the SOLWEIG model.
' - '\n' - '--------------\n' - 'Full manual available via the Help-button.') + return self.tr( + "The SOLWEIG Analyzer plugin can be used to make basic grid analysis of model results generated by the SOLWEIG model.
" + "\n" + "--------------\n" + "Full manual available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/post_processor/Outdoor%20Thermal%20Comfort%20SOLWEIG%20Analyzer.html" return url def tr(self, string): - return QCoreApplication.translate('Post-Processing', string) + return QCoreApplication.translate("Post-Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/icon_solweig.png") return icon diff --git a/postprocessor/spatialtc_algorithm.py b/postprocessor/spatialtc_algorithm.py index 7b25c39..0508da6 100644 --- a/postprocessor/spatialtc_algorithm.py +++ b/postprocessor/spatialtc_algorithm.py @@ -1,23 +1,25 @@ # -*- coding: utf-8 -*- -__author__ = 'Fredrik Lindberg' -__date__ = '2021-02-05' -__copyright__ = '(C) 2021 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2021-02-05" +__copyright__ = "(C) 2021 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QVariant -from qgis.core import (QgsProcessingAlgorithm, - QgsProcessingParameterBoolean, - QgsProcessingParameterNumber, - QgsProcessingParameterRasterDestination, - QgsProcessingParameterRasterLayer, - QgsProcessingParameterEnum, - QgsProcessingException, - QgsProcessingParameterDefinition) +from qgis.core import ( + QgsProcessingAlgorithm, + QgsProcessingParameterBoolean, + QgsProcessingParameterNumber, + QgsProcessingParameterRasterDestination, + QgsProcessingParameterRasterLayer, + QgsProcessingParameterEnum, + QgsProcessingException, + QgsProcessingParameterDefinition, +) from qgis.PyQt.QtGui import QIcon from osgeo import gdal, osr from osgeo.gdalconst import * @@ -29,22 +31,34 @@ from ..util.misc import saveraster from ..functions.SOLWEIGpython import PET_calculations as pet from ..functions.SOLWEIGpython import UTCI_calculations as utci -from ..functions.SOLWEIGpython.COMFA.radiationfunctionsCOMFA import COMFA_RAD_SPATIAL_TC as COMFA_rad +from ..functions.SOLWEIGpython.COMFA.radiationfunctionsCOMFA import ( + COMFA_RAD_SPATIAL_TC as COMFA_rad, +) from ..functions.SOLWEIGpython.COMFA.COMFA_BUDGET import COMFA_Mact from ..functions.SOLWEIGpython.COMFA.COMFA_BUDGET import COMFA_BUDGET + def load_grid(filepath, feedback): # Function that loads QGIS input raster into numpy array # provider = data.dataProvider() # Provider for raster data # temp_filename = str(provider.dataSourceUri()) # File path for data try: - temp_grid = gdal.Open(filepath) # Open raster with GDAL + temp_grid = gdal.Open(filepath) # Open raster with GDAL feedback.setProgressText("Successfully loaded " + filepath) except: - raise QgsProcessingException("Error: Could not load " + filepath + ". File does not exist. Check path!") - + raise QgsProcessingException( + "Error: Could not load " + + filepath + + ". File does not exist. Check path!" + ) + # Return gdal raster layer as numpy array, number of rows and columns in raster - return temp_grid.ReadAsArray().astype(float), temp_grid.ReadAsArray().astype(float).shape[0], temp_grid.ReadAsArray().astype(float).shape[1] + return ( + temp_grid.ReadAsArray().astype(float), + temp_grid.ReadAsArray().astype(float).shape[0], + temp_grid.ReadAsArray().astype(float).shape[1], + ) + def get_latlon(raster, gdal_raster): old_cs = osr.SpatialReference() @@ -71,28 +85,32 @@ def get_latlon(raster, gdal_raster): heightx = gdal_raster.RasterYSize geotransform = gdal_raster.GetGeoTransform() minx = geotransform[0] - miny = geotransform[3] + widthx * geotransform[4] + heightx * geotransform[5] + miny = ( + geotransform[3] + widthx * geotransform[4] + heightx * geotransform[5] + ) lonlat = transform.TransformPoint(minx, miny) gdalver = float(gdal.__version__[0]) - if gdalver == 3.: - lon = lonlat[1] #changed to gdal 3 - lat = lonlat[0] #changed to gdal 3 + if gdalver == 3.0: + lon = lonlat[1] # changed to gdal 3 + lat = lonlat[0] # changed to gdal 3 else: - lon = lonlat[0] #changed to gdal 2 - lat = lonlat[1] #changed to gdal 2 + lon = lonlat[0] # changed to gdal 2 + lat = lonlat[1] # changed to gdal 2 return lat, lon + class ProcessingSpatialTCAlgorithm(QgsProcessingAlgorithm): """ This class is a postprocessing algoritm to calculate Spatial thermal comfort maps """ + # SOLWEIG_DIR = 'SOLWEIG_DIR' - TMRT_MAP = 'TMRT_MAP' - BUILDING_MAP = 'BUILDING_MAP' - UROCK_MAP = 'UROCK_MAP' - TC_TYPE = 'TC_TYPE' - METDATA = 'METDATA' + TMRT_MAP = "TMRT_MAP" + BUILDING_MAP = "BUILDING_MAP" + UROCK_MAP = "UROCK_MAP" + TC_TYPE = "TC_TYPE" + METDATA = "METDATA" # KUP_MAP = 'KUP_MAP' # KDOWN_MAP = 'KDOWN_MAP' @@ -100,98 +118,166 @@ class ProcessingSpatialTCAlgorithm(QgsProcessingAlgorithm): # LUP_MAP = 'LUP_MAP' # LDOWN_MAP = 'LDOWN_MAP' - #PET parameters - AGE = 'AGE' - ACTIVITY = 'ACTIVITY' - CLO = 'CLO' - WEIGHT = 'WEIGHT' - HEIGHT = 'HEIGHT' - SEX = 'SEX' + # PET parameters + AGE = "AGE" + ACTIVITY = "ACTIVITY" + CLO = "CLO" + WEIGHT = "WEIGHT" + HEIGHT = "HEIGHT" + SEX = "SEX" # SENSOR_HEIGHT = 'SENSOR_HEIGHT' - COMFA = 'COMFA' + COMFA = "COMFA" # Output - TC_OUT = 'TC_OUT' - + TC_OUT = "TC_OUT" def initAlgorithm(self, config): # self.addParameter(QgsProcessingParameterFile(self.SOLWEIG_DIR, # self.tr('Path to SOLWEIG output folder'), # QgsProcessingParameterFile.Folder, # optional=False)) - self.addParameter(QgsProcessingParameterRasterLayer(self.TMRT_MAP, - self.tr('Mean radiant temperature raster (SOLWEIG)'), - '', - optional=False)) - self.addParameter(QgsProcessingParameterRasterLayer(self.UROCK_MAP, - self.tr('Pedestrian wind speed raster (URock)'), - '', - optional=False)) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.TMRT_MAP, + self.tr("Mean radiant temperature raster (SOLWEIG)"), + "", + optional=False, + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.UROCK_MAP, + self.tr("Pedestrian wind speed raster (URock)"), + "", + optional=False, + ) + ) # self.addParameter(QgsProcessingParameterRasterLayer(self.BUILDING_MAP, # self.tr('Raster to exclude building pixels from analysis (SOLWEIG)'), - # '', + # '', # optional=False)) # self.addParameter(QgsProcessingParameterFile(self.METDATA, # self.tr('Input meteorological file (.txt). Same as used for Tmrt map.'), extension='txt', # optional=False)) - self.varType = ((self.tr('Physiological Equivalent Temperature (PET)'), '0'), - (self.tr('Universal Thermal Climate Index (UTCI)'), '1'), - (self.tr('COMfort FormulA (COMFA)'), '2')) - self.addParameter(QgsProcessingParameterEnum(self.TC_TYPE, - self.tr('Thermal Index to map'), - options=[i[0] for i in self.varType], - defaultValue=0)) - - #PET parameters - age = QgsProcessingParameterNumber(self.AGE, self.tr('Age (yy)'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(35), optional=True, minValue=0, maxValue=120) - age.setFlags(age.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + self.varType = ( + (self.tr("Physiological Equivalent Temperature (PET)"), "0"), + (self.tr("Universal Thermal Climate Index (UTCI)"), "1"), + (self.tr("COMfort FormulA (COMFA)"), "2"), + ) + self.addParameter( + QgsProcessingParameterEnum( + self.TC_TYPE, + self.tr("Thermal Index to map"), + options=[i[0] for i in self.varType], + defaultValue=0, + ) + ) + + # PET parameters + age = QgsProcessingParameterNumber( + self.AGE, + self.tr("Age (yy)"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(35), + optional=True, + minValue=0, + maxValue=120, + ) + age.setFlags( + age.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(age) - act = QgsProcessingParameterNumber(self.ACTIVITY, self.tr('Activity (W)'), - QgsProcessingParameterNumber.Type.Double, - QVariant(80), optional=True, minValue=0, maxValue=1000) - act.setFlags(act.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + act = QgsProcessingParameterNumber( + self.ACTIVITY, + self.tr("Activity (W)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(80), + optional=True, + minValue=0, + maxValue=1000, + ) + act.setFlags( + act.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(act) - clo = QgsProcessingParameterNumber(self.CLO, self.tr('Clothing (clo)'), - QgsProcessingParameterNumber.Type.Double, - QVariant(0.9), optional=True, minValue=0, maxValue=10) - clo.setFlags(clo.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + clo = QgsProcessingParameterNumber( + self.CLO, + self.tr("Clothing (clo)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(0.9), + optional=True, + minValue=0, + maxValue=10, + ) + clo.setFlags( + clo.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(clo) - wei = QgsProcessingParameterNumber(self.WEIGHT, self.tr('Weight (kg)'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(75), optional=True, minValue=0, maxValue=500) - wei.setFlags(wei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + wei = QgsProcessingParameterNumber( + self.WEIGHT, + self.tr("Weight (kg)"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(75), + optional=True, + minValue=0, + maxValue=500, + ) + wei.setFlags( + wei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(wei) - hei = QgsProcessingParameterNumber(self.HEIGHT, self.tr('Height (cm)'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(180), optional=True, minValue=0, maxValue=250) - hei.setFlags(hei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + hei = QgsProcessingParameterNumber( + self.HEIGHT, + self.tr("Height (cm)"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(180), + optional=True, + minValue=0, + maxValue=250, + ) + hei.setFlags( + hei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(hei) sex = QgsProcessingParameterEnum( - self.SEX, self.tr('Sex'), ['Male', 'Female'], optional=True, defaultValue=0) - sex.setFlags(sex.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + self.SEX, + self.tr("Sex"), + ["Male", "Female"], + optional=True, + defaultValue=0, + ) + sex.setFlags( + sex.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(sex) # COMFA or COMFA-kid - comfa = QgsProcessingParameterBoolean(self.COMFA, - self.tr("COMFA-kid (Cheng and Brown, 2020)"), defaultValue=False) - comfa.setFlags(comfa.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) - self.addParameter(comfa) + comfa = QgsProcessingParameterBoolean( + self.COMFA, + self.tr("COMFA-kid (Cheng and Brown, 2020)"), + defaultValue=False, + ) + comfa.setFlags( + comfa.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) + self.addParameter(comfa) # Output - self.addParameter(QgsProcessingParameterRasterDestination(self.TC_OUT, - self.tr("Output thermal comfort raster"), - None, - optional=False)) - + self.addParameter( + QgsProcessingParameterRasterDestination( + self.TC_OUT, + self.tr("Output thermal comfort raster"), + None, + optional=False, + ) + ) def processAlgorithm(self, parameters, context, feedback): - + # InputParameters # solweigDir = self.parameterAsString(parameters, self.SOLWEIG_DIR, context) - + tmrt = self.parameterAsRasterLayer(parameters, self.TMRT_MAP, context) # Kup = self.parameterAsRasterLayer(parameters, self.KUP_MAP, context) # Kdown = self.parameterAsRasterLayer(parameters, self.KDOWN_MAP, context) @@ -199,21 +285,23 @@ def processAlgorithm(self, parameters, context, feedback): # Lup = self.parameterAsRasterLayer(parameters, self.LUP_MAP, context) # Ldown = self.parameterAsRasterLayer(parameters, self.LDOWN_MAP, context) ws = self.parameterAsRasterLayer(parameters, self.UROCK_MAP, context) - #sensorheight = self.parameterAsDouble(parameters, self.SENSOR_HEIGHT, context) - sensorheight = 1.5 # Should be retrieved from URock RunInfo + # sensorheight = self.parameterAsDouble(parameters, self.SENSOR_HEIGHT, context) + sensorheight = 1.5 # Should be retrieved from URock RunInfo # buildings = self.parameterAsRasterLayer(parameters, self.BUILDING_MAP, context) tcTypeStr = self.parameterAsString(parameters, self.TC_TYPE, context) tcType = int(tcTypeStr) if tcType == 0: - thermal_index = 'Physiological Equivalent Temperature (PET)' + thermal_index = "Physiological Equivalent Temperature (PET)" elif tcType == 1: - thermal_index = 'Universal Thermal Climate Index (UTCI)' + thermal_index = "Universal Thermal Climate Index (UTCI)" elif tcType == 2: - thermal_index = 'COMfort FormulA (COMFA)' + thermal_index = "COMfort FormulA (COMFA)" comfa_kid = self.parameterAsBool(parameters, self.COMFA, context) # metdata = self.parameterAsString(parameters, self.METDATA, context) - outputRaster = self.parameterAsOutputLayer(parameters, self.TC_OUT, context) + outputRaster = self.parameterAsOutputLayer( + parameters, self.TC_OUT, context + ) mbody = None ht = None @@ -225,39 +313,49 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText("Initializing...") # Get SOLWEIG output folder path from Tmrt raster path provider = tmrt.dataProvider() - filepath_tmrt = str(provider.dataSourceUri()) # Path for Tmrt raster - solweig_path = os.path.dirname(os.path.abspath(filepath_tmrt)) #issue #702 - #solweig_path = filepath_tmrt.split('Tmrt')[0] # Path to SOLWEIG output folder, i.e. where Tmrt raster is located + filepath_tmrt = str(provider.dataSourceUri()) # Path for Tmrt raster + solweig_path = os.path.dirname( + os.path.abspath(filepath_tmrt) + ) # issue #702 + # solweig_path = filepath_tmrt.split('Tmrt')[0] # Path to SOLWEIG output folder, i.e. where Tmrt raster is located _, solweigfile = os.path.split(filepath_tmrt) - # solweig_path = os.path.dirname(filepath_tmrt) # issue 31 + # solweig_path = os.path.dirname(filepath_tmrt) # issue 31 # LOAD raster data - try: #response to issue #51 + try: # response to issue #51 # Load buildings raster (should be in SOLWEIG output folder) - filepath_build = solweig_path + '/buildings.tif' + filepath_build = solweig_path + "/buildings.tif" gdal_buildings = gdal.Open(filepath_build) build = gdal_buildings.ReadAsArray().astype(float) except: - raise QgsProcessingException("Error: No building raster found. It should be located in the same folder as the output" - " folder specified when SOLWEIG was executed. Make sure you ticked in 'Save necessary rasters for" - " Tree planter and Spatial TC tools' when you ran SOLWEIG.") - + raise QgsProcessingException( + "Error: No building raster found. It should be located in the same folder as the output" + " folder specified when SOLWEIG was executed. Make sure you ticked in 'Save necessary rasters for" + " Tree planter and Spatial TC tools' when you ran SOLWEIG." + ) + # Get latlon from grid coordinate system lat, lon = get_latlon(tmrt, gdal_buildings) # LOAD Metdata try: - metdata = np.loadtxt(solweig_path + '/metforcing.txt', skiprows=1, delimiter=' ') + metdata = np.loadtxt( + solweig_path + "/metforcing.txt", skiprows=1, delimiter=" " + ) except: - raise QgsProcessingException("Error: Make sure format of meteorological file is correct. You can " - "prepare your data by using 'Prepare Existing Data' in " - "the Pre-processor") + raise QgsProcessingException( + "Error: Make sure format of meteorological file is correct. You can " + "prepare your data by using 'Prepare Existing Data' in " + "the Pre-processor" + ) if metdata.shape[1] == 24: feedback.setProgressText("Meteorological data successfully loaded") else: - raise QgsProcessingException("Error: Wrong number of columns in meteorological data. Use the forcing file " - "that was used when Tmrt was calculated") + raise QgsProcessingException( + "Error: Wrong number of columns in meteorological data. Use the forcing file " + "that was used when Tmrt was calculated" + ) # Creating vectors from meteorological input # yyyy = metdata[:,0] @@ -273,16 +371,29 @@ def processAlgorithm(self, parameters, context, feedback): # Ws = self.metdata[:, 9] # Derive metdata from Trmt raster name - yyyyTmrt = int(solweigfile.split('_')[-3]) #int(filepath_tmrt[-18:-14]) #issue 571 - doyTmrt = int(solweigfile.split('_')[-2]) #int(filepath_tmrt[-13:-10]) + yyyyTmrt = int( + solweigfile.split("_")[-3] + ) # int(filepath_tmrt[-18:-14]) #issue 571 + doyTmrt = int( + solweigfile.split("_")[-2] + ) # int(filepath_tmrt[-13:-10]) hoursTmrt = int(filepath_tmrt[-9:-7]) minuTmrt = int(filepath_tmrt[-7:-5]) - if np.where(metdata[:,2] == hoursTmrt).__len__() == 1: - posMet = np.where(metdata[:,0] == yyyyTmrt) and np.where(metdata[:,1] == doyTmrt) and np.where(metdata[:,2] == hoursTmrt) + if np.where(metdata[:, 2] == hoursTmrt).__len__() == 1: + posMet = ( + np.where(metdata[:, 0] == yyyyTmrt) + and np.where(metdata[:, 1] == doyTmrt) + and np.where(metdata[:, 2] == hoursTmrt) + ) else: - posMet = np.where(metdata[:,0] == yyyyTmrt) and np.where(metdata[:,1] == doyTmrt) and np.where(metdata[:,2] == hoursTmrt) and np.where(metdata[:,3] == minuTmrt) - + posMet = ( + np.where(metdata[:, 0] == yyyyTmrt) + and np.where(metdata[:, 1] == doyTmrt) + and np.where(metdata[:, 2] == hoursTmrt) + and np.where(metdata[:, 3] == minuTmrt) + ) + Ta = metdata[posMet, 11][0][0] RH = metdata[posMet, 10][0][0] YYYY = metdata[posMet, 0][0][0] @@ -291,30 +402,69 @@ def processAlgorithm(self, parameters, context, feedback): minu = metdata[posMet, 3][0][0] # Timestamp - current_time = str((pd.to_datetime(YYYY, format='%Y') + pd.to_timedelta(jday - 1, unit='d') + - pd.to_timedelta(hours, unit='h') + pd.to_timedelta(minu, unit='m'))) - - feedback.setProgressText('Estimating ' + thermal_index + ' on ' + current_time) - feedback.setProgressText('Location: ' + str(np.around(lat, decimals=2)) + ' latitude, ' + str(np.around(lon, decimals=2)) + ' longitude') - feedback.setProgressText("Air temperature derived from meteorological data is: " + str(Ta) + ' ' + u'\N{DEGREE SIGN}C') - feedback.setProgressText("Relative Humidity derived from meteorological data is: " + str(RH) + '%') - feedback.setProgressText("Incoming shortwave radiation derived from meteorological data is: " + str(metdata[posMet[0], 14][0]) + u' Wm\u00b2') + current_time = str( + ( + pd.to_datetime(YYYY, format="%Y") + + pd.to_timedelta(jday - 1, unit="d") + + pd.to_timedelta(hours, unit="h") + + pd.to_timedelta(minu, unit="m") + ) + ) + + feedback.setProgressText( + "Estimating " + thermal_index + " on " + current_time + ) + feedback.setProgressText( + "Location: " + + str(np.around(lat, decimals=2)) + + " latitude, " + + str(np.around(lon, decimals=2)) + + " longitude" + ) + feedback.setProgressText( + "Air temperature derived from meteorological data is: " + + str(Ta) + + " " + + "\N{DEGREE SIGN}C" + ) + feedback.setProgressText( + "Relative Humidity derived from meteorological data is: " + + str(RH) + + "%" + ) + feedback.setProgressText( + "Incoming shortwave radiation derived from meteorological data is: " + + str(metdata[posMet[0], 14][0]) + + " Wm\u00b2" + ) # feedback.setProgressText("Wind speed derived from meteorological data is: " + str(metdata[posMet[0], 9][0]) + ' m/s') # Loading Kup, Kdown, Kdiff, Lup and Ldown if calculating COMFA if tcType == 2: - filepath = filepath_tmrt.split('Tmrt') + filepath = filepath_tmrt.split("Tmrt") # filepath = os.path.dirname(filepath_tmrt) # issue 31 filepath_tmrt.split('Tmrt') # Load Kup, Kdown, Lup, Ldown grids - Kup, rows, cols = load_grid(filepath[0] + 'Kup' + filepath[1], feedback) - Kdown, _, __ = load_grid(filepath[0] + 'Kdown' + filepath[1], feedback) - Kdiff, _, __ = load_grid(filepath[0] + 'Kdiff' + filepath[1], feedback) - Lup, _, __ = load_grid(filepath[0] + 'Lup' + filepath[1], feedback) - Ldown, _, __ = load_grid(filepath[0] + 'Ldown' + filepath[1], feedback) - settingsSolweig = np.loadtxt(filepath[0] + '/treeplantersettings.txt', skiprows=1, delimiter=' ') + Kup, rows, cols = load_grid( + filepath[0] + "Kup" + filepath[1], feedback + ) + Kdown, _, __ = load_grid( + filepath[0] + "Kdown" + filepath[1], feedback + ) + Kdiff, _, __ = load_grid( + filepath[0] + "Kdiff" + filepath[1], feedback + ) + Lup, _, __ = load_grid(filepath[0] + "Lup" + filepath[1], feedback) + Ldown, _, __ = load_grid( + filepath[0] + "Ldown" + filepath[1], feedback + ) + settingsSolweig = np.loadtxt( + filepath[0] + "/treeplantersettings.txt", + skiprows=1, + delimiter=" ", + ) UTC = int(settingsSolweig[0]) alt = settingsSolweig[12] - location = {'longitude': lon, 'latitude': lat, 'altitude': alt} + location = {"longitude": lon, "latitude": lat, "altitude": alt} else: # Load Tmrt grid for PET and UTCI tmrtGrid, rows, cols = load_grid(filepath_tmrt, feedback) @@ -324,26 +474,45 @@ def processAlgorithm(self, parameters, context, feedback): filename_ws = str(provider.dataSourceUri()) wsGrid, rows2, cols2 = load_grid(filename_ws, feedback) - rows3 = build.shape[0] # Number of rows in building grid - cols3 = build.shape[1] # Number of columns in building grid + rows3 = build.shape[0] # Number of rows in building grid + cols3 = build.shape[1] # Number of columns in building grid if not (rows == rows2) & (cols == cols2): - raise QgsProcessingException("Error: Wind speed raster not same domain as Tmrt raster: All rasters must be of same extent and resolution") + raise QgsProcessingException( + "Error: Wind speed raster not same domain as Tmrt raster: All rasters must be of same extent and resolution" + ) if not (rows == rows3) & (cols == cols3): - raise QgsProcessingException("Error: Buildings raster not same domain as Tmrt raster: All rasters must be of same extent and resolution") + raise QgsProcessingException( + "Error: Buildings raster not same domain as Tmrt raster: All rasters must be of same extent and resolution" + ) # Physiological variables for PET and COMFA - mbody = self.parameterAsDouble(parameters, self.WEIGHT, context) # Body weight in kg - clo = self.parameterAsDouble(parameters, self.CLO, context) # Clothing in clo - age = self.parameterAsDouble(parameters, self.AGE, context) # Age in years - activity = self.parameterAsDouble(parameters, self.ACTIVITY, context) # Activity in watt - sex = self.parameterAsInt(parameters, self.SEX, context) + 1 # Sex, #TODO CHECK SO SAME FOR PET AND COMFA + mbody = self.parameterAsDouble( + parameters, self.WEIGHT, context + ) # Body weight in kg + clo = self.parameterAsDouble( + parameters, self.CLO, context + ) # Clothing in clo + age = self.parameterAsDouble( + parameters, self.AGE, context + ) # Age in years + activity = self.parameterAsDouble( + parameters, self.ACTIVITY, context + ) # Activity in watt + sex = ( + self.parameterAsInt(parameters, self.SEX, context) + 1 + ) # Sex, #TODO CHECK SO SAME FOR PET AND COMFA if tcType == 0: - feedback.setProgressText("Calculating PET for all ground level pixels") + feedback.setProgressText( + "Calculating PET for all ground level pixels" + ) # Other PET variables - ht = self.parameterAsDouble(parameters, self.HEIGHT, context) / 100. # Body height in meters + ht = ( + self.parameterAsDouble(parameters, self.HEIGHT, context) + / 100.0 + ) # Body height in meters pet.mbody = mbody pet.age = age @@ -353,44 +522,70 @@ def processAlgorithm(self, parameters, context, feedback): pet.clo = clo # Wind speed - #WsPET = (10. / sensorheight) ** 0.2 * wsGrid + # WsPET = (10. / sensorheight) ** 0.2 * wsGrid - result = pet.calculate_PET_grid(Ta, RH, tmrtGrid, wsGrid, pet, feedback) + result = pet.calculate_PET_grid( + Ta, RH, tmrtGrid, wsGrid, pet, feedback + ) elif tcType == 1: - feedback.setProgressText("Calculating UTCI for all ground level pixels") + feedback.setProgressText( + "Calculating UTCI for all ground level pixels" + ) # Recalculating wind speed based on power law - WsUTCI = (10. / sensorheight) ** 0.2 * wsGrid - result = utci.utci_calculator_grid(Ta, RH, tmrtGrid, WsUTCI, feedback) + WsUTCI = (10.0 / sensorheight) ** 0.2 * wsGrid + result = utci.utci_calculator_grid( + Ta, RH, tmrtGrid, WsUTCI, feedback + ) elif tcType == 2: # If True = COMFA-kid (Cheng & Brown, 2020), if False = regular COMFA if comfa_kid: - feedback.setProgressText("Calculating energy balance (COMFA-kid (Cheng and Brown, 2020)) for all ground level pixels") + feedback.setProgressText( + "Calculating energy balance (COMFA-kid (Cheng and Brown, 2020)) for all ground level pixels" + ) else: - feedback.setProgressText("Calculating energy balance (COMFA) for all ground level pixels") + feedback.setProgressText( + "Calculating energy balance (COMFA) for all ground level pixels" + ) # COMFA parameters # Settings # Atr = 0.7 # atmospheric transmittance - alpha = 0.37 # Albedo of the cylinder - emis = 0.95 # Emissivity of the cylinder - Aeff = 0.78 # Effective area of body. 0.78 for standing from Campbell and Normal (1998) (0.70 for sitting) - L = 0.1 # length of cylinder (cm) - D = 0.01 # Diameter of cylinder (cm) - ht = self.parameterAsDouble(parameters, self.HEIGHT, context) # Height of person in cm + alpha = 0.37 # Albedo of the cylinder + emis = 0.95 # Emissivity of the cylinder + Aeff = 0.78 # Effective area of body. 0.78 for standing from Campbell and Normal (1998) (0.70 for sitting) + L = 0.1 # length of cylinder (cm) + D = 0.01 # Diameter of cylinder (cm) + ht = self.parameterAsDouble( + parameters, self.HEIGHT, context + ) # Height of person in cm # Calculate COMFA radiation using SOLWEIG output L, D, Lin, Lup, Kin, Kup, emis, alpha, Aeff, Kd, metdata, location, utc - Rabs = COMFA_rad(L, D, Ldown, Lup, Kdown, Kup, emis, alpha, Aeff, Kdiff, metdata[posMet[0],:], location, UTC) + Rabs = COMFA_rad( + L, + D, + Ldown, + Lup, + Kdown, + Kup, + emis, + alpha, + Aeff, + Kdiff, + metdata[posMet[0], :], + location, + UTC, + ) # Recalculating wind speed based on powerlaw WsCOMFA = (10 / sensorheight) ** 0.2 * wsGrid # Mact (Wm-2) based on Cheng & Brown (2020) - Mact, Mact_PET = COMFA_Mact(mbody, ht, sex, age, activity, 'W') + Mact, Mact_PET = COMFA_Mact(mbody, ht, sex, age, activity, "W") # Activity speed - va = 0. + va = 0.0 # Rco: rco = Icl * 186.6. Or convert from conductivity based on 1 Clo = 0.1555 (m 2K W-1). rco = clo * 186.6 - # Rcvo: Can convert from a conductivity value of Icl, where Icl is found in tables for specific clothing ensembles from ISO (2007). - # 1) convert from Icl to Re,cl (m2 kPa W-1): Re,cl = Icl (m 2K W-1)*0.18 (constant from ISO 9920 pg 12 based on 1 or 2-layer clothing ensembles). - # 2) Convert Re,cl to rcvo: rcvo = Re,cl*18,400, where 18400 is a conversion factor from Re,cl (vapour resistance, in m2kPaW-1) + # Rcvo: Can convert from a conductivity value of Icl, where Icl is found in tables for specific clothing ensembles from ISO (2007). + # 1) convert from Icl to Re,cl (m2 kPa W-1): Re,cl = Icl (m 2K W-1)*0.18 (constant from ISO 9920 pg 12 based on 1 or 2-layer clothing ensembles). + # 2) Convert Re,cl to rcvo: rcvo = Re,cl*18,400, where 18400 is a conversion factor from Re,cl (vapour resistance, in m2kPaW-1) # to rcvo, using Lv = 2.5*106 J kg-1, rho = 1.16 kg m-3, and Pa = 98kPa. rcvo = clo * 0.18 * 18400 @@ -400,9 +595,23 @@ def processAlgorithm(self, parameters, context, feedback): TREMIT = np.zeros((Rabs.shape[0], Rabs.shape[1])) for y in np.arange(Rabs.shape[0]): for x in np.arange(Rabs.shape[1]): - MET[y,x], CONV[y,x], EVAP[y,x], TREMIT[y,x] = COMFA_BUDGET(Mact, Ta, RH, WsCOMFA[y,x], va, rco, rcvo, mbody, ht, age, comfa_kid) - result = MET + Rabs - CONV - EVAP - TREMIT - + MET[y, x], CONV[y, x], EVAP[y, x], TREMIT[y, x] = ( + COMFA_BUDGET( + Mact, + Ta, + RH, + WsCOMFA[y, x], + va, + rco, + rcvo, + mbody, + ht, + age, + comfa_kid, + ) + ) + result = MET + Rabs - CONV - EVAP - TREMIT + result[build == 0] = -9999 saveraster(gdal_buildings, outputRaster, result) @@ -410,9 +619,9 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText("Processing finished.") return {self.TC_OUT: outputRaster} - + def name(self): - return 'Outdoor Thermal Comfort: Spatial Thermal Comfort' + return "Outdoor Thermal Comfort: Spatial Thermal Comfort" def displayName(self): return self.tr(self.name()) @@ -421,25 +630,29 @@ def group(self): return self.tr(self.groupId()) def groupId(self): - return 'Post-Processor' + return "Post-Processor" def shortHelpString(self): - return self.tr('The Spatial TC (Thermal Comfort) plugin can be used to create maps of spatial variation of thermal comfort indices by exploiting output from the SOLWEIG model together with the URock model in UMEP. All tools need to be executed via the Processing toolbox in QGIS and no filenames and folder structures should be changed.
' - '\n' - 'The Tmrt raster should be located in the SOLWEIG output folder. Remember to tick "Save necessary raster(s) for the TreePlanter and Spatial TC tools" when running SOLWEIG.' - '\n' - '--------------\n' - 'Full manual available via the Help-button.') + return self.tr( + "The Spatial TC (Thermal Comfort) plugin can be used to create maps of spatial variation of thermal comfort indices by exploiting output from the SOLWEIG model together with the URock model in UMEP. All tools need to be executed via the Processing toolbox in QGIS and no filenames and folder structures should be changed.
" + "\n" + 'The Tmrt raster should be located in the SOLWEIG output folder. Remember to tick "Save necessary raster(s) for the TreePlanter and Spatial TC tools" when running SOLWEIG.' + "\n" + "--------------\n" + "Full manual available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/post_processor/Outdoor%20Thermal%20Comfort%20SOLWEIG%20Analyzer.html" return url def tr(self, string): - return QCoreApplication.translate('Post-Processing', string) + return QCoreApplication.translate("Post-Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/stc.png") return icon diff --git a/postprocessor/suewsanalyzer_algorithm.py b/postprocessor/suewsanalyzer_algorithm.py index f6b9e6f..be98a70 100644 --- a/postprocessor/suewsanalyzer_algorithm.py +++ b/postprocessor/suewsanalyzer_algorithm.py @@ -1,28 +1,30 @@ # -*- coding: utf-8 -*- -__author__ = 'Fredrik Lindberg' -__date__ = '2021-02-05' -__copyright__ = '(C) 2021 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2021-02-05" +__copyright__ = "(C) 2021 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QVariant, QDate, Qt -from qgis.core import (QgsProcessing, - QgsProcessingAlgorithm, - QgsProcessingParameterBoolean, - QgsProcessingParameterNumber, - QgsProcessingParameterRasterDestination, - QgsProcessingParameterString, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterField, - QgsProcessingParameterEnum, - QgsProcessingException, - QgsVectorDataProvider, - QgsProcessingParameterFile, - QgsField) +from qgis.core import ( + QgsProcessing, + QgsProcessingAlgorithm, + QgsProcessingParameterBoolean, + QgsProcessingParameterNumber, + QgsProcessingParameterRasterDestination, + QgsProcessingParameterString, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterField, + QgsProcessingParameterEnum, + QgsProcessingException, + QgsVectorDataProvider, + QgsProcessingParameterFile, + QgsField, +) from qgis.PyQt.QtGui import QIcon from processing.gui.wrappers import WidgetWrapper @@ -35,200 +37,284 @@ import inspect from pathlib import Path import shutil -from ..util.misc import saveraster, get_resolution_from_file, SUEWS_txt_to_df, extract_suews_years +from ..util.misc import ( + saveraster, + get_resolution_from_file, + SUEWS_txt_to_df, + extract_suews_years, +) from .params_dict import * import yaml + class ProcessingSuewsAnalyzerAlgorithm(QgsProcessingAlgorithm): """ This class is a processing version of SuewsAnalyzer but only for generating aggregated grids """ - SUEWS_NL = 'SUEWS_NL' - VARIA_IN = 'VARIA_IN' - INPUT_POLYGONLAYER = 'INPUT_POLYGONLAYER' - ID_FIELD = 'ID_FIELD' - YEAR = 'YEAR' - DATEINISTART = 'DATEINISTART' - DATEINIEND = 'DATEINIEND' - IRREGULAR = 'IRREGULAR' - PIXELSIZE = 'PIXELSIZE' - TIME_OF_DAY = 'TIME_OF_DAY' - STAT_TYPE = 'STAT_TYPE' - ADD_ATTRIBUTES ='ADD_ATTRIBUTES' - - # Output - SUEWS_GRID_OUT = 'SUEWS_GRID_OUT' + SUEWS_NL = "SUEWS_NL" + VARIA_IN = "VARIA_IN" + INPUT_POLYGONLAYER = "INPUT_POLYGONLAYER" + ID_FIELD = "ID_FIELD" + YEAR = "YEAR" + DATEINISTART = "DATEINISTART" + DATEINIEND = "DATEINIEND" + IRREGULAR = "IRREGULAR" + PIXELSIZE = "PIXELSIZE" + TIME_OF_DAY = "TIME_OF_DAY" + STAT_TYPE = "STAT_TYPE" + ADD_ATTRIBUTES = "ADD_ATTRIBUTES" + + # Output + SUEWS_GRID_OUT = "SUEWS_GRID_OUT" def initAlgorithm(self, config): self.plugin_dir = os.path.dirname(__file__) - var_list = [] idx = 0 for item in list(sorted(list(params_dict.keys()))): item_desc = item + f" ({params_dict[item]['description']})" item = tuple([item_desc, str(idx)]) var_list.append(item) - idx = idx+1 + idx = idx + 1 self.varType = tuple(var_list) - self.addParameter(QgsProcessingParameterFile(self.SUEWS_NL, - self.tr('Yaml-file'), - extension='yml', - optional=False)) - self.addParameter(QgsProcessingParameterEnum(self.VARIA_IN, - self.tr('Variable to post-process'), - options=[i[0] for i in self.varType], - defaultValue=26)) - self.dayType = ((self.tr('Diurnal'), '0'), - (self.tr('Daytime'), '1'), - (self.tr('Nighttime'), '2')) - self.addParameter(QgsProcessingParameterEnum(self.TIME_OF_DAY, - self.tr('Time of day'), - options=[i[0] for i in self.dayType], - defaultValue=0)) - self.statType = ((self.tr('Mean'), '0'), - (self.tr('Minimum'), '1'), - (self.tr('Maximum'), '2'), - (self.tr('Median'), '3'), - (self.tr('IQR'), '4')) - self.addParameter(QgsProcessingParameterEnum(self.STAT_TYPE, - self.tr('Statistic measure'), - options=[i[0] for i in self.statType], - defaultValue=0)) - paramS = QgsProcessingParameterString(self.DATEINISTART, 'Start date') - paramS.setMetadata({'widget_wrapper': {'class': DateWidgetStart}}) + self.addParameter( + QgsProcessingParameterFile( + self.SUEWS_NL, + self.tr("Yaml-file"), + extension="yml", + optional=False, + ) + ) + self.addParameter( + QgsProcessingParameterEnum( + self.VARIA_IN, + self.tr("Variable to post-process"), + options=[i[0] for i in self.varType], + defaultValue=26, + ) + ) + self.dayType = ( + (self.tr("Diurnal"), "0"), + (self.tr("Daytime"), "1"), + (self.tr("Nighttime"), "2"), + ) + self.addParameter( + QgsProcessingParameterEnum( + self.TIME_OF_DAY, + self.tr("Time of day"), + options=[i[0] for i in self.dayType], + defaultValue=0, + ) + ) + self.statType = ( + (self.tr("Mean"), "0"), + (self.tr("Minimum"), "1"), + (self.tr("Maximum"), "2"), + (self.tr("Median"), "3"), + (self.tr("IQR"), "4"), + ) + self.addParameter( + QgsProcessingParameterEnum( + self.STAT_TYPE, + self.tr("Statistic measure"), + options=[i[0] for i in self.statType], + defaultValue=0, + ) + ) + paramS = QgsProcessingParameterString(self.DATEINISTART, "Start date") + paramS.setMetadata({"widget_wrapper": {"class": DateWidgetStart}}) self.addParameter(paramS) - paramE = QgsProcessingParameterString(self.DATEINIEND, 'End date') - paramE.setMetadata({'widget_wrapper': {'class': DateWidgetEnd}}) + paramE = QgsProcessingParameterString(self.DATEINIEND, "End date") + paramE.setMetadata({"widget_wrapper": {"class": DateWidgetEnd}}) self.addParameter(paramE) - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_POLYGONLAYER, - self.tr('Vector polygon grid'), - [QgsProcessing.SourceType.TypeVectorPolygon])) - self.addParameter(QgsProcessingParameterField(self.ID_FIELD, - self.tr('ID field'), - '', - self.INPUT_POLYGONLAYER, - QgsProcessingParameterField.DataType.Numeric)) - self.addParameter(QgsProcessingParameterBoolean(self.IRREGULAR, - self.tr("Polygon grid irregular (not squared)"), - defaultValue=False)) - self.addParameter(QgsProcessingParameterNumber(self.PIXELSIZE, - self.tr('Pixelsize if irregular grid is used (meter)'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(10), False, minValue=1)) + self.addParameter( + QgsProcessingParameterFeatureSource( + self.INPUT_POLYGONLAYER, + self.tr("Vector polygon grid"), + [QgsProcessing.SourceType.TypeVectorPolygon], + ) + ) + self.addParameter( + QgsProcessingParameterField( + self.ID_FIELD, + self.tr("ID field"), + "", + self.INPUT_POLYGONLAYER, + QgsProcessingParameterField.DataType.Numeric, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.IRREGULAR, + self.tr("Polygon grid irregular (not squared)"), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.PIXELSIZE, + self.tr("Pixelsize if irregular grid is used (meter)"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(10), + False, + minValue=1, + ) + ) # Output - self.addParameter(QgsProcessingParameterBoolean(self.ADD_ATTRIBUTES, - self.tr("Add results to Vector polygon grid attribute table"), - defaultValue=False)) - self.addParameter(QgsProcessingParameterRasterDestination(self.SUEWS_GRID_OUT, - self.tr("Output raster from statistical analysis"), - None, - optional=False)) + self.addParameter( + QgsProcessingParameterBoolean( + self.ADD_ATTRIBUTES, + self.tr("Add results to Vector polygon grid attribute table"), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterRasterDestination( + self.SUEWS_GRID_OUT, + self.tr("Output raster from statistical analysis"), + None, + optional=False, + ) + ) def processAlgorithm(self, parameters, context, feedback): - + # InputParameters suewsNL = self.parameterAsString(parameters, self.SUEWS_NL, context) variaIn = self.parameterAsString(parameters, self.VARIA_IN, context) - startday = self.parameterAsString(parameters, self.DATEINISTART, context) + startday = self.parameterAsString( + parameters, self.DATEINISTART, context + ) endday = self.parameterAsString(parameters, self.DATEINIEND, context) - inputPolygonlayer = self.parameterAsVectorLayer(parameters, self.INPUT_POLYGONLAYER, context) + inputPolygonlayer = self.parameterAsVectorLayer( + parameters, self.INPUT_POLYGONLAYER, context + ) idField = self.parameterAsFields(parameters, self.ID_FIELD, context) irreg = self.parameterAsBool(parameters, self.IRREGULAR, context) - statTypeStr = self.parameterAsString(parameters, self.STAT_TYPE, context) - dayTypeStr = self.parameterAsString(parameters, self.TIME_OF_DAY, context) + statTypeStr = self.parameterAsString( + parameters, self.STAT_TYPE, context + ) + dayTypeStr = self.parameterAsString( + parameters, self.TIME_OF_DAY, context + ) pixelsize = self.parameterAsDouble(parameters, self.PIXELSIZE, context) - addAttributes = self.parameterAsBool(parameters, self.ADD_ATTRIBUTES, context) - outputStat = self.parameterAsOutputLayer(parameters, self.SUEWS_GRID_OUT, context) + addAttributes = self.parameterAsBool( + parameters, self.ADD_ATTRIBUTES, context + ) + outputStat = self.parameterAsOutputLayer( + parameters, self.SUEWS_GRID_OUT, context + ) feedback.setProgressText("Initializing...") - with open(suewsNL, 'r') as f: - yaml_dict = yaml.load(f, Loader=yaml.SafeLoader) + with open(suewsNL, "r") as f: + yaml_dict = yaml.load(f, Loader=yaml.SafeLoader) + + self.fileoutputpath = str(yaml_dict["model"]["control"]["output_file"]) - self.fileoutputpath = str(yaml_dict['model']['control']['output_file']) - if self.fileoutputpath.startswith("."): yamlfolder = self.yamlPath[0][:-15] self.fileoutputpath = yamlfolder + self.fileoutputpath[1:] - resolutionFilesOut = get_resolution_from_file(yaml_dict['model']['control']['output_file']) + resolutionFilesOut = get_resolution_from_file( + yaml_dict["model"]["control"]["output_file"] + ) self.resout = int(float(resolutionFilesOut) / 60) years = extract_suews_years(self.fileoutputpath) - self.YYYY = str(startday).split('-')[0] + self.YYYY = str(startday).split("-")[0] if self.YYYY not in years: - raise QgsProcessingException(f"Selected year '{self.YYYY}' is not present in the data.\n Availible years are {str(years)}") - + raise QgsProcessingException( + f"Selected year '{self.YYYY}' is not present in the data.\n Availible years are {str(years)}" + ) - self.id = int(variaIn) #self.dlg.comboBox_SpatialVariable.currentIndex() - 1 + self.id = int( + variaIn + ) # self.dlg.comboBox_SpatialVariable.currentIndex() - 1 poly_field = idField - if startday >= endday: - raise QgsProcessingException('Start date is greater or equal than end date') + raise QgsProcessingException( + "Start date is greater or equal than end date" + ) - # load, cut data and calculate statistics statvectemp = [0] statresult = [0] idvec = [0] - vlayer = inputPolygonlayer #QgsVectorLayer(poly.source(), "polygon", "ogr") + vlayer = inputPolygonlayer # QgsVectorLayer(poly.source(), "polygon", "ogr") prov = vlayer.dataProvider() fields = prov.fields() - path=vlayer.dataProvider().dataSourceUri() - if path.rfind('|') > 0: - polygonpath = path [:path.rfind('|')] # work around. Probably other solution exists + path = vlayer.dataProvider().dataSourceUri() + if path.rfind("|") > 0: + polygonpath = path[ + : path.rfind("|") + ] # work around. Probably other solution exists else: polygonpath = path - grid_list = [feature[poly_field[0]] for feature in vlayer.getFeatures()] - + grid_list = [ + feature[poly_field[0]] for feature in vlayer.getFeatures() + ] + for grid in grid_list: - datawhole = SUEWS_txt_to_df(self.fileoutputpath + '/' + str(grid) + '_' + - str(self.YYYY) + '_SUEWS_' + str(self.resout) + '.txt') - + datawhole = SUEWS_txt_to_df( + self.fileoutputpath + + "/" + + str(grid) + + "_" + + str(self.YYYY) + + "_SUEWS_" + + str(self.resout) + + ".txt" + ) + data1 = datawhole.loc[startday:endday] - - if dayTypeStr == '0': # Diurnal - pass - elif dayTypeStr == '1': # Daytime: - data1 = data1[data1.loc[:, 'Zenith'] < 90] - elif dayTypeStr == '2': # Nighttime: - data1 = data1[data1.loc[:, 'Zenith'] > 90.] - var = self.varType[self.id][0].split(' ')[0] # get position and slice variable + if dayTypeStr == "0": # Diurnal + pass + elif dayTypeStr == "1": # Daytime: + data1 = data1[data1.loc[:, "Zenith"] < 90] + + elif dayTypeStr == "2": # Nighttime: + data1 = data1[data1.loc[:, "Zenith"] > 90.0] + var = self.varType[self.id][0].split(" ")[ + 0 + ] # get position and slice variable vardata = data1.loc[:, var] - if statTypeStr == '0': + if statTypeStr == "0": statresult = np.nanmean(vardata) - suffix = '_mean' - if statTypeStr == '1': + suffix = "_mean" + if statTypeStr == "1": statresult = np.nanmin(vardata) - suffix = '_min' - if statTypeStr == '2': + suffix = "_min" + if statTypeStr == "2": statresult = np.nanmax(vardata) - suffix = '_max' - if statTypeStr == '3': + suffix = "_max" + if statTypeStr == "3": statresult = np.nanmedian(vardata) - suffix = '_median' - if statTypeStr == '4': - statresult = np.nanpercentile(vardata, 75) - np.percentile(vardata, 25) - suffix = '_IQR' + suffix = "_median" + if statTypeStr == "4": + statresult = np.nanpercentile(vardata, 75) - np.percentile( + vardata, 25 + ) + suffix = "_IQR" statvectemp = np.vstack((statvectemp, statresult)) idvec = np.vstack((idvec, int(grid))) statvector = statvectemp[1:, :] statmat = np.hstack((idvec[1:, :], statvector)) - + header = var + suffix if addAttributes: self.addattributes(vlayer, statmat, header) @@ -243,7 +329,11 @@ def processAlgorithm(self, parameters, context, feedback): if irreg: resx = pixelsize else: - for f in vlayer.getFeatures(): # Taking first polygon. Could probably be done nicer + for ( + f + ) in ( + vlayer.getFeatures() + ): # Taking first polygon. Could probably be done nicer # geom = f.geometry().asPolygon() geom = f.geometry().asMultiPolygon() break @@ -251,20 +341,30 @@ def processAlgorithm(self, parameters, context, feedback): resy = np.abs(geom[0][0][0][1] - geom[0][0][2][1]) # y if not resx == resy: - raise QgsProcessingException("Polygons not squared in current CRS") + raise QgsProcessingException( + "Polygons not squared in current CRS" + ) return - - if os.path.isfile(self.plugin_dir + '/tempgrid.tif'): # response to issue 103 + if os.path.isfile( + self.plugin_dir + "/tempgrid.tif" + ): # response to issue 103 try: - shutil.rmtree(self.plugin_dir + '/tempgrid.tif') + shutil.rmtree(self.plugin_dir + "/tempgrid.tif") except OSError: - os.remove(self.plugin_dir + '/tempgrid.tif') + os.remove(self.plugin_dir + "/tempgrid.tif") crs = vlayer.crs().toWkt() - self.rasterize(polygonpath, str(self.plugin_dir + '/tempgrid.tif'), str(poly_field[0]), resx, crs, extent) - - dataset = gdal.Open(self.plugin_dir + '/tempgrid.tif') + self.rasterize( + polygonpath, + str(self.plugin_dir + "/tempgrid.tif"), + str(poly_field[0]), + resx, + crs, + extent, + ) + + dataset = gdal.Open(self.plugin_dir + "/tempgrid.tif") idgrid_array = dataset.ReadAsArray().astype(float) gridout = np.zeros((idgrid_array.shape[0], idgrid_array.shape[1])) @@ -278,7 +378,17 @@ def processAlgorithm(self, parameters, context, feedback): return {self.SUEWS_GRID_OUT: outputStat} - def rasterize(self, src, dst, attribute, resolution, crs, extent, all_touch=False, na=-9999): + def rasterize( + self, + src, + dst, + attribute, + resolution, + crs, + extent, + all_touch=False, + na=-9999, + ): # Open shapefile, retrieve the layer # print(src) @@ -292,10 +402,12 @@ def rasterize(self, src, dst, attribute, resolution, crs, extent, all_touch=Fals ymin = extent.yMinimum() # Create the target raster layer - cols = int((xmax - xmin)/resolution) + cols = int((xmax - xmin) / resolution) # rows = int((ymax - ymin)/resolution) + 1 - rows = int((ymax - ymin)/resolution) # issue 164 - trgt = gdal.GetDriverByName("GTiff").Create(dst, cols, rows, 1, gdal.GDT_Float32) + rows = int((ymax - ymin) / resolution) # issue 164 + trgt = gdal.GetDriverByName("GTiff").Create( + dst, cols, rows, 1, gdal.GDT_Float32 + ) trgt.SetGeoTransform((xmin, resolution, 0, ymax, 0, -resolution)) # Add crs @@ -318,15 +430,16 @@ def rasterize(self, src, dst, attribute, resolution, crs, extent, all_touch=Fals # Close target an source rasters del trgt - del src_data + del src_data - def addattributes(self, vlayer, matdata, header): current_index_length = len(vlayer.dataProvider().attributeIndexes()) caps = vlayer.dataProvider().capabilities() if caps & QgsVectorDataProvider.Capability.AddAttributes: - vlayer.dataProvider().addAttributes([QgsField(header, QVariant.Double)]) + vlayer.dataProvider().addAttributes( + [QgsField(header, QVariant.Double)] + ) attr_dict = {} for y in range(0, matdata.shape[0]): attr_dict.clear() @@ -336,10 +449,12 @@ def addattributes(self, vlayer, matdata, header): vlayer.updateFields() else: - raise QgsProcessingException("Error", "Vector Layer does not support adding attributes") - + raise QgsProcessingException( + "Error", "Vector Layer does not support adding attributes" + ) + def name(self): - return 'Urban Energy Balance: SUEWS Analyzer' + return "Urban Energy Balance: SUEWS Analyzer" def displayName(self): return self.tr(self.name()) @@ -348,29 +463,34 @@ def group(self): return self.tr(self.groupId()) def groupId(self): - return 'Post-Processor' + return "Post-Processor" def shortHelpString(self): - return self.tr('The SUEWS Analyzer plugin can be used to make basic grid analysis of model results generated by the SUEWS model.
' - '\n' - '--------------\n' - 'Full manual available via the Help-button.') + return self.tr( + "The SUEWS Analyzer plugin can be used to make basic grid analysis of model results generated by the SUEWS model.
" + "\n" + "--------------\n" + "Full manual available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/post_processor/Urban%20Energy%20Balance%20SUEWS%20Analyser.html" return url def tr(self, string): - return QCoreApplication.translate('Post-Processing', string) + return QCoreApplication.translate("Post-Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/SuewsLogo.png") return icon def createInstance(self): return ProcessingSuewsAnalyzerAlgorithm() + class DateWidgetStart(WidgetWrapper): def createWidget(self): self._combo = QDateEdit() @@ -385,6 +505,7 @@ def value(self): date_chosen = self._combo.dateTime() return date_chosen.toString(Qt.DateFormat.ISODate) + class DateWidgetEnd(WidgetWrapper): def createWidget(self): self._combo = QDateEdit() diff --git a/postprocessor/targetanalyzer_algorithm.py b/postprocessor/targetanalyzer_algorithm.py index 8c77793..53164c1 100644 --- a/postprocessor/targetanalyzer_algorithm.py +++ b/postprocessor/targetanalyzer_algorithm.py @@ -1,28 +1,30 @@ # -*- coding: utf-8 -*- -__author__ = 'Fredrik Lindberg' -__date__ = '2021-02-05' -__copyright__ = '(C) 2021 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2021-02-05" +__copyright__ = "(C) 2021 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QVariant, QDate, Qt -from qgis.core import (QgsProcessing, - QgsProcessingAlgorithm, - QgsProcessingParameterBoolean, - QgsProcessingParameterNumber, - QgsProcessingParameterRasterDestination, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterField, - QgsProcessingParameterEnum, - QgsProcessingException, - QgsProcessingParameterDateTime, - QgsVectorDataProvider, - QgsProcessingParameterFile, - QgsField) +from qgis.core import ( + QgsProcessing, + QgsProcessingAlgorithm, + QgsProcessingParameterBoolean, + QgsProcessingParameterNumber, + QgsProcessingParameterRasterDestination, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterField, + QgsProcessingParameterEnum, + QgsProcessingException, + QgsProcessingParameterDateTime, + QgsVectorDataProvider, + QgsProcessingParameterFile, + QgsField, +) from qgis.PyQt.QtGui import QIcon from processing.gui.wrappers import WidgetWrapper @@ -37,7 +39,8 @@ import shutil import datetime from ..util.misc import saveraster -#from ..util.umep_uwg_export_component import read_uwg_file + +# from ..util.umep_uwg_export_component import read_uwg_file try: from target_py import Target @@ -50,109 +53,180 @@ class ProcessingTARGETAnalyzerAlgorithm(QgsProcessingAlgorithm): """ This class is a processing version of UWGAnalyzer but only for generating aggregated grids """ - INPUT_FOLDER = 'INPUT_FOLDER' - OUTPUT_FOLDER = 'OUTPUT_FOLDER' - INPUT_POLYGONLAYER = 'INPUT_POLYGONLAYER' - ID_FIELD = 'ID_FIELD' - SINGLE_DAY_BOOL = 'SINGLE_DAY_BOOL' - SINGLE_DAY = 'SINGLE_DAY' - IRREGULAR = 'IRREGULAR' - PIXELSIZE = 'PIXELSIZE' - STAT_TYPE = 'STAT_TYPE' - ADD_ATTRIBUTES ='ADD_ATTRIBUTES' - TIME_OF_DAY ='TIME_OF_DAY' - - # Output - UWG_GRID_OUT = 'UWG_GRID_OUT' + INPUT_FOLDER = "INPUT_FOLDER" + OUTPUT_FOLDER = "OUTPUT_FOLDER" + INPUT_POLYGONLAYER = "INPUT_POLYGONLAYER" + ID_FIELD = "ID_FIELD" + SINGLE_DAY_BOOL = "SINGLE_DAY_BOOL" + SINGLE_DAY = "SINGLE_DAY" + IRREGULAR = "IRREGULAR" + PIXELSIZE = "PIXELSIZE" + STAT_TYPE = "STAT_TYPE" + ADD_ATTRIBUTES = "ADD_ATTRIBUTES" + TIME_OF_DAY = "TIME_OF_DAY" + + # Output + UWG_GRID_OUT = "UWG_GRID_OUT" def initAlgorithm(self, config): self.plugin_dir = os.path.dirname(__file__) - self.addParameter(QgsProcessingParameterFile(self.INPUT_FOLDER, - self.tr('Path to TARGET Run name folder'), - QgsProcessingParameterFile.Behavior.Folder)) - self.addParameter(QgsProcessingParameterBoolean(self.SINGLE_DAY_BOOL, - self.tr("Examine single night"), defaultValue=False, optional=True)) - self.addParameter(QgsProcessingParameterDateTime(self.SINGLE_DAY, - self.tr('Month and day when single night begins (year is irrelevant)'), - QgsProcessingParameterDateTime.Type.Date)) - self.statType = ((self.tr('Mean'), '0'), - (self.tr('Maximun'), '1'), - (self.tr('Median'), '2'), - (self.tr('75% percentile'), '3'), - (self.tr('95% percentile'), '4')) - self.addParameter(QgsProcessingParameterEnum(self.STAT_TYPE, - self.tr('Statistic measure'), - options=[i[0] for i in self.statType], - defaultValue=0)) - self.dayType = ((self.tr('Diurnal'), '0'), - (self.tr('Daytime'), '1'), - (self.tr('Nighttime'), '2')) - self.addParameter(QgsProcessingParameterEnum(self.TIME_OF_DAY, - self.tr('Time of day'), - options=[i[0] for i in self.dayType], - defaultValue=0)) - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_POLYGONLAYER, - self.tr('Vector polygon grid'), - [QgsProcessing.SourceType.TypeVectorPolygon])) - self.addParameter(QgsProcessingParameterField(self.ID_FIELD, - self.tr('ID field'), - '', - self.INPUT_POLYGONLAYER, - QgsProcessingParameterField.DataType.Numeric)) - self.addParameter(QgsProcessingParameterBoolean(self.IRREGULAR, - self.tr("Polygon grid irregular (not squared)"), - defaultValue=False)) - self.addParameter(QgsProcessingParameterNumber(self.PIXELSIZE, - self.tr('Pixelsize if irregular grid is used (meter)'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(10), False, minValue=1)) + self.addParameter( + QgsProcessingParameterFile( + self.INPUT_FOLDER, + self.tr("Path to TARGET Run name folder"), + QgsProcessingParameterFile.Behavior.Folder, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.SINGLE_DAY_BOOL, + self.tr("Examine single night"), + defaultValue=False, + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterDateTime( + self.SINGLE_DAY, + self.tr( + "Month and day when single night begins (year is irrelevant)" + ), + QgsProcessingParameterDateTime.Type.Date, + ) + ) + self.statType = ( + (self.tr("Mean"), "0"), + (self.tr("Maximun"), "1"), + (self.tr("Median"), "2"), + (self.tr("75% percentile"), "3"), + (self.tr("95% percentile"), "4"), + ) + self.addParameter( + QgsProcessingParameterEnum( + self.STAT_TYPE, + self.tr("Statistic measure"), + options=[i[0] for i in self.statType], + defaultValue=0, + ) + ) + self.dayType = ( + (self.tr("Diurnal"), "0"), + (self.tr("Daytime"), "1"), + (self.tr("Nighttime"), "2"), + ) + self.addParameter( + QgsProcessingParameterEnum( + self.TIME_OF_DAY, + self.tr("Time of day"), + options=[i[0] for i in self.dayType], + defaultValue=0, + ) + ) + self.addParameter( + QgsProcessingParameterFeatureSource( + self.INPUT_POLYGONLAYER, + self.tr("Vector polygon grid"), + [QgsProcessing.SourceType.TypeVectorPolygon], + ) + ) + self.addParameter( + QgsProcessingParameterField( + self.ID_FIELD, + self.tr("ID field"), + "", + self.INPUT_POLYGONLAYER, + QgsProcessingParameterField.DataType.Numeric, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.IRREGULAR, + self.tr("Polygon grid irregular (not squared)"), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.PIXELSIZE, + self.tr("Pixelsize if irregular grid is used (meter)"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(10), + False, + minValue=1, + ) + ) # Output - self.addParameter(QgsProcessingParameterBoolean(self.ADD_ATTRIBUTES, - self.tr("Add results to vector polygon grid attribute table"), - defaultValue=False)) - self.addParameter(QgsProcessingParameterRasterDestination(self.UWG_GRID_OUT, - self.tr("Output raster from statistical analysis"), - None, - optional=True, - createByDefault=False)) + self.addParameter( + QgsProcessingParameterBoolean( + self.ADD_ATTRIBUTES, + self.tr("Add results to vector polygon grid attribute table"), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterRasterDestination( + self.UWG_GRID_OUT, + self.tr("Output raster from statistical analysis"), + None, + optional=True, + createByDefault=False, + ) + ) def processAlgorithm(self, parameters, context, feedback): - + # InputParameters - targetIn = self.parameterAsString(parameters, self.INPUT_FOLDER, context) - #uwgOut = self.parameterAsString(parameters, self.OUTPUT_FOLDER, context) + targetIn = self.parameterAsString( + parameters, self.INPUT_FOLDER, context + ) + # uwgOut = self.parameterAsString(parameters, self.OUTPUT_FOLDER, context) # variaIn = self.parameterAsString(parameters, self.VARIA_IN, context) # startday = self.parameterAsString(parameters, self.DATEINISTART, context) - singleNight = self.parameterAsBool(parameters, self.SINGLE_DAY_BOOL, context) + singleNight = self.parameterAsBool( + parameters, self.SINGLE_DAY_BOOL, context + ) # endday = self.parameterAsString(parameters, self.DATEINIEND, context) - inputPolygonlayer = self.parameterAsVectorLayer(parameters, self.INPUT_POLYGONLAYER, context) + inputPolygonlayer = self.parameterAsVectorLayer( + parameters, self.INPUT_POLYGONLAYER, context + ) idField = self.parameterAsFields(parameters, self.ID_FIELD, context) irreg = self.parameterAsBool(parameters, self.IRREGULAR, context) - statTypeStr = self.parameterAsString(parameters, self.STAT_TYPE, context) + statTypeStr = self.parameterAsString( + parameters, self.STAT_TYPE, context + ) # dayTypeStr = self.parameterAsString(parameters, self.TIME_OF_DAY, context) pixelsize = self.parameterAsDouble(parameters, self.PIXELSIZE, context) - addAttributes = self.parameterAsBool(parameters, self.ADD_ATTRIBUTES, context) - outputStat = self.parameterAsOutputLayer(parameters, self.UWG_GRID_OUT, context) - dayTypeStr = self.parameterAsString(parameters, self.TIME_OF_DAY, context) + addAttributes = self.parameterAsBool( + parameters, self.ADD_ATTRIBUTES, context + ) + outputStat = self.parameterAsOutputLayer( + parameters, self.UWG_GRID_OUT, context + ) + dayTypeStr = self.parameterAsString( + parameters, self.TIME_OF_DAY, context + ) feedback.setProgressText("Initializing...") # statType = int(statTypeStr) - feedback.setProgressText("Model input directory: " + targetIn + "/input") - feedback.setProgressText("Model output directory: " + targetIn + "/output") + feedback.setProgressText( + "Model input directory: " + targetIn + "/input" + ) + feedback.setProgressText( + "Model output directory: " + targetIn + "/output" + ) - cfM = read_config(targetIn + '/config.ini') + cfM = read_config(targetIn + "/config.ini") - runName = cfM['run_name'] + runName = cfM["run_name"] feedback.setProgressText("Run name: " + runName) - - #fileList = os.listdir(uwgIn) + # fileList = os.listdir(uwgIn) # a = fileList[0].find("_") # prefix = fileList[0][0:a] @@ -164,51 +238,73 @@ def processAlgorithm(self, parameters, context, feedback): # nDays = uwgDict['nDay'] # Load rural data - #inputDir + "/output/" + runName + 'metdata_UMEP.txt' - sitein = targetIn + "/output/" + runName + '_metdata_UMEP.txt' + # inputDir + "/output/" + runName + 'metdata_UMEP.txt' + sitein = targetIn + "/output/" + runName + "_metdata_UMEP.txt" dataref = np.genfromtxt(sitein, skip_header=1) - yyyy = dataref[0,0] + yyyy = dataref[0, 0] # start = datetime.date(int(yyyy), int(mm), int(dd)) # end = start + datetime.timedelta(days=int(nDays)) - start = datetime.datetime.strptime(cfM['date1'], "%Y,%m,%d,%H").strftime("%Y-%m-%d") #string - end = datetime.datetime.strptime(cfM['date2'], "%Y,%m,%d,%H").strftime("%Y-%m-%d") #string - startDate = datetime.datetime.strptime(cfM['date1'], "%Y,%m,%d,%H") - endDate = datetime.datetime.strptime(cfM['date2'], "%Y,%m,%d,%H") - - feedback.setProgressText('Days indentified as modelled by TARGET: ' + start + ' to ' + end) + start = datetime.datetime.strptime( + cfM["date1"], "%Y,%m,%d,%H" + ).strftime( + "%Y-%m-%d" + ) # string + end = datetime.datetime.strptime(cfM["date2"], "%Y,%m,%d,%H").strftime( + "%Y-%m-%d" + ) # string + startDate = datetime.datetime.strptime(cfM["date1"], "%Y,%m,%d,%H") + endDate = datetime.datetime.strptime(cfM["date2"], "%Y,%m,%d,%H") + + feedback.setProgressText( + "Days indentified as modelled by TARGET: " + start + " to " + end + ) if singleNight: - singledate = self.parameterAsString(parameters, self.SINGLE_DAY, context) - startDate = datetime.datetime.strptime(singledate, '%Y-%m-%d') + singledate = self.parameterAsString( + parameters, self.SINGLE_DAY, context + ) + startDate = datetime.datetime.strptime(singledate, "%Y-%m-%d") mm = startDate.month dd = startDate.day startDate = datetime.date(int(yyyy), int(mm), int(dd)) nDays = 1 endDate = startDate + datetime.timedelta(days=int(nDays)) if startDate >= end or startDate < start: - raise QgsProcessingException('Single date has to be within the modelled days OR the same as the last modelled day.') + raise QgsProcessingException( + "Single date has to be within the modelled days OR the same as the last modelled day." + ) else: - feedback.setProgressText('Single day and following night to examine: ' + startDate.strftime('%d %b')) + feedback.setProgressText( + "Single day and following night to examine: " + + startDate.strftime("%d %b") + ) else: - #uwgDict = read_uwg_file(uwgIn, fileList[0][:-4]) - #mm = uwgDict['Month'] - #dd = uwgDict['Day'] - #nDays = uwgDict['nDay'] - #startDate = datetime.date(int(cfM['date1'][0]), int(cfM['date1'][1]), int(cfM['date1'][2])) - #endDate = datetime.date(int(cfM['date2'][0]), int(cfM['date2'][1]), int(cfM['date2'][2])) - #endDate = startDate + datetime.timedelta(days=int(nDays)) - feedback.setProgressText('Dates to be analyzed: ' + startDate.strftime('%d %b') + ' to ' + endDate.strftime('%d %b')) + # uwgDict = read_uwg_file(uwgIn, fileList[0][:-4]) + # mm = uwgDict['Month'] + # dd = uwgDict['Day'] + # nDays = uwgDict['nDay'] + # startDate = datetime.date(int(cfM['date1'][0]), int(cfM['date1'][1]), int(cfM['date1'][2])) + # endDate = datetime.date(int(cfM['date2'][0]), int(cfM['date2'][1]), int(cfM['date2'][2])) + # endDate = startDate + datetime.timedelta(days=int(nDays)) + feedback.setProgressText( + "Dates to be analyzed: " + + startDate.strftime("%d %b") + + " to " + + endDate.strftime("%d %b") + ) # poly = inputPolygonlayer poly_field = idField vlayer = inputPolygonlayer # prov = vlayer.dataProvider() - path=vlayer.dataProvider().dataSourceUri() + path = vlayer.dataProvider().dataSourceUri() # polygonpath = path [:path.rfind('|')] # work around. Probably other solution exists - if path.rfind('|') > 0: - polygonpath = path [:path.rfind('|')] # work around. Probably other solution exists + if path.rfind("|") > 0: + polygonpath = path[ + : path.rfind("|") + ] # work around. Probably other solution exists else: polygonpath = path @@ -220,10 +316,9 @@ def processAlgorithm(self, parameters, context, feedback): statresult = [0] idvec = [0] - startD = int(startDate.strftime('%j')) - endD = int(endDate.strftime('%j')) + startD = int(startDate.strftime("%j")) + endD = int(endDate.strftime("%j")) - # for i in range(0, self.idgrid.shape[0]): # loop over vector grid instead index = 1 nGrids = vlayer.featureCount() @@ -238,7 +333,15 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText("Processing grid: " + str(gid)) - datawhole = np.genfromtxt(targetIn + '/output/' + runName + '_' + gid + '_UMEP_TARGET.txt', skip_header=1) + datawhole = np.genfromtxt( + targetIn + + "/output/" + + runName + + "_" + + gid + + "_UMEP_TARGET.txt", + skip_header=1, + ) # cut TARGET data start = np.min(np.where(datawhole[:, 1] == startD)) @@ -246,46 +349,52 @@ def processAlgorithm(self, parameters, context, feedback): ending = np.max(np.where(datawhole[:, 1] == endD - 1)) else: ending = np.min(np.where(datawhole[:, 1] == endD)) - data1 = datawhole[start:int(ending + 12), :] # + 12 to include whole final night + data1 = datawhole[ + start : int(ending + 12), : + ] # + 12 to include whole final night # cut ref data if endD > np.max(dataref[:, 1]): ending = np.max(np.where(dataref[:, 1] == endD - 1)) else: ending = np.min(np.where(dataref[:, 1] == endD)) - data2 = dataref[start:int(ending + 12), :] # + 12 to include whole final night + data2 = dataref[ + start : int(ending + 12), : + ] # + 12 to include whole final night # Select depending of time of day for ref data - if dayTypeStr == '1': - data1 = data1[np.where(data1[:, 14] > 1.), :] #14 is position for global radiation + if dayTypeStr == "1": + data1 = data1[ + np.where(data1[:, 14] > 1.0), : + ] # 14 is position for global radiation data1 = data1[0][:] - data2 = data2[np.where(data2[:, 14] > 1.), :] + data2 = data2[np.where(data2[:, 14] > 1.0), :] data2 = data2[0][:] - if dayTypeStr == '2': - data1 = data1[np.where(data1[:, 14] < 1.), :] + if dayTypeStr == "2": + data1 = data1[np.where(data1[:, 14] < 1.0), :] data1 = data1[0][:] - data2 = data2[np.where(data2[:, 14] < 1.), :] + data2 = data2[np.where(data2[:, 14] < 1.0), :] data2 = data2[0][:] - vardatauwg = data1[:, 11] # 11 is temperature column - vardataref = data2[:, 11] + vardatauwg = data1[:, 11] # 11 is temperature column + vardataref = data2[:, 11] vardata = vardatauwg - vardataref - if statTypeStr == '0': + if statTypeStr == "0": statresult = np.nanmean(vardata) - header = 'mean' - if statTypeStr == '1': + header = "mean" + if statTypeStr == "1": statresult = np.nanmax(vardata) - header = 'max' - if statTypeStr == '2': + header = "max" + if statTypeStr == "2": statresult = np.nanpercentile(vardata, 50) - header = 'median' - if statTypeStr == '3': + header = "median" + if statTypeStr == "3": statresult = np.nanpercentile(vardata, 75) - header = '75precentile' - if statTypeStr == '4': + header = "75precentile" + if statTypeStr == "4": statresult = np.nanpercentile(vardata, 95) - header = '95precentile' + header = "95precentile" statvectemp = np.vstack((statvectemp, statresult)) idvec = np.vstack((idvec, int(gid))) @@ -293,7 +402,7 @@ def processAlgorithm(self, parameters, context, feedback): statvector = statvectemp[1:, :] # fix_print_with_import statmat = np.hstack((idvec[1:, :], statvector)) - statmat[statmat < -500] = -9999 #Response to #107 + statmat[statmat < -500] = -9999 # Response to #107 if addAttributes: self.addattributes(vlayer, statmat, header) @@ -301,26 +410,41 @@ def processAlgorithm(self, parameters, context, feedback): if irreg: resx = pixelsize else: - for f in vlayer.getFeatures(): # Taking first polygon. Could probably be done nicer + for ( + f + ) in ( + vlayer.getFeatures() + ): # Taking first polygon. Could probably be done nicer geom = f.geometry().asMultiPolygon() break resx = np.abs(geom[0][0][0][0] - geom[0][0][2][0]) # x resy = np.abs(geom[0][0][0][1] - geom[0][0][2][1]) # y if not resx == resy: - raise QgsProcessingException("Polygons not squared in current CRS") + raise QgsProcessingException( + "Polygons not squared in current CRS" + ) - if os.path.isfile(self.plugin_dir + '/tempgrid.tif'): # response to issue 103 + if os.path.isfile( + self.plugin_dir + "/tempgrid.tif" + ): # response to issue 103 try: - shutil.rmtree(self.plugin_dir + '/tempgrid.tif') + shutil.rmtree(self.plugin_dir + "/tempgrid.tif") except OSError: - os.remove(self.plugin_dir + '/tempgrid.tif') - + os.remove(self.plugin_dir + "/tempgrid.tif") + extent = vlayer.extent() crs = vlayer.crs().toWkt() - self.rasterize(polygonpath, str(self.plugin_dir + '/tempgrid.tif'), str(poly_field[0]), resx, crs, extent) - - dataset = gdal.Open(self.plugin_dir + '/tempgrid.tif') + self.rasterize( + polygonpath, + str(self.plugin_dir + "/tempgrid.tif"), + str(poly_field[0]), + resx, + crs, + extent, + ) + + dataset = gdal.Open(self.plugin_dir + "/tempgrid.tif") idgrid_array = dataset.ReadAsArray().astype(float) gridout = np.zeros((idgrid_array.shape[0], idgrid_array.shape[1])) @@ -335,7 +459,17 @@ def processAlgorithm(self, parameters, context, feedback): return {self.UWG_GRID_OUT: outputStat} - def rasterize(self, src, dst, attribute, resolution, crs, extent, all_touch=False, na=-9999): + def rasterize( + self, + src, + dst, + attribute, + resolution, + crs, + extent, + all_touch=False, + na=-9999, + ): # Open shapefile, retrieve the layer src_data = ogr.Open(src) @@ -348,9 +482,11 @@ def rasterize(self, src, dst, attribute, resolution, crs, extent, all_touch=Fals ymin = extent.yMinimum() # Create the target raster layer - cols = int((xmax - xmin)/resolution) - rows = int((ymax - ymin)/resolution) # issue 164 - trgt = gdal.GetDriverByName("GTiff").Create(dst, cols, rows, 1, GDT_Float32) + cols = int((xmax - xmin) / resolution) + rows = int((ymax - ymin) / resolution) # issue 164 + trgt = gdal.GetDriverByName("GTiff").Create( + dst, cols, rows, 1, GDT_Float32 + ) trgt.SetGeoTransform((xmin, resolution, 0, ymax, 0, -resolution)) # Add crs @@ -373,15 +509,16 @@ def rasterize(self, src, dst, attribute, resolution, crs, extent, all_touch=Fals # Close target an source rasters del trgt - del src_data + del src_data - def addattributes(self, vlayer, matdata, header): current_index_length = len(vlayer.dataProvider().attributeIndexes()) caps = vlayer.dataProvider().capabilities() if caps & QgsVectorDataProvider.Capability.AddAttributes: - vlayer.dataProvider().addAttributes([QgsField(header, QVariant.Double)]) + vlayer.dataProvider().addAttributes( + [QgsField(header, QVariant.Double)] + ) attr_dict = {} for y in range(0, matdata.shape[0]): attr_dict.clear() @@ -391,10 +528,12 @@ def addattributes(self, vlayer, matdata, header): vlayer.updateFields() else: - raise QgsProcessingException("Vector Layer does not support adding attributes") - + raise QgsProcessingException( + "Vector Layer does not support adding attributes" + ) + def name(self): - return 'Urban Heat Island: TARGET Analyzer' + return "Urban Heat Island: TARGET Analyzer" def displayName(self): return self.tr(self.name()) @@ -403,29 +542,34 @@ def group(self): return self.tr(self.groupId()) def groupId(self): - return 'Post-Processor' + return "Post-Processor" def shortHelpString(self): - return self.tr('The TARGET Analyzer plugin can be used to make basic grid analysis of model results generated by the TARGET model.
' - '\n' - '--------------\n' - 'Full manual available via the Help-button.') + return self.tr( + "The TARGET Analyzer plugin can be used to make basic grid analysis of model results generated by the TARGET model.
" + "\n" + "--------------\n" + "Full manual available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/post_processor/Urban%20Heat%20Island%20TARGET%20Analyser.html" return url def tr(self, string): - return QCoreApplication.translate('Post-Processing', string) + return QCoreApplication.translate("Post-Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/icon_uwg.png") return icon def createInstance(self): return ProcessingTARGETAnalyzerAlgorithm() + class DateWidgetStart(WidgetWrapper): def createWidget(self): self._combo = QDateEdit() @@ -440,6 +584,7 @@ def value(self): date_chosen = self._combo.dateTime() return date_chosen.toString(Qt.DateFormat.ISODate) + class DateWidgetEnd(WidgetWrapper): def createWidget(self): self._combo = QDateEdit() diff --git a/postprocessor/treeplanter_algorithm.py b/postprocessor/treeplanter_algorithm.py index cb4cbb7..616e00c 100644 --- a/postprocessor/treeplanter_algorithm.py +++ b/postprocessor/treeplanter_algorithm.py @@ -22,26 +22,28 @@ ***************************************************************************/ """ -__author__ = 'Fredrik Lindberg' -__date__ = '2020-04-02' -__copyright__ = '(C) 2020 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2020-04-02" +__copyright__ = "(C) 2020 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QVariant -from qgis.core import (QgsProcessing, - QgsProcessingAlgorithm, - # QgsProcessingParameterString, - QgsProcessingParameterBoolean, - QgsProcessingParameterNumber, - QgsProcessingParameterFolderDestination, - QgsProcessingParameterEnum, - QgsProcessingParameterFeatureSource, - # QgsProcessingParameterField, - QgsProcessingParameterFile, - QgsProcessingParameterDefinition) +from qgis.core import ( + QgsProcessing, + QgsProcessingAlgorithm, + # QgsProcessingParameterString, + QgsProcessingParameterBoolean, + QgsProcessingParameterNumber, + QgsProcessingParameterFolderDestination, + QgsProcessingParameterEnum, + QgsProcessingParameterFeatureSource, + # QgsProcessingParameterField, + QgsProcessingParameterFile, + QgsProcessingParameterDefinition, +) from qgis.PyQt.QtGui import QIcon from osgeo import gdal, osr, ogr from osgeo.gdalconst import * @@ -51,121 +53,200 @@ from pathlib import Path import sys from ..util import misc + # from ..util import RoughnessCalcFunctionV2 as rg # from ..util import imageMorphometricParms_v1 as morph # Modules necessary for Tree Planter -#import numpy as np -#from osgeo import gdal, osr, ogr +# import numpy as np +# from osgeo import gdal, osr, ogr # import matplotlib.pylab as plt -#import os +# import os # import time import math import glob import datetime + # import scipy as sp from scipy.ndimage import label # Import UMEP tools -from ..util.SEBESOLWEIGCommonFiles.Solweig_v2015_metdata_noload import Solweig_2015a_metdata_noload +from ..util.SEBESOLWEIGCommonFiles.Solweig_v2015_metdata_noload import ( + Solweig_2015a_metdata_noload, +) + # from ..util.SEBESOLWEIGCommonFiles import Solweig_v2015_metdata_noload as metload -from ..util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import clearnessindex_2013b +from ..util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import ( + clearnessindex_2013b, +) from ..functions.TreeGenerator import makevegdems as makevegdems # from ..functions.TreePlanter.SOLWEIG.shadowingfunction_wallheight_23 import shadowingfunction_wallheight_23 -from ..util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_23 import shadowingfunction_wallheight_23 +from ..util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_23 import ( + shadowingfunction_wallheight_23, +) + # from ..functions.TreePlanter.SOLWEIG1D import Solweig1D_2019a_calc as so from ..functions.wallalgorithms import findwalls + # from ..functions.TreePlanter.SOLWEIG.misc import saveraster from ..util.misc import saveraster # Import functions and classes for Tree planter from ..functions.TreePlanter.TreePlanter import TreePlanterPrepare from ..functions.TreePlanter.TreePlanter import TreePlanterHillClimber -from ..functions.TreePlanter.TreePlanter.TreePlanterClasses import Inputdata, Treedata, Regional_groups, ClippedInputdata, Treerasters +from ..functions.TreePlanter.TreePlanter.TreePlanterClasses import ( + Inputdata, + Treedata, + Regional_groups, + ClippedInputdata, + Treerasters, +) from ..functions.TreePlanter.TreePlanter import GreedyAlgorithm + # from ..functions.TreePlanter.SOLWEIG1D.SOLWEIG_1D import tmrt_1d_fun from ..functions.TreePlanter.SOLWEIG1D.SOLWEIG1D_2023a import tmrt_1d_fun + # from ..functions.TreePlanter.treeplanterclasses import Treedata # from ..functions.TreePlanter.treeplanterclasses import Regional_groups # from ..functions.TreePlanter.treeplanterclasses import ClippedInputdata # from ..functions.TreePlanter.treeplanterclasses import Treerasters + class ProcessingTreePlanterAlgorithm(QgsProcessingAlgorithm): """ This algorithm is a processing version of Tree planter """ - TTYPE = 'TTYPE' - HEIGHT = 'HEIGHT' - DIA = 'DIA' - TRUNK = 'TRUNK' - TRANS_VEG = 'TRANS_VEG' - INPUT_POLYGONLAYER = 'INPUT_POLYGONLAYER' - NTREE = 'NTREE' - SOLWEIG_DIR = 'SOLWEIG_DIR' - #INPUT_MET = 'INPUT_MET' - START_HOUR = 'START_HOUR' - END_HOUR = 'END_HOUR' + TTYPE = "TTYPE" + HEIGHT = "HEIGHT" + DIA = "DIA" + TRUNK = "TRUNK" + TRANS_VEG = "TRANS_VEG" + INPUT_POLYGONLAYER = "INPUT_POLYGONLAYER" + NTREE = "NTREE" + SOLWEIG_DIR = "SOLWEIG_DIR" + # INPUT_MET = 'INPUT_MET' + START_HOUR = "START_HOUR" + END_HOUR = "END_HOUR" # Advanced - ITERATIONS = 'ITERATIONS' - INCLUDE_OUTSIDE = 'INCLUDE_OUTSIDE' - RANDOM_STARTING = 'RANDOM_STARTING' - GREEDY_ALGORITHM = 'GREEDY_ALGORITHM' + ITERATIONS = "ITERATIONS" + INCLUDE_OUTSIDE = "INCLUDE_OUTSIDE" + RANDOM_STARTING = "RANDOM_STARTING" + GREEDY_ALGORITHM = "GREEDY_ALGORITHM" # Output - OUTPUT_CDSM = 'OUTPUT_CDSM' - OUTPUT_POINTFILE = 'OUTPUT_POINTFILE' - OUTPUT_TMRT = 'OUTPUT_TMRT' - OUTPUT_OCCURRENCE = 'OUTPUT_OCCURRENCE' - OUTPUT_DIR = 'OUTPUT_DIR' + OUTPUT_CDSM = "OUTPUT_CDSM" + OUTPUT_POINTFILE = "OUTPUT_POINTFILE" + OUTPUT_TMRT = "OUTPUT_TMRT" + OUTPUT_OCCURRENCE = "OUTPUT_OCCURRENCE" + OUTPUT_DIR = "OUTPUT_DIR" def initAlgorithm(self, config): - self.addParameter(QgsProcessingParameterFile(self.SOLWEIG_DIR, - 'Path to SOLWEIG output directory', QgsProcessingParameterFile.Behavior.Folder)) - #self.addParameter(QgsProcessingParameterFile(self.INPUT_MET, + self.addParameter( + QgsProcessingParameterFile( + self.SOLWEIG_DIR, + "Path to SOLWEIG output directory", + QgsProcessingParameterFile.Behavior.Folder, + ) + ) + # self.addParameter(QgsProcessingParameterFile(self.INPUT_MET, # self.tr('Input meteorological file'), extension='txt')) - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_POLYGONLAYER, - self.tr('Planting area (Vector polygon)'), [QgsProcessing.SourceType.TypeVectorPolygon])) + self.addParameter( + QgsProcessingParameterFeatureSource( + self.INPUT_POLYGONLAYER, + self.tr("Planting area (Vector polygon)"), + [QgsProcessing.SourceType.TypeVectorPolygon], + ) + ) # self.addParameter(QgsProcessingParameterFile(self.INPUT_POLYGONLAYER, # self.tr('Planting area (Vector polygon)'), extension='shp')) - ttype = ((self.tr('Deciduous'), '0'), - (self.tr('Conifer'), '1')) - self.addParameter(QgsProcessingParameterNumber(self.START_HOUR, - self.tr('From (hour)'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(13), False, minValue=0, maxValue=23)) - self.addParameter(QgsProcessingParameterNumber(self.END_HOUR, - self.tr('Thru (hour)'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(15), False, minValue=0, maxValue=23)) - self.addParameter(QgsProcessingParameterEnum(self.TTYPE, - self.tr('Tree type'), - options=[i[0] for i in ttype], defaultValue=0)) - self.addParameter(QgsProcessingParameterNumber(self.HEIGHT, - self.tr('Tree height (meter above ground level)'), - QgsProcessingParameterNumber.Type.Double, - QVariant(10), False, minValue=0)) - self.addParameter(QgsProcessingParameterNumber(self.DIA, - self.tr('Tree canopy diameter (meter)'), - QgsProcessingParameterNumber.Type.Double, - QVariant(5), False, minValue=0)) - self.addParameter(QgsProcessingParameterNumber(self.TRUNK, - self.tr('Trunk zone height (meter above ground level)'), - QgsProcessingParameterNumber.Type.Double, - QVariant(3), False, minValue=0)) - self.addParameter(QgsProcessingParameterNumber(self.TRANS_VEG, - self.tr('Transmissivity of light through vegetation (%)'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(3), False, minValue=0, maxValue=100)) - self.addParameter(QgsProcessingParameterNumber(self.NTREE, - self.tr('Number of trees to plant'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(3), False, minValue=1)) - + ttype = ((self.tr("Deciduous"), "0"), (self.tr("Conifer"), "1")) + self.addParameter( + QgsProcessingParameterNumber( + self.START_HOUR, + self.tr("From (hour)"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(13), + False, + minValue=0, + maxValue=23, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.END_HOUR, + self.tr("Thru (hour)"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(15), + False, + minValue=0, + maxValue=23, + ) + ) + self.addParameter( + QgsProcessingParameterEnum( + self.TTYPE, + self.tr("Tree type"), + options=[i[0] for i in ttype], + defaultValue=0, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.HEIGHT, + self.tr("Tree height (meter above ground level)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(10), + False, + minValue=0, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.DIA, + self.tr("Tree canopy diameter (meter)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(5), + False, + minValue=0, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.TRUNK, + self.tr("Trunk zone height (meter above ground level)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(3), + False, + minValue=0, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.TRANS_VEG, + self.tr("Transmissivity of light through vegetation (%)"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(3), + False, + minValue=0, + maxValue=100, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.NTREE, + self.tr("Number of trees to plant"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(3), + False, + minValue=1, + ) + ) + # Output # self.addParameter(QgsProcessingParameterRasterDestination(self.OUTPUT_CDSM, # self.tr("Canopy Digital Surface Model"), @@ -180,40 +261,89 @@ def initAlgorithm(self, config): # self.addParameter(QgsProcessingParameterRasterDestination(self.OUTPUT_OCCURRENCE, # self.tr("Occurrence map (Only available when running with random or genetic and more than one tree)"), optional=True, createByDefault=False)) - self.addParameter(QgsProcessingParameterBoolean(self.OUTPUT_CDSM, - self.tr("Save Canopy Digital Surface Model"), defaultValue=True)) - self.addParameter(QgsProcessingParameterBoolean(self.OUTPUT_POINTFILE, - self.tr("Save Vector point file with tree location(s)"), defaultValue=True)) - self.addParameter(QgsProcessingParameterBoolean(self.OUTPUT_OCCURRENCE, - self.tr("Save Occurrence map (Only available when running with random or genetic algorithms and more than one tree)"), defaultValue=False)) - - self.addParameter(QgsProcessingParameterFolderDestination(self.OUTPUT_DIR, - 'Output folder')) + self.addParameter( + QgsProcessingParameterBoolean( + self.OUTPUT_CDSM, + self.tr("Save Canopy Digital Surface Model"), + defaultValue=True, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.OUTPUT_POINTFILE, + self.tr("Save Vector point file with tree location(s)"), + defaultValue=True, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.OUTPUT_OCCURRENCE, + self.tr( + "Save Occurrence map (Only available when running with random or genetic algorithms and more than one tree)" + ), + defaultValue=False, + ) + ) + + self.addParameter( + QgsProcessingParameterFolderDestination( + self.OUTPUT_DIR, "Output folder" + ) + ) # Advanced parameters - iterations = QgsProcessingParameterNumber(self.ITERATIONS, - self.tr("Number of restart iterations"), - QgsProcessingParameterNumber.Type.Integer, defaultValue=2000, minValue=1) - iterations.setFlags(iterations.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + iterations = QgsProcessingParameterNumber( + self.ITERATIONS, + self.tr("Number of restart iterations"), + QgsProcessingParameterNumber.Type.Integer, + defaultValue=2000, + minValue=1, + ) + iterations.setFlags( + iterations.flags() + | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(iterations) - - includeOutside = QgsProcessingParameterBoolean(self.INCLUDE_OUTSIDE, - self.tr("Allow areas outside of Planting area to be included in calculation"), defaultValue=True) - includeOutside.setFlags(includeOutside.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + + includeOutside = QgsProcessingParameterBoolean( + self.INCLUDE_OUTSIDE, + self.tr( + "Allow areas outside of Planting area to be included in calculation" + ), + defaultValue=True, + ) + includeOutside.setFlags( + includeOutside.flags() + | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(includeOutside) - - randomStarting = QgsProcessingParameterBoolean(self.RANDOM_STARTING, - self.tr("Use random starting positions in the hill climbing algorithm"), defaultValue=False) - randomStarting.setFlags(randomStarting.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) - self.addParameter(randomStarting) - - greedyAlgorithm = QgsProcessingParameterBoolean(self.GREEDY_ALGORITHM, - self.tr("Use a greedy algorithm to position trees"), defaultValue=False) - greedyAlgorithm.setFlags(greedyAlgorithm.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + + randomStarting = QgsProcessingParameterBoolean( + self.RANDOM_STARTING, + self.tr( + "Use random starting positions in the hill climbing algorithm" + ), + defaultValue=False, + ) + randomStarting.setFlags( + randomStarting.flags() + | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) + self.addParameter(randomStarting) + + greedyAlgorithm = QgsProcessingParameterBoolean( + self.GREEDY_ALGORITHM, + self.tr("Use a greedy algorithm to position trees"), + defaultValue=False, + ) + greedyAlgorithm.setFlags( + greedyAlgorithm.flags() + | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(greedyAlgorithm) def processAlgorithm(self, parameters, context, feedback): - # InputParameters + # InputParameters # SOLWEIG_DIR = 'SOLWEIG_DIR' # OUTPUT_DIR = 'OUTPUT_DIR' @@ -223,73 +353,103 @@ def processAlgorithm(self, parameters, context, feedback): # START_HOUR = 'START_HOUR' # END_HOUR = 'END_HOUR' - infolder = self.parameterAsString(parameters, self.SOLWEIG_DIR, context) + infolder = self.parameterAsString( + parameters, self.SOLWEIG_DIR, context + ) ttype = self.parameterAsString(parameters, self.TTYPE, context) height = self.parameterAsDouble(parameters, self.HEIGHT, context) dia = self.parameterAsDouble(parameters, self.DIA, context) trunk = self.parameterAsDouble(parameters, self.TRUNK, context) transVeg = self.parameterAsDouble(parameters, self.TRANS_VEG, context) nTree = self.parameterAsInt(parameters, self.NTREE, context) - transVeg = (self.parameterAsDouble(parameters, self.TRANS_VEG, context) / 100) - #INPUT_MET = self.parameterAsString(parameters, self.INPUT_MET, context) - INPUT_MET = infolder + '/metforcing.txt' + transVeg = ( + self.parameterAsDouble(parameters, self.TRANS_VEG, context) / 100 + ) + # INPUT_MET = self.parameterAsString(parameters, self.INPUT_MET, context) + INPUT_MET = infolder + "/metforcing.txt" ITERATIONS = self.parameterAsInt(parameters, self.ITERATIONS, context) h_start = self.parameterAsString(parameters, self.START_HOUR, context) h_end = self.parameterAsString(parameters, self.END_HOUR, context) - outside_selected = self.parameterAsBoolean(parameters, self.INCLUDE_OUTSIDE, context) - greedy = self.parameterAsBoolean(parameters, self.GREEDY_ALGORITHM, context) - starting_algorithm = self.parameterAsBoolean(parameters, self.RANDOM_STARTING, context) + outside_selected = self.parameterAsBoolean( + parameters, self.INCLUDE_OUTSIDE, context + ) + greedy = self.parameterAsBoolean( + parameters, self.GREEDY_ALGORITHM, context + ) + starting_algorithm = self.parameterAsBoolean( + parameters, self.RANDOM_STARTING, context + ) # inputPolygonlayer = parameters[self.INPUT_POLYGONLAYER] - inputPolygonlayer = self.parameterAsVectorLayer(parameters, self.INPUT_POLYGONLAYER, context).dataProvider().dataSourceUri() + inputPolygonlayer = ( + self.parameterAsVectorLayer( + parameters, self.INPUT_POLYGONLAYER, context + ) + .dataProvider() + .dataSourceUri() + ) # outputCDSM = self.parameterAsOutputLayer(parameters, self.OUTPUT_CDSM, context) # outputPoint = self.parameterAsOutputLayer(parameters, self.OUTPUT_POINTFILE, context) # outputTMRT = self.parameterAsOutputLayer(parameters, self.OUTPUT_TMRT, context) # outputOCCURRENCE = self.parameterAsOutputLayer(parameters, self.OUTPUT_OCCURRENCE, context) - outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) + outputDir = self.parameterAsString( + parameters, self.OUTPUT_DIR, context + ) - if parameters['OUTPUT_DIR'] == 'TEMPORARY_OUTPUT': + if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): os.mkdir(outputDir) - outputCDSM = self.parameterAsBool(parameters, self.OUTPUT_CDSM, context) - outputPoint = self.parameterAsBool(parameters, self.OUTPUT_POINTFILE, context) - outputOccurrence = self.parameterAsBool(parameters, self.OUTPUT_OCCURRENCE, context) + outputCDSM = self.parameterAsBool( + parameters, self.OUTPUT_CDSM, context + ) + outputPoint = self.parameterAsBool( + parameters, self.OUTPUT_POINTFILE, context + ) + outputOccurrence = self.parameterAsBool( + parameters, self.OUTPUT_OCCURRENCE, context + ) # Output path for CDSM if outputCDSM: - outputCDSM = outputDir + '/treePlanterCDSM.tif' + outputCDSM = outputDir + "/treePlanterCDSM.tif" # Output path for vector point file if outputPoint: - outputPoint = outputDir + '/treePlanterPoint.shp' + outputPoint = outputDir + "/treePlanterPoint.shp" # Output path for occurrences map - if (outputOccurrence & (nTree > 1) & (not greedy)): - outputOccurrence = outputDir + '/occurrences.tif' + if outputOccurrence & (nTree > 1) & (not greedy): + outputOccurrence = outputDir + "/occurrences.tif" elif nTree == 1: - feedback.setProgressText('Finding location for one tree. Occurrence raster will not be produced.') + feedback.setProgressText( + "Finding location for one tree. Occurrence raster will not be produced." + ) elif greedy: - feedback.setProgressText('Running with greedy algorithm. Occurrence raster will not be produced.') + feedback.setProgressText( + "Running with greedy algorithm. Occurrence raster will not be produced." + ) - feedback.setProgressText('Starting model at ' + str(datetime.datetime.now()) + '...') + feedback.setProgressText( + "Starting model at " + str(datetime.datetime.now()) + "..." + ) feedback.setProgressText("Initializing and loading layers...") # TREE PLANTER CODE - h_start = h_start + '00' - h_end = h_end + '00' + h_start = h_start + "00" + h_end = h_end + "00" ## List shadow and tmrt files in infolder - sh_fl = [f for f in sorted(glob.glob(infolder + '/Shadow_*.tif'))] - tmrt_fl = [f for f in sorted(glob.glob(infolder + '/Tmrt_*.tif'))] + sh_fl = [f for f in sorted(glob.glob(infolder + "/Shadow_*.tif"))] + tmrt_fl = [f for f in sorted(glob.glob(infolder + "/Tmrt_*.tif"))] # Creating vector with hours from file names - h_fl = np.zeros((sh_fl.__len__(),1)) + h_fl = np.zeros((sh_fl.__len__(), 1)) for iz in range(sh_fl.__len__()): - h_fl[iz,0] = np.float32(sh_fl[iz][-9:-5]) + h_fl[iz, 0] = np.float32(sh_fl[iz][-9:-5]) for ix in range(sh_fl.__len__()): if h_start in sh_fl[ix]: @@ -301,25 +461,48 @@ def processAlgorithm(self, parameters, context, feedback): r2 = iy break - r_range = range(r1,r2) + r_range = range(r1, r2) # Loading all shadow and tmrt rasters and summing up all tmrt into one - tree_input = Inputdata(r_range, sh_fl, tmrt_fl, infolder, inputPolygonlayer, feedback) + tree_input = Inputdata( + r_range, sh_fl, tmrt_fl, infolder, inputPolygonlayer, feedback + ) if not outside_selected: - feedback.setProgressText("Tree shade ineffective outside planting area...") - tree_input.buildings = tree_input.buildings * tree_input.selected_area + feedback.setProgressText( + "Tree shade ineffective outside planting area..." + ) + tree_input.buildings = ( + tree_input.buildings * tree_input.selected_area + ) for i in np.arange(tree_input.tmrt_ts.shape[2]): - tree_input.tmrt_ts[:,:,i] = tree_input.tmrt_ts[:,:,i] * tree_input.selected_area - tree_input.shadow[:,:,i] = tree_input.shadow[:,:,i] * tree_input.selected_area + tree_input.tmrt_ts[:, :, i] = ( + tree_input.tmrt_ts[:, :, i] * tree_input.selected_area + ) + tree_input.shadow[:, :, i] = ( + tree_input.shadow[:, :, i] * tree_input.selected_area + ) # Tmrt for shaded point - tmrt_1d, azimuth, altitude, amaxvalue = tmrt_1d_fun(INPUT_MET,infolder,transVeg,tree_input.lon,tree_input.lat,tree_input.dsm,r_range,outputDir) - tmrt_1d = np.around(tmrt_1d, decimals=1) # Round Tmrt to one decimal + tmrt_1d, azimuth, altitude, amaxvalue = tmrt_1d_fun( + INPUT_MET, + infolder, + transVeg, + tree_input.lon, + tree_input.lat, + tree_input.dsm, + r_range, + outputDir, + ) + tmrt_1d = np.around(tmrt_1d, decimals=1) # Round Tmrt to one decimal # Create tree in empty matrix - treey = math.ceil(tree_input.rows / 2) # Y-position of tree in empty setting. Y-position is in the middle of Y. - treex = math.ceil(tree_input.cols / 2) # X-position of tree in empty setting. X-position is in the middle of X. + treey = math.ceil( + tree_input.rows / 2 + ) # Y-position of tree in empty setting. Y-position is in the middle of Y. + treex = math.ceil( + tree_input.cols / 2 + ) # X-position of tree in empty setting. X-position is in the middle of X. # Create Treedata class object treedata = Treedata(ttype, height, trunk, dia, treey, treex) @@ -328,51 +511,97 @@ def processAlgorithm(self, parameters, context, feedback): bld_orig = tree_input.buildings.copy() # Remove all Tmrt values that are on top of buildings - tree_input.tmrt_s = tree_input.tmrt_s * tree_input.buildings + tree_input.tmrt_s = tree_input.tmrt_s * tree_input.buildings cdsm_ = np.zeros((tree_input.rows, tree_input.cols)) # Empty cdsm tdsm_ = np.zeros((tree_input.rows, tree_input.cols)) # Empty tdsm - dsm_empty = np.ones((tree_input.rows, tree_input.cols)) # Empty dsm raster - buildings_empty = np.ones((tree_input.rows, tree_input.cols)) # Empty building raster + dsm_empty = np.ones( + (tree_input.rows, tree_input.cols) + ) # Empty dsm raster + buildings_empty = np.ones( + (tree_input.rows, tree_input.cols) + ) # Empty building raster - rowcol = tree_input.rows*tree_input.cols + rowcol = tree_input.rows * tree_input.cols # CDSM and TDSM for new tree - cdsm_, tdsm_ = makevegdems.vegunitsgeneration(buildings_empty, cdsm_, tdsm_, treedata.ttype, treedata.height, treedata.trunk, treedata.dia, treedata.treey, treedata.treex, - tree_input.cols, tree_input.rows, tree_input.scale) + cdsm_, tdsm_ = makevegdems.vegunitsgeneration( + buildings_empty, + cdsm_, + tdsm_, + treedata.ttype, + treedata.height, + treedata.trunk, + treedata.dia, + treedata.treey, + treedata.treex, + tree_input.cols, + tree_input.rows, + tree_input.scale, + ) # Create shadows for new tree - treebush = np.zeros((tree_input.rows, tree_input.cols)) # Empty tree bush matrix - - treewalls = np.zeros((tree_input.rows, tree_input.cols)) # Empty tree walls matrix - treewallsdir = np.zeros((tree_input.rows, tree_input.cols)) # Empty tree walls direction matrix - - treesh_ts1 = np.zeros((tree_input.rows, tree_input.cols, r_range.__len__())) # Shade for each timestep, shade = 0 - treesh_ts2 = np.zeros((tree_input.rows, tree_input.cols, r_range.__len__())) # Shade for each timestep, shade = 1 - treesh_sum_sh = np.zeros((tree_input.rows,tree_input.cols)) # Sum of shade for all timesteps - treesh_sum_tmrt = np.zeros((tree_input.rows, tree_input.cols)) # Sum of tmrt for all timesteps - - dem_temp = np.ones((tree_input.rows,tree_input.cols)) + treebush = np.zeros( + (tree_input.rows, tree_input.cols) + ) # Empty tree bush matrix + + treewalls = np.zeros( + (tree_input.rows, tree_input.cols) + ) # Empty tree walls matrix + treewallsdir = np.zeros( + (tree_input.rows, tree_input.cols) + ) # Empty tree walls direction matrix + + treesh_ts1 = np.zeros( + (tree_input.rows, tree_input.cols, r_range.__len__()) + ) # Shade for each timestep, shade = 0 + treesh_ts2 = np.zeros( + (tree_input.rows, tree_input.cols, r_range.__len__()) + ) # Shade for each timestep, shade = 1 + treesh_sum_sh = np.zeros( + (tree_input.rows, tree_input.cols) + ) # Sum of shade for all timesteps + treesh_sum_tmrt = np.zeros( + (tree_input.rows, tree_input.cols) + ) # Sum of tmrt for all timesteps + + dem_temp = np.ones((tree_input.rows, tree_input.cols)) # Create shadow for new tree i_c = 0 for i in r_range: - vegsh, sh, _, wallsh, wallsun, wallshve, _, facesun = shadowingfunction_wallheight_23(dem_temp, cdsm_, tdsm_, - azimuth[0][i], altitude[0][i], tree_input.scale, - amaxvalue, treebush, treewalls, - treewallsdir * np.pi / 180.) + vegsh, sh, _, wallsh, wallsun, wallshve, _, facesun = ( + shadowingfunction_wallheight_23( + dem_temp, + cdsm_, + tdsm_, + azimuth[0][i], + altitude[0][i], + tree_input.scale, + amaxvalue, + treebush, + treewalls, + treewallsdir * np.pi / 180.0, + ) + ) treesh_ts1[:, :, i_c] = vegsh - treesh_ts2[:, :, i_c] = (1 - vegsh) - treesh_sum_sh = treesh_sum_sh + treesh_ts2[:,:,i_c] * i - treesh_sum_tmrt[:, :] = treesh_sum_tmrt + treesh_ts2[:,:,i_c] * tmrt_1d[i_c,0] + treesh_ts2[:, :, i_c] = 1 - vegsh + treesh_sum_sh = treesh_sum_sh + treesh_ts2[:, :, i_c] * i + treesh_sum_tmrt[:, :] = ( + treesh_sum_tmrt + treesh_ts2[:, :, i_c] * tmrt_1d[i_c, 0] + ) i_c += 1 ## Regional groups for tree shadows - shadow_rg = Regional_groups(r_range, treesh_sum_sh, treesh_ts2, tmrt_1d) + shadow_rg = Regional_groups( + r_range, treesh_sum_sh, treesh_ts2, tmrt_1d + ) # Create rasters for new tree; shadows and Tmrt - treerasters = Treerasters(treesh_sum_tmrt, shadow_rg.shadow, treesh_ts1, cdsm_, treedata) + treerasters = Treerasters( + treesh_sum_tmrt, shadow_rg.shadow, treesh_ts1, cdsm_, treedata + ) # Crop to size of inputPolygonlayer cropped_rasters = ClippedInputdata(tree_input, treerasters) @@ -380,73 +609,120 @@ def processAlgorithm(self, parameters, context, feedback): if greedy: # Greedy algorithm feedback.setProgressText("Running with greedy algorithm...") - t_y, t_x, tmrt_max = GreedyAlgorithm.greedyplanter(cropped_rasters, treedata, treerasters, tmrt_1d, nTree, feedback) + t_y, t_x, tmrt_max = GreedyAlgorithm.greedyplanter( + cropped_rasters, + treedata, + treerasters, + tmrt_1d, + nTree, + feedback, + ) else: # Hill climbing algorithm # Creating matrices with Tmrt for tree shadows at each possible position - treerasters, positions = TreePlanterPrepare.treeplanter(cropped_rasters, treedata, treerasters, tmrt_1d) + treerasters, positions = TreePlanterPrepare.treeplanter( + cropped_rasters, treedata, treerasters, tmrt_1d + ) # Starting algorithm. if starting_algorithm: sa = 0 - feedback.setProgressText("Running hill climbing algorithm with random algorithm for starting positions...") + feedback.setProgressText( + "Running hill climbing algorithm with random algorithm for starting positions..." + ) else: sa = 1 - feedback.setProgressText("Running hill climbing algorithm with genetic algorithm for starting positions...") + feedback.setProgressText( + "Running hill climbing algorithm with genetic algorithm for starting positions..." + ) if nTree == 1: - t_y, t_x = np.where(treerasters.d_tmrt == np.max(treerasters.d_tmrt)) + t_y, t_x = np.where( + treerasters.d_tmrt == np.max(treerasters.d_tmrt) + ) else: possible_locations = np.sum(treerasters.d_tmrt > 0) - feedback.setProgressText(str(possible_locations) + " possible locations for trees...") + feedback.setProgressText( + str(possible_locations) + + " possible locations for trees..." + ) # Running tree planter - t_y, t_x, tmrt_max, t_y_all, t_x_all = TreePlanterHillClimber.treeoptinit(treerasters, cropped_rasters, positions, treedata, - shadow_rg, tmrt_1d, nTree, ITERATIONS, sa, feedback) - + t_y, t_x, tmrt_max, t_y_all, t_x_all = ( + TreePlanterHillClimber.treeoptinit( + treerasters, + cropped_rasters, + positions, + treedata, + shadow_rg, + tmrt_1d, + nTree, + ITERATIONS, + sa, + feedback, + ) + ) + if outputOccurrence: # Create occurrence map t_y_all += cropped_rasters.clip_rows[0] t_x_all += cropped_rasters.clip_rows[0] - occurrence_map = np.zeros((tree_input.rows,tree_input.cols)) + occurrence_map = np.zeros( + (tree_input.rows, tree_input.cols) + ) for row in np.arange(t_y_all.shape[0]): for col in np.arange(t_y_all.shape[1]): - occurrence_map[t_y_all[row, col].astype(int), t_x_all[row, col].astype(int)] += 1 + occurrence_map[ + t_y_all[row, col].astype(int), + t_x_all[row, col].astype(int), + ] += 1 occurrence_map /= ITERATIONS # Save occurrence map as raster - saveraster(tree_input.dataSet, outputOccurrence, occurrence_map) + saveraster( + tree_input.dataSet, outputOccurrence, occurrence_map + ) # Optimal locations for CDSM and point outputs t_y = t_y + cropped_rasters.clip_rows[0] t_x = t_x + cropped_rasters.clip_cols[0] - cdsm_empty = np.zeros((tree_input.rows,tree_input.cols)) - tdsm_empty = np.zeros((tree_input.rows,tree_input.cols)) - - cdsm_new = np.zeros((tree_input.rows,tree_input.cols)) - tdsm_new = np.zeros((tree_input.rows,tree_input.cols)) - - for i in range(0,t_y.shape[0]): - cdsm_temp, tdsm_temp = makevegdems.vegunitsgeneration(bld_orig, cdsm_empty, tdsm_empty, - treedata.ttype, treedata.height, treedata.trunk, treedata.dia, - t_y[i], t_x[i], - tree_input.cols, tree_input.rows, tree_input.scale) + cdsm_empty = np.zeros((tree_input.rows, tree_input.cols)) + tdsm_empty = np.zeros((tree_input.rows, tree_input.cols)) + + cdsm_new = np.zeros((tree_input.rows, tree_input.cols)) + tdsm_new = np.zeros((tree_input.rows, tree_input.cols)) + + for i in range(0, t_y.shape[0]): + cdsm_temp, tdsm_temp = makevegdems.vegunitsgeneration( + bld_orig, + cdsm_empty, + tdsm_empty, + treedata.ttype, + treedata.height, + treedata.trunk, + treedata.dia, + t_y[i], + t_x[i], + tree_input.cols, + tree_input.rows, + tree_input.scale, + ) cdsm_new += cdsm_temp tdsm_new += tdsm_temp cdsm_new = cdsm_new + tree_input.cdsm - + # Save CDSM as raster saveraster(tree_input.dataSet, outputCDSM, cdsm_new) # Create point vector and save as shapefile srs = osr.SpatialReference() srs.ImportFromWkt(tree_input.dataSet.GetProjection()) - driver = ogr.GetDriverByName('ESRI Shapefile') + driver = ogr.GetDriverByName("ESRI Shapefile") shapeFile = driver.CreateDataSource(outputPoint) - shapeLayer = shapeFile.CreateLayer('ogr_pts', srs, ogr.wkbPoint) + shapeLayer = shapeFile.CreateLayer("ogr_pts", srs, ogr.wkbPoint) layerDefinition = shapeLayer.GetLayerDefn() # Add fields for tree height, canopy diameter and trunk height @@ -465,13 +741,15 @@ def processAlgorithm(self, parameters, context, feedback): trunkField.SetPrecision(2) shapeLayer.CreateField(trunkField) - (minx, x_size, x_rotation, miny, y_rotation, y_size) = tree_input.dataSet.GetGeoTransform() - for i in range(0,t_y.shape[0]): + minx, x_size, x_rotation, miny, y_rotation, y_size = ( + tree_input.dataSet.GetGeoTransform() + ) + for i in range(0, t_y.shape[0]): temp_y = t_y[i] * y_size + miny + (y_size / 2) - #temp_y = t_y[i] * y_size + miny # + (1 / y_size) + # temp_y = t_y[i] * y_size + miny # + (1 / y_size) temp_x = t_x[i] * x_size + minx + (x_size / 2) - #temp_x = t_x[i] * x_size + minx # + (1 / x_size) - + # temp_x = t_x[i] * x_size + minx # + (1 / x_size) + point = ogr.Geometry(ogr.wkbPoint) point.SetPoint(0, temp_x, temp_y) @@ -492,54 +770,58 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText("TreePlanter: Model calculation finished.") return {self.OUTPUT_DIR: outputDir} - + def name(self): - return 'Outdoor Thermal Comfort: TreePlanter' + return "Outdoor Thermal Comfort: TreePlanter" def displayName(self): - return self.tr('Outdoor Thermal Comfort: TreePlanter v1.0.1') + return self.tr("Outdoor Thermal Comfort: TreePlanter v1.0.1") def group(self): return self.tr(self.groupId()) def groupId(self): - return 'Post-Processor' + return "Post-Processor" def shortHelpString(self): - return self.tr('This is a model for optimization of tree arrangement to mitigate thermal ' - 'heat stress with respect to radiant load represented by mean radiant temperature (Tmrt). ' - 'The optimization of tree arrangement is achieved by utilizing ' - 'a metaheuristic hill-climbing algorithm that evaluates the combined shading effect of 1 to N ' - 'trees and their corresponding mitigating decrease in Tmrt.\n' - '\n' - 'Default settings:' - '
  • Metaheuristic algorithm = Hill-climbing algorithm
  • ' - '
  • Algorithm for starting positions = genetic
  • ' - '
  • Number of iterations = 2000
  • ' - '
  • Areas outside of Planting area are included
' - '\n' - 'TIPS and TRICKS for best performance:
' - '- The input folder (Path to SOLWEIG output directory) should have been produced beforehand using the SOLWEIG-' - 'model with the option to "Save necessary rasters for the TreePlanter tool" ticked in\n' - '- The model has a very high computational complexity. Therefore, try to reduce the number of variables by e.g.: \n' - ' + Use a low number of trees\n' - ' + Try to use small area as planting area\n' - ' + Use hourly meteorological data, preferably one single day.\n' - ' If running with a large number of trees, or over a large extent, consider using the greedy algorithm.\n' - '-------------\n' - 'Wallenberg and Lindberg (2020): https://doi.org/10.5194/gmd-15-1107-2022
' - '--------------\n' - 'Full manual available via the Help-button.') + return self.tr( + "This is a model for optimization of tree arrangement to mitigate thermal " + "heat stress with respect to radiant load represented by mean radiant temperature (Tmrt). " + "The optimization of tree arrangement is achieved by utilizing " + "a metaheuristic hill-climbing algorithm that evaluates the combined shading effect of 1 to N " + "trees and their corresponding mitigating decrease in Tmrt.\n" + "\n" + "Default settings:" + "
  • Metaheuristic algorithm = Hill-climbing algorithm
  • " + "
  • Algorithm for starting positions = genetic
  • " + "
  • Number of iterations = 2000
  • " + "
  • Areas outside of Planting area are included
" + "\n" + "TIPS and TRICKS for best performance:
" + "- The input folder (Path to SOLWEIG output directory) should have been produced beforehand using the SOLWEIG-" + 'model with the option to "Save necessary rasters for the TreePlanter tool" ticked in\n' + "- The model has a very high computational complexity. Therefore, try to reduce the number of variables by e.g.: \n" + " + Use a low number of trees\n" + " + Try to use small area as planting area\n" + " + Use hourly meteorological data, preferably one single day.\n" + " If running with a large number of trees, or over a large extent, consider using the greedy algorithm.\n" + "-------------\n" + "Wallenberg and Lindberg (2020): https://doi.org/10.5194/gmd-15-1107-2022
" + "--------------\n" + "Full manual available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/processor/Outdoor%20Thermal%20Comfort%20TreePlanter.html" return url def tr(self, string): - return QCoreApplication.translate('Processing', string) + return QCoreApplication.translate("Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/icon_tree.png") return icon diff --git a/postprocessor/urock_analyser_algorithm.py b/postprocessor/urock_analyser_algorithm.py index 5e4884c..bc7e5ca 100644 --- a/postprocessor/urock_analyser_algorithm.py +++ b/postprocessor/urock_analyser_algorithm.py @@ -22,26 +22,29 @@ ***************************************************************************/ """ -__author__ = 'Jérémy Bernard, University of Gothenburg' -__date__ = '2022-01-19' -__copyright__ = '(C) 2022 by Jérémy Bernard, University of Gothenburg' +__author__ = "Jérémy Bernard, University of Gothenburg" +__date__ = "2022-01-19" +__copyright__ = "(C) 2022 by Jérémy Bernard, University of Gothenburg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication import processing -from qgis.core import (QgsProcessing, - QgsProcessingAlgorithm, - QgsProcessingParameterField, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterFolderDestination, - QgsProcessingParameterString, - QgsProcessingParameterBoolean, - QgsProcessingParameterFile, - QgsProcessingException) -#from qgis.utils import iface +from qgis.core import ( + QgsProcessing, + QgsProcessingAlgorithm, + QgsProcessingParameterField, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterFolderDestination, + QgsProcessingParameterString, + QgsProcessingParameterBoolean, + QgsProcessingParameterFile, + QgsProcessingException, +) + +# from qgis.utils import iface import os from pathlib import Path import struct @@ -49,9 +52,16 @@ import inspect import xarray as xr -from ..functions.URock.H2gisConnection import getJavaDir, setJavaDir, saveJavaDir +from ..functions.URock.H2gisConnection import ( + getJavaDir, + setJavaDir, + saveJavaDir, +) from ..functions.URock.urock_analyser_functions import plotSectionalViews -from ..functions.URock.GlobalVariables import TEMPO_DIRECTORY, OUTPUT_VECTOR_EXTENSION +from ..functions.URock.GlobalVariables import ( + TEMPO_DIRECTORY, + OUTPUT_VECTOR_EXTENSION, +) class URockAnalyserAlgorithm(QgsProcessingAlgorithm): @@ -74,16 +84,15 @@ class URockAnalyserAlgorithm(QgsProcessingAlgorithm): # Input variables # JAVA_PATH = "JAVA_PATH" - INPUT_LINES = 'INPUT_LINES' - INPUT_POLYGONS = 'INPUT_POLYGONS' + INPUT_LINES = "INPUT_LINES" + INPUT_POLYGONS = "INPUT_POLYGONS" ID_FIELD_LINES = "ID_FIELD_LINES" ID_FIELD_POLYGONS = "ID_FIELD_POLYGONS" - INPUT_WIND_FILE = 'INPUT_WIND_FILE' - IS_STREAM = 'IS_STREAM' - OUTPUT_DIRECTORY = 'OUTPUT_DIRECTORY' + INPUT_WIND_FILE = "INPUT_WIND_FILE" + IS_STREAM = "IS_STREAM" + OUTPUT_DIRECTORY = "OUTPUT_DIRECTORY" SIMULATION_NAME = "SIMULATION_NAME" - def initAlgorithm(self, config): """ Here we define the inputs and output of the algorithm, along @@ -94,67 +103,81 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterFeatureSource( self.INPUT_LINES, - self.tr('Input line layer for vertical sectional plot(s)'), + self.tr("Input line layer for vertical sectional plot(s)"), [QgsProcessing.SourceType.TypeVectorLine], - optional = True + optional=True, ) ) # Booleans to let the user decide the type of plotting (stream or arrows) self.addParameter( QgsProcessingParameterBoolean( self.IS_STREAM, - self.tr("Plot streams instead of arrows (works only for cubic voxels"), - defaultValue=False)) - + self.tr( + "Plot streams instead of arrows (works only for cubic voxels" + ), + defaultValue=False, + ) + ) + self.addParameter( QgsProcessingParameterField( self.ID_FIELD_LINES, - self.tr('Lines ID field (used if mutiple lines is present)'), + self.tr("Lines ID field (used if mutiple lines is present)"), None, self.INPUT_LINES, QgsProcessingParameterField.DataType.Numeric, - optional = True)) - + optional=True, + ) + ) - # We add the input vector features source (polygon layer) self.addParameter( QgsProcessingParameterFeatureSource( self.INPUT_POLYGONS, - self.tr('Input polygons layer for average wind profile'), + self.tr("Input polygons layer for average wind profile"), [QgsProcessing.SourceType.TypeVectorPolygon], - optional = True + optional=True, ) ) self.addParameter( QgsProcessingParameterField( self.ID_FIELD_POLYGONS, - self.tr('Polygons ID field (used if mutiple polygons is present)'), + self.tr( + "Polygons ID field (used if mutiple polygons is present)" + ), None, self.INPUT_POLYGONS, QgsProcessingParameterField.DataType.Numeric, - optional = True)) + optional=True, + ) + ) # We add the input wind speed saved in a NetCDF format self.addParameter( QgsProcessingParameterFile( self.INPUT_WIND_FILE, - self.tr('Input wind data file (.nc)'), - extension='nc')) + self.tr("Input wind data file (.nc)"), + extension="nc", + ) + ) # Output directory and file names self.addParameter( QgsProcessingParameterString( self.SIMULATION_NAME, - self.tr('Name of the simulation used for saving figure(s)'), + self.tr("Name of the simulation used for saving figure(s)"), "", False, - True)) + True, + ) + ) self.addParameter( QgsProcessingParameterFolderDestination( self.OUTPUT_DIRECTORY, - self.tr('Directory to save the figure(s)'), - optional = True)) - + self.tr("Directory to save the figure(s)"), + optional=True, + ) + ) + # Optional parameter # self.addParameter( # QgsProcessingParameterString( @@ -162,67 +185,95 @@ def initAlgorithm(self, config): # self.tr('Java environment path (should be set automatically'), # javaDirDefault, # False, - # False)) + # False)) def processAlgorithm(self, parameters, context, feedback): """ Here is where the processing itself takes place. """ - + # Get the plugin directory to save some useful files plugin_directory = self.plugin_dir = os.path.dirname(__file__) # Get the default value of the Java environment path if already exists javaDirDefault = getJavaDir(plugin_directory) - - if not javaDirDefault: # Raise an error if could not find a Java installation - raise QgsProcessingException("No Java installation found") - elif ("Program Files (x86)" in javaDirDefault) and (struct.calcsize("P") * 8 != 32): + + if ( + not javaDirDefault + ): # Raise an error if could not find a Java installation + raise QgsProcessingException("No Java installation found") + elif ("Program Files (x86)" in javaDirDefault) and ( + struct.calcsize("P") * 8 != 32 + ): # Raise an error if Java is 32 bits but Python 64 bits - raise QgsProcessingException('Only a 32 bits version of Java has been'+ - 'found while your Python installation is 64 bits.'+ - 'Consider installing a 64 bits Java version.') - else: # Set a Java dir if not exist and save it into a file in the plugin repository + raise QgsProcessingException( + "Only a 32 bits version of Java has been" + + "found while your Python installation is 64 bits." + + "Consider installing a 64 bits Java version." + ) + else: # Set a Java dir if not exist and save it into a file in the plugin repository setJavaDir(javaDirDefault) - saveJavaDir(javaPath = javaDirDefault, - pluginDirectory = plugin_directory) + saveJavaDir( + javaPath=javaDirDefault, pluginDirectory=plugin_directory + ) # Defines java environmenet variable javaEnvVar = javaDirDefault - + # Defines path of the NetCDF file - inputWindFile = self.parameterAsString(parameters, self.INPUT_WIND_FILE, context) - + inputWindFile = self.parameterAsString( + parameters, self.INPUT_WIND_FILE, context + ) + # Get line layer, id field name and then file directory and crs - inputLines = self.parameterAsVectorLayer(parameters, self.INPUT_LINES, context) - idLines = self.parameterAsString(parameters, self.ID_FIELD_LINES, context) + inputLines = self.parameterAsVectorLayer( + parameters, self.INPUT_LINES, context + ) + idLines = self.parameterAsString( + parameters, self.ID_FIELD_LINES, context + ) if inputLines: # Test the number of vertices (points) per line features = inputLines.getFeatures() for feature in features: if len(feature.geometry().asMultiPolyline()[0]) > 2: - raise QgsProcessingException("Only 2 vertices (point) lines are accepted. "+ - "One of your lines contains more than 2 vertices,"+ - " please explode it into several 2 vertices lines.") - + raise QgsProcessingException( + "Only 2 vertices (point) lines are accepted. " + + "One of your lines contains more than 2 vertices," + + " please explode it into several 2 vertices lines." + ) + lines_file = str(inputLines.dataProvider().dataSourceUri()) if lines_file.count("|") > 0: lines_file = lines_file.split("|")[0] srid_lines = inputLines.crs().authid()[5:] # Save the file as geojson in case of .gpkg format if lines_file.split(".")[-1].lower() == "gpkg": - new_lines_file = os.path.join(TEMPO_DIRECTORY, "LINES" + OUTPUT_VECTOR_EXTENSION) - processing.run("native:savefeatures", - {'INPUT': lines_file, - 'OUTPUT': new_lines_file,'LAYER_NAME':'','DATASOURCE_OPTIONS':'','LAYER_OPTIONS':''}) + new_lines_file = os.path.join( + TEMPO_DIRECTORY, "LINES" + OUTPUT_VECTOR_EXTENSION + ) + processing.run( + "native:savefeatures", + { + "INPUT": lines_file, + "OUTPUT": new_lines_file, + "LAYER_NAME": "", + "DATASOURCE_OPTIONS": "", + "LAYER_OPTIONS": "", + }, + ) lines_file = new_lines_file else: - lines_file = '' + lines_file = "" srid_lines = None - + # Get polygon layer, id field name and then file directory and crs - inputPolygons = self.parameterAsVectorLayer(parameters, self.INPUT_POLYGONS, context) - idPolygons = self.parameterAsString(parameters, self.ID_FIELD_POLYGONS, context) + inputPolygons = self.parameterAsVectorLayer( + parameters, self.INPUT_POLYGONS, context + ) + idPolygons = self.parameterAsString( + parameters, self.ID_FIELD_POLYGONS, context + ) if inputPolygons: polygons_file = str(inputPolygons.dataProvider().dataSourceUri()) if polygons_file.count("|") > 0: @@ -230,54 +281,74 @@ def processAlgorithm(self, parameters, context, feedback): srid_polygons = inputPolygons.crs().authid()[5:] # Save the file as geojson in case of .gpkg format if polygons_file.split(".")[-1].lower() == "gpkg": - new_polygons_file = os.path.join(TEMPO_DIRECTORY, "POLYGONS" + OUTPUT_VECTOR_EXTENSION) - processing.run("native:savefeatures", - {'INPUT': polygons_file, - 'OUTPUT': new_polygons_file,'LAYER_NAME':'','DATASOURCE_OPTIONS':'','LAYER_OPTIONS':''}) + new_polygons_file = os.path.join( + TEMPO_DIRECTORY, "POLYGONS" + OUTPUT_VECTOR_EXTENSION + ) + processing.run( + "native:savefeatures", + { + "INPUT": polygons_file, + "OUTPUT": new_polygons_file, + "LAYER_NAME": "", + "DATASOURCE_OPTIONS": "", + "LAYER_OPTIONS": "", + }, + ) polygons_file = new_polygons_file else: - polygons_file = '' + polygons_file = "" srid_polygons = None if inputLines and inputPolygons: if srid_polygons != srid_lines: - feedback.pushWarning('Coordinate system of input building layer and vegetation layer differ!') - + feedback.pushWarning( + "Coordinate system of input building layer and vegetation layer differ!" + ) + # Defines outputs isStream = self.parameterAsBool(parameters, self.IS_STREAM, context) - simulationName = self.parameterAsString(parameters, self.SIMULATION_NAME, context) - outputDirectory = self.parameterAsString(parameters, self.OUTPUT_DIRECTORY, context) + simulationName = self.parameterAsString( + parameters, self.SIMULATION_NAME, context + ) + outputDirectory = self.parameterAsString( + parameters, self.OUTPUT_DIRECTORY, context + ) # Creates the output folder if it does not exist - if not os.path.exists(outputDirectory) and outputDirectory != '': + if not os.path.exists(outputDirectory) and outputDirectory != "": if os.path.exists(Path(outputDirectory).parent.absolute()): os.mkdir(outputDirectory) else: - raise QgsProcessingException('The output directory does not exist, neither its parent directory') - + raise QgsProcessingException( + "The output directory does not exist, neither its parent directory" + ) + # Check that conditions are fullfilled for stream calculation if isStream: horizontal_res = xr.open_dataset(inputWindFile).horizontal_res vertical_res = xr.open_dataset(inputWindFile).vertical_res if horizontal_res != vertical_res: - raise QgsProcessingException('For stream plots, your netCDF file should contain cubic voxels') - + raise QgsProcessingException( + "For stream plots, your netCDF file should contain cubic voxels" + ) + # Start the analyser - fig, ax, scale, fig_poly, ax_poly =\ - plotSectionalViews(pluginDirectory = plugin_directory, - inputWindFile = inputWindFile, - lines_file = lines_file, - srid_lines = srid_lines, - idLines = idLines, - polygons_file = polygons_file, - srid_polygons = srid_polygons, - idPolygons = idPolygons, - isStream = isStream, - savePlot = True, - outputDirectory = outputDirectory, - simulationName = simulationName, - feedback = feedback) - + fig, ax, scale, fig_poly, ax_poly = plotSectionalViews( + pluginDirectory=plugin_directory, + inputWindFile=inputWindFile, + lines_file=lines_file, + srid_lines=srid_lines, + idLines=idLines, + polygons_file=polygons_file, + srid_polygons=srid_polygons, + idPolygons=idPolygons, + isStream=isStream, + savePlot=True, + outputDirectory=outputDirectory, + simulationName=simulationName, + feedback=feedback, + ) + # Return the results of the algorithm. return {self.OUTPUT_DIRECTORY: outputDirectory} @@ -289,14 +360,14 @@ def name(self): lowercase alphanumeric characters only and no spaces or other formatting characters. """ - return 'Urban Wind Field: URock analyzer' + return "Urban Wind Field: URock analyzer" def displayName(self): """ Returns the translated algorithm name, which should be used for any user-visible display of the algorithm name. """ - return self.tr('Urban Wind Field: URock AnalyZer') + return self.tr("Urban Wind Field: URock AnalyZer") def group(self): """ @@ -313,34 +384,38 @@ def groupId(self): contain lowercase alphanumeric characters only and no spaces or other formatting characters. """ - return 'Post-Processor' + return "Post-Processor" def tr(self, string): - return QCoreApplication.translate('Processing', string) + return QCoreApplication.translate("Processing", string) def shortHelpString(self): - return self.tr('The URock Analyser plugin can be used to plot the results '+ - 'obtained using the URock model along the vertical axis.'+ - ' This plugin is available only from UMEP for processing .\n\n' - 'Rem: The plug-in performance is far from optimum since the '+ - 'NetCDF file is loaded in Java AND in Python. '+ - 'Thus it could take some time if the NetCDF file is large.' - '\n' - '\n' - 'This tools requires Java. If Java is not installed on your system,'+ - 'visit www.java.com and install the latest version. Make sure to install correct version '+ - 'based on your system architecture (32- or 64-bit).' - '\n' - '\n' - '---------------\n' - 'Full manual available via the Help-button.') + return self.tr( + "The URock Analyser plugin can be used to plot the results " + + "obtained using the URock model along the vertical axis." + + " This plugin is available only from UMEP for processing .\n\n" + "Rem: The plug-in performance is far from optimum since the " + + "NetCDF file is loaded in Java AND in Python. " + + "Thus it could take some time if the NetCDF file is large." + "\n" + "\n" + "This tools requires Java. If Java is not installed on your system," + + "visit www.java.com and install the latest version. Make sure to install correct version " + + "based on your system architecture (32- or 64-bit)." + "\n" + "\n" + "---------------\n" + "Full manual available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/post_processor/Urban%20Wind%20Fields%20URock%20Analyzer.html" return url - + def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/urock.png") return icon diff --git a/postprocessor/uwganalyzer_algorithm.py b/postprocessor/uwganalyzer_algorithm.py index 5047007..30a45e0 100644 --- a/postprocessor/uwganalyzer_algorithm.py +++ b/postprocessor/uwganalyzer_algorithm.py @@ -1,28 +1,30 @@ # -*- coding: utf-8 -*- -__author__ = 'Fredrik Lindberg' -__date__ = '2021-02-05' -__copyright__ = '(C) 2021 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2021-02-05" +__copyright__ = "(C) 2021 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QVariant, QDate, Qt -from qgis.core import (QgsProcessing, - QgsProcessingAlgorithm, - QgsProcessingParameterBoolean, - QgsProcessingParameterNumber, - QgsProcessingParameterRasterDestination, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterField, - QgsProcessingParameterEnum, - QgsProcessingException, - QgsProcessingParameterDateTime, - QgsVectorDataProvider, - QgsProcessingParameterFile, - QgsField) +from qgis.core import ( + QgsProcessing, + QgsProcessingAlgorithm, + QgsProcessingParameterBoolean, + QgsProcessingParameterNumber, + QgsProcessingParameterRasterDestination, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterField, + QgsProcessingParameterEnum, + QgsProcessingException, + QgsProcessingParameterDateTime, + QgsVectorDataProvider, + QgsProcessingParameterFile, + QgsField, +) from qgis.PyQt.QtGui import QIcon from processing.gui.wrappers import WidgetWrapper @@ -46,88 +48,152 @@ class ProcessingUWGAnalyzerAlgorithm(QgsProcessingAlgorithm): """ This class is a processing version of UWGAnalyzer but only for generating aggregated grids """ - INPUT_FOLDER = 'INPUT_FOLDER' - OUTPUT_FOLDER = 'OUTPUT_FOLDER' - INPUT_POLYGONLAYER = 'INPUT_POLYGONLAYER' - ID_FIELD = 'ID_FIELD' - SINGLE_DAY_BOOL = 'SINGLE_DAY_BOOL' - SINGLE_DAY = 'SINGLE_DAY' - IRREGULAR = 'IRREGULAR' - PIXELSIZE = 'PIXELSIZE' - STAT_TYPE = 'STAT_TYPE' - ADD_ATTRIBUTES ='ADD_ATTRIBUTES' - - # Output - UWG_GRID_OUT = 'UWG_GRID_OUT' + INPUT_FOLDER = "INPUT_FOLDER" + OUTPUT_FOLDER = "OUTPUT_FOLDER" + INPUT_POLYGONLAYER = "INPUT_POLYGONLAYER" + ID_FIELD = "ID_FIELD" + SINGLE_DAY_BOOL = "SINGLE_DAY_BOOL" + SINGLE_DAY = "SINGLE_DAY" + IRREGULAR = "IRREGULAR" + PIXELSIZE = "PIXELSIZE" + STAT_TYPE = "STAT_TYPE" + ADD_ATTRIBUTES = "ADD_ATTRIBUTES" + + # Output + UWG_GRID_OUT = "UWG_GRID_OUT" def initAlgorithm(self, config): self.plugin_dir = os.path.dirname(__file__) - self.addParameter(QgsProcessingParameterFile(self.INPUT_FOLDER, - self.tr('Path to folder where UWG input files are located'), - QgsProcessingParameterFile.Behavior.Folder)) - self.addParameter(QgsProcessingParameterFile(self.OUTPUT_FOLDER, - self.tr('Path to folder where UWG output files are located'), - QgsProcessingParameterFile.Behavior.Folder)) - self.addParameter(QgsProcessingParameterBoolean(self.SINGLE_DAY_BOOL, - self.tr("Examine single night"), defaultValue=False, optional=True)) - self.addParameter(QgsProcessingParameterDateTime(self.SINGLE_DAY, - self.tr('Month and day when single night begins (year is irrelevant)'), - QgsProcessingParameterDateTime.Type.Date)) - self.statType = ((self.tr('Mean'), '0'), - (self.tr('Maximun'), '1'), - (self.tr('Median'), '2'), - (self.tr('75% percentile'), '3'), - (self.tr('95% percentile'), '4')) - self.addParameter(QgsProcessingParameterEnum(self.STAT_TYPE, - self.tr('Statistic measure'), - options=[i[0] for i in self.statType], - defaultValue=0)) - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_POLYGONLAYER, - self.tr('Vector polygon grid'), - [QgsProcessing.SourceType.TypeVectorPolygon])) - self.addParameter(QgsProcessingParameterField(self.ID_FIELD, - self.tr('ID field'), - '', - self.INPUT_POLYGONLAYER, - QgsProcessingParameterField.DataType.Numeric)) - self.addParameter(QgsProcessingParameterBoolean(self.IRREGULAR, - self.tr("Polygon grid irregular (not squared)"), - defaultValue=False)) - self.addParameter(QgsProcessingParameterNumber(self.PIXELSIZE, - self.tr('Pixelsize if irregular grid is used (meter)'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(10), False, minValue=1)) + self.addParameter( + QgsProcessingParameterFile( + self.INPUT_FOLDER, + self.tr("Path to folder where UWG input files are located"), + QgsProcessingParameterFile.Behavior.Folder, + ) + ) + self.addParameter( + QgsProcessingParameterFile( + self.OUTPUT_FOLDER, + self.tr("Path to folder where UWG output files are located"), + QgsProcessingParameterFile.Behavior.Folder, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.SINGLE_DAY_BOOL, + self.tr("Examine single night"), + defaultValue=False, + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterDateTime( + self.SINGLE_DAY, + self.tr( + "Month and day when single night begins (year is irrelevant)" + ), + QgsProcessingParameterDateTime.Type.Date, + ) + ) + self.statType = ( + (self.tr("Mean"), "0"), + (self.tr("Maximun"), "1"), + (self.tr("Median"), "2"), + (self.tr("75% percentile"), "3"), + (self.tr("95% percentile"), "4"), + ) + self.addParameter( + QgsProcessingParameterEnum( + self.STAT_TYPE, + self.tr("Statistic measure"), + options=[i[0] for i in self.statType], + defaultValue=0, + ) + ) + self.addParameter( + QgsProcessingParameterFeatureSource( + self.INPUT_POLYGONLAYER, + self.tr("Vector polygon grid"), + [QgsProcessing.SourceType.TypeVectorPolygon], + ) + ) + self.addParameter( + QgsProcessingParameterField( + self.ID_FIELD, + self.tr("ID field"), + "", + self.INPUT_POLYGONLAYER, + QgsProcessingParameterField.DataType.Numeric, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.IRREGULAR, + self.tr("Polygon grid irregular (not squared)"), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.PIXELSIZE, + self.tr("Pixelsize if irregular grid is used (meter)"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(10), + False, + minValue=1, + ) + ) # Output - self.addParameter(QgsProcessingParameterBoolean(self.ADD_ATTRIBUTES, - self.tr("Add results to vector polygon grid attribute table"), - defaultValue=False)) - self.addParameter(QgsProcessingParameterRasterDestination(self.UWG_GRID_OUT, - self.tr("Output raster from statistical analysis"), - None, - optional=True, - createByDefault=False)) + self.addParameter( + QgsProcessingParameterBoolean( + self.ADD_ATTRIBUTES, + self.tr("Add results to vector polygon grid attribute table"), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterRasterDestination( + self.UWG_GRID_OUT, + self.tr("Output raster from statistical analysis"), + None, + optional=True, + createByDefault=False, + ) + ) def processAlgorithm(self, parameters, context, feedback): - + # InputParameters uwgIn = self.parameterAsString(parameters, self.INPUT_FOLDER, context) - uwgOut = self.parameterAsString(parameters, self.OUTPUT_FOLDER, context) + uwgOut = self.parameterAsString( + parameters, self.OUTPUT_FOLDER, context + ) # variaIn = self.parameterAsString(parameters, self.VARIA_IN, context) # startday = self.parameterAsString(parameters, self.DATEINISTART, context) - singleNight = self.parameterAsBool(parameters, self.SINGLE_DAY_BOOL, context) + singleNight = self.parameterAsBool( + parameters, self.SINGLE_DAY_BOOL, context + ) # endday = self.parameterAsString(parameters, self.DATEINIEND, context) - inputPolygonlayer = self.parameterAsVectorLayer(parameters, self.INPUT_POLYGONLAYER, context) + inputPolygonlayer = self.parameterAsVectorLayer( + parameters, self.INPUT_POLYGONLAYER, context + ) idField = self.parameterAsFields(parameters, self.ID_FIELD, context) irreg = self.parameterAsBool(parameters, self.IRREGULAR, context) - statTypeStr = self.parameterAsString(parameters, self.STAT_TYPE, context) + statTypeStr = self.parameterAsString( + parameters, self.STAT_TYPE, context + ) # dayTypeStr = self.parameterAsString(parameters, self.TIME_OF_DAY, context) pixelsize = self.parameterAsDouble(parameters, self.PIXELSIZE, context) - addAttributes = self.parameterAsBool(parameters, self.ADD_ATTRIBUTES, context) - outputStat = self.parameterAsOutputLayer(parameters, self.UWG_GRID_OUT, context) + addAttributes = self.parameterAsBool( + parameters, self.ADD_ATTRIBUTES, context + ) + outputStat = self.parameterAsOutputLayer( + parameters, self.UWG_GRID_OUT, context + ) feedback.setProgressText("Initializing...") @@ -143,50 +209,69 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText("Prefix: " + prefix) uwgDict = read_uwg_file(uwgIn, fileList[0][:-4]) - mm = uwgDict['Month'] - dd = uwgDict['Day'] - nDays = uwgDict['nDay'] + mm = uwgDict["Month"] + dd = uwgDict["Day"] + nDays = uwgDict["nDay"] # Load rural data - sitein = uwgOut + '/metdata_UMEP.txt' + sitein = uwgOut + "/metdata_UMEP.txt" dataref = np.genfromtxt(sitein, skip_header=1) - yyyy = dataref[0,0] + yyyy = dataref[0, 0] start = datetime.date(int(yyyy), int(mm), int(dd)) end = start + datetime.timedelta(days=int(nDays)) - feedback.setProgressText('Days indentified as modelled by UWG: ' + start.strftime('%d %b') + ' to ' + end.strftime('%d %b')) + feedback.setProgressText( + "Days indentified as modelled by UWG: " + + start.strftime("%d %b") + + " to " + + end.strftime("%d %b") + ) if singleNight: - singledate = self.parameterAsString(parameters, self.SINGLE_DAY, context) - startDate = datetime.datetime.strptime(singledate, '%Y-%m-%d') + singledate = self.parameterAsString( + parameters, self.SINGLE_DAY, context + ) + startDate = datetime.datetime.strptime(singledate, "%Y-%m-%d") mm = startDate.month dd = startDate.day startDate = datetime.date(int(yyyy), int(mm), int(dd)) nDays = 1 endDate = startDate + datetime.timedelta(days=int(nDays)) if startDate >= end or startDate < start: - raise QgsProcessingException('Single date has to be within the modelled days OR the same as the last modelled day.') + raise QgsProcessingException( + "Single date has to be within the modelled days OR the same as the last modelled day." + ) else: - feedback.setProgressText('Single day and following night to examine: ' + startDate.strftime('%d %b')) + feedback.setProgressText( + "Single day and following night to examine: " + + startDate.strftime("%d %b") + ) else: uwgDict = read_uwg_file(uwgIn, fileList[0][:-4]) - mm = uwgDict['Month'] - dd = uwgDict['Day'] - nDays = uwgDict['nDay'] + mm = uwgDict["Month"] + dd = uwgDict["Day"] + nDays = uwgDict["nDay"] startDate = datetime.date(int(yyyy), int(mm), int(dd)) endDate = startDate + datetime.timedelta(days=int(nDays)) - feedback.setProgressText('Dates to be analyzed: ' + startDate.strftime('%d %b') + ' to ' + endDate.strftime('%d %b')) + feedback.setProgressText( + "Dates to be analyzed: " + + startDate.strftime("%d %b") + + " to " + + endDate.strftime("%d %b") + ) # poly = inputPolygonlayer poly_field = idField vlayer = inputPolygonlayer # prov = vlayer.dataProvider() - path=vlayer.dataProvider().dataSourceUri() + path = vlayer.dataProvider().dataSourceUri() # polygonpath = path [:path.rfind('|')] # work around. Probably other solution exists - if path.rfind('|') > 0: - polygonpath = path [:path.rfind('|')] # work around. Probably other solution exists + if path.rfind("|") > 0: + polygonpath = path[ + : path.rfind("|") + ] # work around. Probably other solution exists else: polygonpath = path @@ -198,10 +283,9 @@ def processAlgorithm(self, parameters, context, feedback): statresult = [0] idvec = [0] - startD = int(startDate.strftime('%j')) - endD = int(endDate.strftime('%j')) - - + startD = int(startDate.strftime("%j")) + endD = int(endDate.strftime("%j")) + # for i in range(0, self.idgrid.shape[0]): # loop over vector grid instead index = 1 nGrids = vlayer.featureCount() @@ -216,7 +300,10 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText("Processing grid: " + str(gid)) - datawhole = np.genfromtxt(uwgOut + '/' + prefix + '_' + gid + '_UMEP_UWG.txt', skip_header=1) + datawhole = np.genfromtxt( + uwgOut + "/" + prefix + "_" + gid + "_UMEP_UWG.txt", + skip_header=1, + ) # cut UWG data start = np.min(np.where(datawhole[:, 1] == startD)) @@ -224,9 +311,13 @@ def processAlgorithm(self, parameters, context, feedback): ending = np.max(np.where(datawhole[:, 1] == endD - 1)) else: ending = np.min(np.where(datawhole[:, 1] == endD)) - data1 = datawhole[start:int(ending + 12), :] # + 12 to include whole final night + data1 = datawhole[ + start : int(ending + 12), : + ] # + 12 to include whole final night - data1 = data1[np.where(data1[:, 14] < 1.), :] # include only nighttime. 14 is position for global radiation + data1 = data1[ + np.where(data1[:, 14] < 1.0), : + ] # include only nighttime. 14 is position for global radiation data1 = data1[0][:] # cut ref data @@ -234,11 +325,15 @@ def processAlgorithm(self, parameters, context, feedback): ending = np.max(np.where(dataref[:, 1] == endD - 1)) else: ending = np.min(np.where(dataref[:, 1] == endD)) - data2 = dataref[start:int(ending + 12), :] # + 12 to include whole final night + data2 = dataref[ + start : int(ending + 12), : + ] # + 12 to include whole final night - data2 = data2[np.where(data2[:, 14] < 1.), :] # include only nighttime. 14 is position for global radiation + data2 = data2[ + np.where(data2[:, 14] < 1.0), : + ] # include only nighttime. 14 is position for global radiation data2 = data2[0][:] - + # if dayTypeStr == '1': # data1 = data1[np.where(data1[:, altpos] < 90.), :] # data1 = data1[0][:] @@ -246,25 +341,25 @@ def processAlgorithm(self, parameters, context, feedback): # data1 = data1[np.where(data1[:, altpos] > 90.), :] # data1 = data1[0][:] - vardatauwg = data1[:, 11] # 11 is temperature column - vardataref = data2[:, 11] + vardatauwg = data1[:, 11] # 11 is temperature column + vardataref = data2[:, 11] vardata = vardatauwg - vardataref - if statTypeStr == '0': + if statTypeStr == "0": statresult = np.nanmean(vardata) - header = 'mean' - if statTypeStr == '1': + header = "mean" + if statTypeStr == "1": statresult = np.nanmax(vardata) - header = 'max' - if statTypeStr == '2': + header = "max" + if statTypeStr == "2": statresult = np.nanpercentile(vardata, 50) - header = 'median' - if statTypeStr == '3': + header = "median" + if statTypeStr == "3": statresult = np.nanpercentile(vardata, 75) - header = '75precentile' - if statTypeStr == '4': + header = "75precentile" + if statTypeStr == "4": statresult = np.nanpercentile(vardata, 95) - header = '95precentile' + header = "95precentile" statvectemp = np.vstack((statvectemp, statresult)) idvec = np.vstack((idvec, int(gid))) @@ -279,7 +374,11 @@ def processAlgorithm(self, parameters, context, feedback): if irreg: resx = pixelsize else: - for f in vlayer.getFeatures(): # Taking first polygon. Could probably be done nicer + for ( + f + ) in ( + vlayer.getFeatures() + ): # Taking first polygon. Could probably be done nicer # geom = f.geometry().asPolygon() geom = f.geometry().asMultiPolygon() break @@ -287,19 +386,30 @@ def processAlgorithm(self, parameters, context, feedback): resy = np.abs(geom[0][0][0][1] - geom[0][0][2][1]) # y if not resx == resy: - raise QgsProcessingException("Polygons not squared in current CRS") + raise QgsProcessingException( + "Polygons not squared in current CRS" + ) - if os.path.isfile(self.plugin_dir + '/tempgrid.tif'): # response to issue 103 + if os.path.isfile( + self.plugin_dir + "/tempgrid.tif" + ): # response to issue 103 try: - shutil.rmtree(self.plugin_dir + '/tempgrid.tif') + shutil.rmtree(self.plugin_dir + "/tempgrid.tif") except OSError: - os.remove(self.plugin_dir + '/tempgrid.tif') - + os.remove(self.plugin_dir + "/tempgrid.tif") + extent = vlayer.extent() crs = vlayer.crs().toWkt() - self.rasterize(polygonpath, str(self.plugin_dir + '/tempgrid.tif'), str(poly_field[0]), resx, crs, extent) - - dataset = gdal.Open(self.plugin_dir + '/tempgrid.tif') + self.rasterize( + polygonpath, + str(self.plugin_dir + "/tempgrid.tif"), + str(poly_field[0]), + resx, + crs, + extent, + ) + + dataset = gdal.Open(self.plugin_dir + "/tempgrid.tif") idgrid_array = dataset.ReadAsArray().astype(float) gridout = np.zeros((idgrid_array.shape[0], idgrid_array.shape[1])) @@ -314,7 +424,17 @@ def processAlgorithm(self, parameters, context, feedback): return {self.UWG_GRID_OUT: outputStat} - def rasterize(self, src, dst, attribute, resolution, crs, extent, all_touch=False, na=-9999): + def rasterize( + self, + src, + dst, + attribute, + resolution, + crs, + extent, + all_touch=False, + na=-9999, + ): # Open shapefile, retrieve the layer # print(src) @@ -328,10 +448,12 @@ def rasterize(self, src, dst, attribute, resolution, crs, extent, all_touch=Fals ymin = extent.yMinimum() # Create the target raster layer - cols = int((xmax - xmin)/resolution) + cols = int((xmax - xmin) / resolution) # rows = int((ymax - ymin)/resolution) + 1 - rows = int((ymax - ymin)/resolution) # issue 164 - trgt = gdal.GetDriverByName("GTiff").Create(dst, cols, rows, 1, GDT_Float32) + rows = int((ymax - ymin) / resolution) # issue 164 + trgt = gdal.GetDriverByName("GTiff").Create( + dst, cols, rows, 1, GDT_Float32 + ) trgt.SetGeoTransform((xmin, resolution, 0, ymax, 0, -resolution)) # Add crs @@ -354,15 +476,16 @@ def rasterize(self, src, dst, attribute, resolution, crs, extent, all_touch=Fals # Close target an source rasters del trgt - del src_data + del src_data - def addattributes(self, vlayer, matdata, header): current_index_length = len(vlayer.dataProvider().attributeIndexes()) caps = vlayer.dataProvider().capabilities() if caps & QgsVectorDataProvider.Capability.AddAttributes: - vlayer.dataProvider().addAttributes([QgsField(header, QVariant.Double)]) + vlayer.dataProvider().addAttributes( + [QgsField(header, QVariant.Double)] + ) attr_dict = {} for y in range(0, matdata.shape[0]): attr_dict.clear() @@ -372,10 +495,12 @@ def addattributes(self, vlayer, matdata, header): vlayer.updateFields() else: - raise QgsProcessingException("Vector Layer does not support adding attributes") - + raise QgsProcessingException( + "Vector Layer does not support adding attributes" + ) + def name(self): - return 'Urban Heat Island: UWG Analyzer' + return "Urban Heat Island: UWG Analyzer" def displayName(self): return self.tr(self.name()) @@ -384,29 +509,34 @@ def group(self): return self.tr(self.groupId()) def groupId(self): - return 'Post-Processor' + return "Post-Processor" def shortHelpString(self): - return self.tr('The UWG Analyzer plugin can be used to make basic grid analysis of model results generated by the Urban Weather Generator.
' - '\n' - '--------------\n' - 'Full manual available via the Help-button.') + return self.tr( + "The UWG Analyzer plugin can be used to make basic grid analysis of model results generated by the Urban Weather Generator.
" + "\n" + "--------------\n" + "Full manual available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/post_processor/Urban%20Heat%20Island%20UWG%20Analyser.html" return url def tr(self, string): - return QCoreApplication.translate('Post-Processing', string) + return QCoreApplication.translate("Post-Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/icon_uwg.png") return icon def createInstance(self): return ProcessingUWGAnalyzerAlgorithm() + class DateWidgetStart(WidgetWrapper): def createWidget(self): self._combo = QDateEdit() @@ -421,6 +551,7 @@ def value(self): date_chosen = self._combo.dateTime() return date_chosen.toString(Qt.DateFormat.ISODate) + class DateWidgetEnd(WidgetWrapper): def createWidget(self): self._combo = QDateEdit() diff --git a/preprocessor/copernicusera5_algorithm.py b/preprocessor/copernicusera5_algorithm.py index 6011856..09bbffc 100644 --- a/preprocessor/copernicusera5_algorithm.py +++ b/preprocessor/copernicusera5_algorithm.py @@ -22,24 +22,27 @@ ***************************************************************************/ """ -__author__ = 'Fredrik Lindberg' -__date__ = '2020-04-02' -__copyright__ = '(C) 2020 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2020-04-02" +__copyright__ = "(C) 2020 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QDate, Qt, QVariant -from qgis.core import (QgsProcessingAlgorithm, - QgsProcessingParameterPoint, - QgsProcessingParameterString, - QgsProcessingParameterNumber, - QgsProcessingParameterFolderDestination, - QgsProcessingParameterCrs, - QgsProcessingException) +from qgis.core import ( + QgsProcessingAlgorithm, + QgsProcessingParameterPoint, + QgsProcessingParameterString, + QgsProcessingParameterNumber, + QgsProcessingParameterFolderDestination, + QgsProcessingParameterCrs, + QgsProcessingException, +) from processing.gui.wrappers import WidgetWrapper + # from qgis.PyQt.QtWidgets import QDateEdit # from processing.gui.wrappers import WidgetWrapper @@ -73,52 +76,78 @@ class ProcessingCopernicusERA5Algorithm(QgsProcessingAlgorithm): This algorithm is a processing version of Image Morphometric Calculator Point """ - INPUT_POINT = 'INPUT_POINT' - CRS = 'CRS' - DATEINISTART = 'DATEINISTART' - DATEINIEND = 'DATEINIEND' - OUTPUT_DIR = 'OUTPUT_DIR' - DIAG_HEIGHT = 'DIAG_HEIGHT' + INPUT_POINT = "INPUT_POINT" + CRS = "CRS" + DATEINISTART = "DATEINISTART" + DATEINIEND = "DATEINIEND" + OUTPUT_DIR = "OUTPUT_DIR" + DIAG_HEIGHT = "DIAG_HEIGHT" - def initAlgorithm(self, config): - self.addParameter(QgsProcessingParameterPoint(self.INPUT_POINT, - self.tr('Point of interest'))) - self.addParameter(QgsProcessingParameterCrs(self.CRS, - self.tr('Coordinate reference system for point of interest'), - 'ProjectCrs')) - paramS = QgsProcessingParameterString(self.DATEINISTART, 'Start date') - paramS.setMetadata({'widget_wrapper': {'class': DateWidgetStart}}) + self.addParameter( + QgsProcessingParameterPoint( + self.INPUT_POINT, self.tr("Point of interest") + ) + ) + self.addParameter( + QgsProcessingParameterCrs( + self.CRS, + self.tr("Coordinate reference system for point of interest"), + "ProjectCrs", + ) + ) + paramS = QgsProcessingParameterString(self.DATEINISTART, "Start date") + paramS.setMetadata({"widget_wrapper": {"class": DateWidgetStart}}) self.addParameter(paramS) - paramE = QgsProcessingParameterString(self.DATEINIEND, 'End date') - paramE.setMetadata({'widget_wrapper': {'class': DateWidgetEnd}}) + paramE = QgsProcessingParameterString(self.DATEINIEND, "End date") + paramE.setMetadata({"widget_wrapper": {"class": DateWidgetEnd}}) self.addParameter(paramE) - self.addParameter(QgsProcessingParameterNumber(self.DIAG_HEIGHT, - self.tr("Height above ground level to diagnose forcing variables (m)"), - QgsProcessingParameterNumber.Type.Double, - QVariant(100), minValue=2, maxValue=500)) - self.addParameter(QgsProcessingParameterFolderDestination(self.OUTPUT_DIR, - self.tr('Output folder'))) - + self.addParameter( + QgsProcessingParameterNumber( + self.DIAG_HEIGHT, + self.tr( + "Height above ground level to diagnose forcing variables (m)" + ), + QgsProcessingParameterNumber.Type.Double, + QVariant(100), + minValue=2, + maxValue=500, + ) + ) + self.addParameter( + QgsProcessingParameterFolderDestination( + self.OUTPUT_DIR, self.tr("Output folder") + ) + ) def processAlgorithm(self, parameters, context, feedback): try: import supy as sp from supy import __version__ as ver_supy except: - raise QgsProcessingException('This plugin requires the supy package ' - 'to be installed OR upgraded. Please consult the FAQ in the manual ' - 'for further information on how to install missing python packages.') + raise QgsProcessingException( + "This plugin requires the supy package " + "to be installed OR upgraded. Please consult the FAQ in the manual " + "for further information on how to install missing python packages." + ) # InputParameters - inputPoint = self.parameterAsPoint(parameters, self.INPUT_POINT, context) + inputPoint = self.parameterAsPoint( + parameters, self.INPUT_POINT, context + ) inputCRS = self.parameterAsCrs(parameters, self.CRS, context) - startDate = self.parameterAsString(parameters, self.DATEINISTART, context) + startDate = self.parameterAsString( + parameters, self.DATEINISTART, context + ) endDate = self.parameterAsString(parameters, self.DATEINIEND, context) - outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) - diagHeight = self.parameterAsDouble(parameters, self.DIAG_HEIGHT, context) - - if parameters['OUTPUT_DIR'] == 'TEMPORARY_OUTPUT': + outputDir = self.parameterAsString( + parameters, self.OUTPUT_DIR, context + ) + diagHeight = self.parameterAsDouble( + parameters, self.DIAG_HEIGHT, context + ) + + if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): os.mkdir(outputDir) @@ -144,46 +173,56 @@ def processAlgorithm(self, parameters, context, feedback): new_cs = osr.SpatialReference() new_cs.ImportFromWkt(wgs84_wkt) - + transform = osr.CoordinateTransformation(old_cs, new_cs) latlon = ogr.CreateGeometryFromWkt( - 'POINT (' + str(x) + ' ' + str(y) + ')') + "POINT (" + str(x) + " " + str(y) + ")" + ) latlon.Transform(transform) gdalver = float(gdal.__version__[0]) - if gdalver == 3.: + if gdalver == 3.0: lon = latlon.GetY() lat = latlon.GetX() else: lat = latlon.GetY() lon = latlon.GetX() - feedback.setProgressText('SuPy version: ' + ver_supy) - feedback.setProgressText('INPUT PARAMETERS:') - feedback.setProgressText('lat = ' + str(lat)) - feedback.setProgressText('lon = ' + str(lon)) - feedback.setProgressText('Start = ' + str(startDate)) - feedback.setProgressText('End = ' + str(endDate)) - feedback.setProgressText('Diagnostic height = ' + str(diagHeight)) - feedback.setProgressText('Output folder = ' + str(outputDir)) + feedback.setProgressText("SuPy version: " + ver_supy) + feedback.setProgressText("INPUT PARAMETERS:") + feedback.setProgressText("lat = " + str(lat)) + feedback.setProgressText("lon = " + str(lon)) + feedback.setProgressText("Start = " + str(startDate)) + feedback.setProgressText("End = " + str(endDate)) + feedback.setProgressText("Diagnostic height = " + str(diagHeight)) + feedback.setProgressText("Output folder = " + str(outputDir)) if startDate >= endDate: - raise QgsProcessingException('Start date is greater or equal than end date') - - logger_sp = logging.getLogger('SuPy') + raise QgsProcessingException( + "Start date is greater or equal than end date" + ) + + logger_sp = logging.getLogger("SuPy") logger_sp.disabled = True - feedback.setProgressText('Downloading and processing in progress...') + feedback.setProgressText("Downloading and processing in progress...") - sp.util.gen_forcing_era5(lat, lon, startDate, endDate, hgt_agl_diag=diagHeight, dir_save=Path(outputDir)) + sp.util.gen_forcing_era5( + lat, + lon, + startDate, + endDate, + hgt_agl_diag=diagHeight, + dir_save=Path(outputDir), + ) results = {self.OUTPUT_DIR: outputDir} return results - + def name(self): - return 'Meteorological Data: Download data (ERA5)' + return "Meteorological Data: Download data (ERA5)" def displayName(self): return self.tr(self.name()) @@ -192,36 +231,40 @@ def group(self): return self.tr(self.groupId()) def groupId(self): - return 'Pre-Processor' + return "Pre-Processor" def shortHelpString(self): - return self.tr('Basic meteorological variables are required for most applications in the UMEP processor. ' \ - 'If observed data are not available for a particular location, hourly data can be retrieved from the global ' \ - 'the Coopernicus programme and thier Climate Data Store. This plugin allows climate reanalysis data to be ' \ - 'extracted for a specific location and period of interest (1940-today), and automatiecally transformed into formatted ' \ - 'forcing files suitable for models within UMEP.' - '\n' - 'Remember to adjust the diagnistic height depending on application. For thermal comfort modelling, set a height close ' \ - 'to ground level (e.g. 2m) and for urban energy balance modelling, set it to 3 times the height of the roughness elements ' - '(e.g. buildings and vegetataion).' - '\n' - 'If your computer is not configured for downloading data from the Climate Data Store, follow the instructions here: ' - 'https://cds.climate.copernicus.eu/how-to-api.' - '\n' - 'Another tip is to visit https://www.shinyweatherdata.com/ where ERA5 data also can be downloaded.' - '\n' - '---------------\n' - 'Full manual available via the Help-button.') + return self.tr( + "Basic meteorological variables are required for most applications in the UMEP processor. " + "If observed data are not available for a particular location, hourly data can be retrieved from the global " + "the Coopernicus programme and thier Climate Data Store. This plugin allows climate reanalysis data to be " + "extracted for a specific location and period of interest (1940-today), and automatiecally transformed into formatted " + "forcing files suitable for models within UMEP." + "\n" + "Remember to adjust the diagnistic height depending on application. For thermal comfort modelling, set a height close " + "to ground level (e.g. 2m) and for urban energy balance modelling, set it to 3 times the height of the roughness elements " + "(e.g. buildings and vegetataion)." + "\n" + "If your computer is not configured for downloading data from the Climate Data Store, follow the instructions here: " + "https://cds.climate.copernicus.eu/how-to-api." + "\n" + "Another tip is to visit https://www.shinyweatherdata.com/ where ERA5 data also can be downloaded." + "\n" + "---------------\n" + "Full manual available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/pre-processor/Meteorological%20Data%20Download%20data%20(ERA5).html" return url def tr(self, string): - return QCoreApplication.translate('Processing', string) + return QCoreApplication.translate("Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/watch.png") return icon @@ -243,6 +286,7 @@ def value(self): date_chosen = self._combo.dateTime() return date_chosen.toString(Qt.DateFormat.ISODate) + class DateWidgetEnd(WidgetWrapper): def createWidget(self): self._combo = QDateEdit() @@ -255,4 +299,4 @@ def createWidget(self): def value(self): date_chosen = self._combo.dateTime() - return date_chosen.toString(Qt.DateFormat.ISODate) \ No newline at end of file + return date_chosen.toString(Qt.DateFormat.ISODate) diff --git a/preprocessor/dsm_generator_algorithm.py b/preprocessor/dsm_generator_algorithm.py index c3d3bea..64abf03 100644 --- a/preprocessor/dsm_generator_algorithm.py +++ b/preprocessor/dsm_generator_algorithm.py @@ -22,37 +22,42 @@ ***************************************************************************/ """ -__author__ = 'Fredrik Lindberg' -__date__ = '2020-04-02' -__copyright__ = '(C) 2020 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2020-04-02" +__copyright__ = "(C) 2020 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QVariant, QFileInfo -from qgis.core import (QgsProcessing, - QgsProcessingAlgorithm, - # QgsProcessingParameterString, - QgsProcessingParameterBoolean, - QgsProcessingParameterNumber, - QgsProcessingParameterRasterDestination, - QgsProcessingParameterVectorDestination, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterExtent, - QgsProcessingParameterDistance, - QgsProcessingParameterRasterLayer, - QgsProcessingParameterField, - QgsProcessingException) +from qgis.core import ( + QgsProcessing, + QgsProcessingAlgorithm, + # QgsProcessingParameterString, + QgsProcessingParameterBoolean, + QgsProcessingParameterNumber, + QgsProcessingParameterRasterDestination, + QgsProcessingParameterVectorDestination, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterExtent, + QgsProcessingParameterDistance, + QgsProcessingParameterRasterLayer, + QgsProcessingParameterField, + QgsProcessingException, +) + # from processing.gui.wrappers import WidgetWrapper -from qgis.core import (QgsVectorLayer, - QgsField, - QgsExpression, - QgsExpressionContext, - QgsExpressionContextScope, - QgsVectorFileWriter, - QgsRasterLayer, - QgsCoordinateTransform) +from qgis.core import ( + QgsVectorLayer, + QgsField, + QgsExpression, + QgsExpressionContext, + QgsExpressionContextScope, + QgsVectorFileWriter, + QgsRasterLayer, + QgsCoordinateTransform, +) from qgis.analysis import QgsZonalStatistics from qgis.PyQt.QtGui import QIcon from osgeo import gdal, osr, ogr @@ -67,92 +72,151 @@ import requests from ..functions.URock.DataUtil import safe + class ProcessingDSMGeneratorAlgorithm(QgsProcessingAlgorithm): """ This algorithm is a processing version of DSM Generator """ - INPUT_DEM = 'INPUT_DEM' - INPUT_POLYGONLAYER = 'INPUT_POLYGONLAYER' - INPUT_FIELD = 'INPUT_FIELD' + INPUT_DEM = "INPUT_DEM" + INPUT_POLYGONLAYER = "INPUT_POLYGONLAYER" + INPUT_FIELD = "INPUT_FIELD" + + USE_OSM = "USE_OSM" + SAVE_OSM = "SAVE_OSM" + BUILDING_LEVEL = "BUILDING_LEVEL" - USE_OSM = 'USE_OSM' - SAVE_OSM = 'SAVE_OSM' - BUILDING_LEVEL = 'BUILDING_LEVEL' + EXTENT = "EXTENT" + PIXEL_RESOLUTION = "PIXEL_RESOLUTION" - EXTENT = 'EXTENT' - PIXEL_RESOLUTION = 'PIXEL_RESOLUTION' + OUTPUT_DSM = "OUTPUT_DSM" + OUTPUT_POLYGON = "OUTPUT_POLYGON" - OUTPUT_DSM = 'OUTPUT_DSM' - OUTPUT_POLYGON = 'OUTPUT_POLYGON' - def initAlgorithm(self, config): - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_DEM, - self.tr('Input Digital Elevation Model:'), None, False)) - - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_POLYGONLAYER, - self.tr('Polygon with building height:'), [QgsProcessing.SourceType.TypeVectorPolygon], optional=True)) - - self.addParameter(QgsProcessingParameterField(self.INPUT_FIELD, - self.tr('Field with building height'),'', self.INPUT_POLYGONLAYER, QgsProcessingParameterField.DataType.Numeric, optional=True)) - - self.addParameter(QgsProcessingParameterBoolean(self.USE_OSM, - self.tr("Use Open Street Map:"), defaultValue=False)) - - self.addParameter(QgsProcessingParameterNumber(self.BUILDING_LEVEL, - self.tr('Building level height (meter)'), - QgsProcessingParameterNumber.Type.Double, - QVariant(3.1), False, minValue=0)) - - self.addParameter(QgsProcessingParameterExtent(self.EXTENT, - self.tr('Extent'))) - - self.addParameter(QgsProcessingParameterDistance(self.PIXEL_RESOLUTION, - self.tr('Pixel resolution'), - defaultValue = 2.0, - parentParameterName='INPUT_DEM')) - - self.addParameter(QgsProcessingParameterRasterDestination(self.OUTPUT_DSM, - self.tr('Output raster Digital Surface Model (DSM)'), - None, False)) - - self.addParameter(QgsProcessingParameterVectorDestination( - self.OUTPUT_POLYGON, - self.tr("Output shapefile with Open Street Map data"), - optional=True, - createByDefault=False - ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_DEM, + self.tr("Input Digital Elevation Model:"), + None, + False, + ) + ) + + self.addParameter( + QgsProcessingParameterFeatureSource( + self.INPUT_POLYGONLAYER, + self.tr("Polygon with building height:"), + [QgsProcessing.SourceType.TypeVectorPolygon], + optional=True, + ) + ) + + self.addParameter( + QgsProcessingParameterField( + self.INPUT_FIELD, + self.tr("Field with building height"), + "", + self.INPUT_POLYGONLAYER, + QgsProcessingParameterField.DataType.Numeric, + optional=True, + ) + ) + + self.addParameter( + QgsProcessingParameterBoolean( + self.USE_OSM, + self.tr("Use Open Street Map:"), + defaultValue=False, + ) + ) + + self.addParameter( + QgsProcessingParameterNumber( + self.BUILDING_LEVEL, + self.tr("Building level height (meter)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(3.1), + False, + minValue=0, + ) + ) + + self.addParameter( + QgsProcessingParameterExtent(self.EXTENT, self.tr("Extent")) + ) + + self.addParameter( + QgsProcessingParameterDistance( + self.PIXEL_RESOLUTION, + self.tr("Pixel resolution"), + defaultValue=2.0, + parentParameterName="INPUT_DEM", + ) + ) + + self.addParameter( + QgsProcessingParameterRasterDestination( + self.OUTPUT_DSM, + self.tr("Output raster Digital Surface Model (DSM)"), + None, + False, ) + ) + + self.addParameter( + QgsProcessingParameterVectorDestination( + self.OUTPUT_POLYGON, + self.tr("Output shapefile with Open Street Map data"), + optional=True, + createByDefault=False, + ) + ) def processAlgorithm(self, parameters, context, feedback): # Input data - demlayer = self.parameterAsRasterLayer(parameters, self.INPUT_DEM, context) - shapelayer = self.parameterAsVectorLayer(parameters, self.INPUT_POLYGONLAYER, context) - heightField = self.parameterAsFields(parameters, self.INPUT_FIELD, context) + demlayer = self.parameterAsRasterLayer( + parameters, self.INPUT_DEM, context + ) + shapelayer = self.parameterAsVectorLayer( + parameters, self.INPUT_POLYGONLAYER, context + ) + heightField = self.parameterAsFields( + parameters, self.INPUT_FIELD, context + ) # OSM switches useOsm = self.parameterAsBool(parameters, self.USE_OSM, context) - buildingLevelHeight = self.parameterAsDouble(parameters, self.BUILDING_LEVEL, context) + buildingLevelHeight = self.parameterAsDouble( + parameters, self.BUILDING_LEVEL, context + ) # Output settings inputExtent = self.parameterAsExtent(parameters, self.EXTENT, context) inputCrs = self.parameterAsExtentCrs(parameters, self.EXTENT, context) - pixelResolution = self.parameterAsDouble(parameters, self.PIXEL_RESOLUTION, context) + pixelResolution = self.parameterAsDouble( + parameters, self.PIXEL_RESOLUTION, context + ) # Output data - outputDSM = self.parameterAsOutputLayer(parameters, self.OUTPUT_DSM, context) - outputShape = self.parameterAsOutputLayer(parameters, self.OUTPUT_POLYGON, context) - - if parameters['OUTPUT_DSM'] == 'TEMPORARY_OUTPUT': + outputDSM = self.parameterAsOutputLayer( + parameters, self.OUTPUT_DSM, context + ) + outputShape = self.parameterAsOutputLayer( + parameters, self.OUTPUT_POLYGON, context + ) + + if parameters["OUTPUT_DSM"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDSM)): os.mkdir(outputDSM) - feedback.setProgressText('Checking extents and coordinate systems') + feedback.setProgressText("Checking extents and coordinate systems") if inputCrs.toWkt() != demlayer.crs().toWkt(): - feedback.pushInfo('Coordinate system of Map canvas and input DEM layer differs!') - #raise QgsProcessingException('Coordinate system of Map canvas and input DEM layer differs!') + feedback.pushInfo( + "Coordinate system of Map canvas and input DEM layer differs!" + ) + # raise QgsProcessingException('Coordinate system of Map canvas and input DEM layer differs!') # Input extent maxy = inputExtent.yMaximum() @@ -178,30 +242,65 @@ def processAlgorithm(self, parameters, context, feedback): if inputCrs.toWkt() != demlayer.crs().toWkt(): gdalver = float(gdal.__version__[0]) - if gdalver == 3.: - minext = ogr.CreateGeometryFromWkt('POINT (' + str(minx) + ' ' + str(miny) + ')') + if gdalver == 3.0: + minext = ogr.CreateGeometryFromWkt( + "POINT (" + str(minx) + " " + str(miny) + ")" + ) minext.Transform(transformExtent) - maxext = ogr.CreateGeometryFromWkt('POINT (' + str(maxx) + ' ' + str(maxy) + ')') + maxext = ogr.CreateGeometryFromWkt( + "POINT (" + str(maxx) + " " + str(maxy) + ")" + ) maxext.Transform(transformExtent) - extent_difference_minx = minext.GetX() - dem_minx # If smaller than zero = warning #changed to gdal 3 - extent_difference_miny = minext.GetY() - dem_miny # If smaller than zero = warning #changed to gdal 3 - extent_difference_maxx = maxext.GetX() - dem_maxx # If larger than zero = warning #changed to gdal 3 - extent_difference_maxy = maxext.GetY() - dem_maxy # If larger than zero = warning #changed to gdal 3 + extent_difference_minx = ( + minext.GetX() - dem_minx + ) # If smaller than zero = warning #changed to gdal 3 + extent_difference_miny = ( + minext.GetY() - dem_miny + ) # If smaller than zero = warning #changed to gdal 3 + extent_difference_maxx = ( + maxext.GetX() - dem_maxx + ) # If larger than zero = warning #changed to gdal 3 + extent_difference_maxy = ( + maxext.GetY() - dem_maxy + ) # If larger than zero = warning #changed to gdal 3 else: input_xymin = transformExtent.TransformPoint(minx, miny) input_xymax = transformExtent.TransformPoint(maxx, maxy) - extent_difference_minx = input_xymin[0] - dem_minx # If smaller than zero = warning #changed to gdal 2 - extent_difference_miny = input_xymin[1] - dem_miny # If smaller than zero = warning #changed to gdal 2 - extent_difference_maxx = input_xymax[0] - dem_maxx # If larger than zero = warning #changed to gdal 2 - extent_difference_maxy = input_xymax[1] - dem_maxy # If larger than zero = warning #changed to gdal 2 + extent_difference_minx = ( + input_xymin[0] - dem_minx + ) # If smaller than zero = warning #changed to gdal 2 + extent_difference_miny = ( + input_xymin[1] - dem_miny + ) # If smaller than zero = warning #changed to gdal 2 + extent_difference_maxx = ( + input_xymax[0] - dem_maxx + ) # If larger than zero = warning #changed to gdal 2 + extent_difference_maxy = ( + input_xymax[1] - dem_maxy + ) # If larger than zero = warning #changed to gdal 2 else: - extent_difference_minx = minx - dem_minx # If smaller than zero = warning #changed to gdal 3 - extent_difference_miny = miny - dem_miny # If smaller than zero = warning #changed to gdal 3 - extent_difference_maxx = maxx - dem_maxx # If larger than zero = warning #changed to gdal 3 - extent_difference_maxy = maxy - dem_maxy # If larger than zero = warning #changed to gdal 3 - - if extent_difference_minx < -0.1 or extent_difference_miny < -0.1 or extent_difference_maxx > 0.1 or extent_difference_maxy > 0.1: - raise QgsProcessingException('Warning! Extent of map canvas is larger than raster extent. Change to an extent equal to or smaller than the raster extent.') + extent_difference_minx = ( + minx - dem_minx + ) # If smaller than zero = warning #changed to gdal 3 + extent_difference_miny = ( + miny - dem_miny + ) # If smaller than zero = warning #changed to gdal 3 + extent_difference_maxx = ( + maxx - dem_maxx + ) # If larger than zero = warning #changed to gdal 3 + extent_difference_maxy = ( + maxy - dem_maxy + ) # If larger than zero = warning #changed to gdal 3 + + if ( + extent_difference_minx < -0.1 + or extent_difference_miny < -0.1 + or extent_difference_maxx > 0.1 + or extent_difference_maxy > 0.1 + ): + raise QgsProcessingException( + "Warning! Extent of map canvas is larger than raster extent. Change to an extent equal to or smaller than the raster extent." + ) provider = demlayer.dataProvider() filepath_dem = str(provider.dataSourceUri()) @@ -210,26 +309,53 @@ def processAlgorithm(self, parameters, context, feedback): dem_crs = osr.SpatialReference() dem_crs.ImportFromWkt(gdal_dem.GetProjection()) - dem_unit = dem_crs.GetAttrValue('UNIT') - - possible_units = ['metre', 'Metre', 'metres', 'Metres', 'meter', 'Meter', 'meters', 'Meters', 'm', 'ft', 'US survey foot', 'feet', 'Feet', 'foot', 'Foot', 'ftUS', 'International foot'] # Possible units + dem_unit = dem_crs.GetAttrValue("UNIT") + + possible_units = [ + "metre", + "Metre", + "metres", + "Metres", + "meter", + "Meter", + "meters", + "Meters", + "m", + "ft", + "US survey foot", + "feet", + "Feet", + "foot", + "Foot", + "ftUS", + "International foot", + ] # Possible units if not dem_unit in possible_units: - raise QgsProcessingException('Error! Raster projection is not in meters or feet. Please reproject.') + raise QgsProcessingException( + "Error! Raster projection is not in meters or feet. Please reproject." + ) if shapelayer is None and useOsm is False: - raise QgsProcessingException('Error! No valid building height layer is selected.') + raise QgsProcessingException( + "Error! No valid building height layer is selected." + ) elif shapelayer: idx = shapelayer.fields().indexFromName(heightField[0]) if idx == -1: - raise QgsProcessingException('Error! An attribute with unique fields must be selected.') + raise QgsProcessingException( + "Error! An attribute with unique fields must be selected." + ) ### main code ### - feedback.setProgressText('Initiating algorithm') + feedback.setProgressText("Initiating algorithm") feedback.setProgress(10) root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - temp_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + '/temp/' + temp_dir = ( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + "/temp/" + ) if useOsm: # Coordinate system of input DEM layer used for creation of OSM shapefile @@ -256,13 +382,17 @@ def processAlgorithm(self, parameters, context, feedback): # Transform to convert from input extent coordinate system to Open Street Map coordinate system transform = osr.CoordinateTransformation(input_crs, osm_crs) - + gdalver = float(gdal.__version__[0]) - if gdalver == 3.: + if gdalver == 3.0: # New code Fredrik 20200625 - lonlatmin = ogr.CreateGeometryFromWkt('POINT (' + str(minx) + ' ' + str(miny) + ')') + lonlatmin = ogr.CreateGeometryFromWkt( + "POINT (" + str(minx) + " " + str(miny) + ")" + ) lonlatmin.Transform(transform) - lonlatmax = ogr.CreateGeometryFromWkt('POINT (' + str(maxx) + ' ' + str(maxy) + ')') + lonlatmax = ogr.CreateGeometryFromWkt( + "POINT (" + str(maxx) + " " + str(maxy) + ")" + ) lonlatmax.Transform(transform) lonmin = lonlatmin.GetY() lonmax = lonlatmax.GetY() @@ -279,95 +409,149 @@ def processAlgorithm(self, parameters, context, feedback): latmax = lonlatmax[1] # Make data queries to overpass-api - urlStr = 'http://overpass-api.de/api/map?bbox=' + str(lonmin) + ',' + str(latmin) + ',' + str(lonmax) + ',' + str(latmax) + urlStr = ( + "http://overpass-api.de/api/map?bbox=" + + str(lonmin) + + "," + + str(latmin) + + "," + + str(lonmax) + + "," + + str(latmax) + ) res = requests.get(urlStr, timeout=15) osmXml = res.text # with urllib.request.urlopen(urlStr) as response: # osmXml = response.read() # osmXml = osmXml.decode('UTF-8') - osmPath = temp_dir + 'OSM_building.osm' - osmFile = open(osmPath, 'w', encoding='utf-8') + osmPath = temp_dir + "OSM_building.osm" + osmFile = open(osmPath, "w", encoding="utf-8") osmFile.write(osmXml) - feedback.setProgressText('Downloading OSM data from ' + urlStr) + feedback.setProgressText("Downloading OSM data from " + urlStr) if os.fstat(osmFile.fileno()).st_size < 1: - urlStr = 'http://api.openstreetmap.org/api/0.6/map?bbox=' + str(lonmin) + ',' + str(latmin) + ',' + str(lonmax) + ',' + str(latmax) + urlStr = ( + "http://api.openstreetmap.org/api/0.6/map?bbox=" + + str(lonmin) + + "," + + str(latmin) + + "," + + str(lonmax) + + "," + + str(latmax) + ) res = requests.get(urlStr, timeout=15) osmXml = res.text # with urllib.request.urlopen(urlStr) as response: # osmXml = response.read() # osmXml = osmXml.decode('UTF-8') - osmPath = temp_dir + 'OSM_building.osm' - osmFile = open(osmPath, 'w', encoding='utf-8') + osmPath = temp_dir + "OSM_building.osm" + osmFile = open(osmPath, "w", encoding="utf-8") osmFile.write(osmXml) - feedback.setProgressText('Downloading OSM data from ' + urlStr) + feedback.setProgressText("Downloading OSM data from " + urlStr) if os.fstat(osmFile.fileno()).st_size < 1: - raise QgsProcessingException('Error! No OSM data available.') + raise QgsProcessingException( + "Error! No OSM data available." + ) osmFile.close() - #Creating shapefile from OSM data - osmconf_dir = root_dir + '/functions/DSMGenerator/osmconf.ini' + # Creating shapefile from OSM data + osmconf_dir = root_dir + "/functions/DSMGenerator/osmconf.ini" gdal.SetConfigOption("OSM_CONFIG_FILE", osmconf_dir) - osm_option = gdal.VectorTranslateOptions(options=[ - '-skipfailures', - '-t_srs', 'EPSG:' + str(ras_epsg), - '-overwrite', - '-nlt', 'MULTIPOLYGON', - '-f', 'ESRI Shapefile']) + osm_option = gdal.VectorTranslateOptions( + options=[ + "-skipfailures", + "-t_srs", + "EPSG:" + str(ras_epsg), + "-overwrite", + "-nlt", + "MULTIPOLYGON", + "-f", + "ESRI Shapefile", + ] + ) gdal.VectorTranslate(temp_dir, osmPath, options=osm_option) - driver = ogr.GetDriverByName('ESRI Shapefile') - driver.DeleteDataSource(temp_dir + 'lines.shp') - driver.DeleteDataSource(temp_dir + 'multilinestrings.shp') - driver.DeleteDataSource(temp_dir + 'other_relations.shp') - driver.DeleteDataSource(temp_dir + 'points.shp') + driver = ogr.GetDriverByName("ESRI Shapefile") + driver.DeleteDataSource(temp_dir + "lines.shp") + driver.DeleteDataSource(temp_dir + "multilinestrings.shp") + driver.DeleteDataSource(temp_dir + "other_relations.shp") + driver.DeleteDataSource(temp_dir + "points.shp") - osmPolygonPath = temp_dir + 'multipolygons.shp' - vlayer = QgsVectorLayer(osmPolygonPath, 'multipolygons', 'ogr') # Reads temp file made from OSM data + osmPolygonPath = temp_dir + "multipolygons.shp" + vlayer = QgsVectorLayer( + osmPolygonPath, "multipolygons", "ogr" + ) # Reads temp file made from OSM data fileInfo = QFileInfo(vlayer.source()) polygon_ln = fileInfo.baseName() # Renames attribute fields def renameField(srcLayer, oldFieldName, newFieldName): - ds = gdal.OpenEx(srcLayer.source(), gdal.OF_VECTOR | gdal.OF_UPDATE) - ds.ExecuteSQL('ALTER TABLE {} RENAME COLUMN {} TO {}'.format(srcLayer.name(), oldFieldName, newFieldName)) + ds = gdal.OpenEx( + srcLayer.source(), gdal.OF_VECTOR | gdal.OF_UPDATE + ) + ds.ExecuteSQL( + "ALTER TABLE {} RENAME COLUMN {} TO {}".format( + srcLayer.name(), oldFieldName, newFieldName + ) + ) srcLayer.reload() + vlayer.startEditing() - renameField(vlayer, 'building_l', 'bld_levels') - renameField(vlayer, 'building_h', 'bld_hght') - renameField(vlayer, 'building_c', 'bld_colour') - renameField(vlayer, 'building_m', 'bld_materi') - renameField(vlayer, 'building_u', 'bld_use') + renameField(vlayer, "building_l", "bld_levels") + renameField(vlayer, "building_h", "bld_hght") + renameField(vlayer, "building_c", "bld_colour") + renameField(vlayer, "building_m", "bld_materi") + renameField(vlayer, "building_u", "bld_use") vlayer.commitChanges() # Adding building height field vlayer.startEditing() - vlayer.dataProvider().addAttributes([QgsField('bld_height', QVariant.Double, 'double', 3, 2)]) + vlayer.dataProvider().addAttributes( + [QgsField("bld_height", QVariant.Double, "double", 3, 2)] + ) vlayer.updateFields() - flname = 'bld_height' + flname = "bld_height" counterNone = 0 counter = 0 for feature in vlayer.getFeatures(): - if feature['height']: # Tries first with actual building height data + if feature[ + "height" + ]: # Tries first with actual building height data try: - feature.setAttribute(feature.fieldNameIndex('bld_height'), float(str(feature['height']))) + feature.setAttribute( + feature.fieldNameIndex("bld_height"), + float(str(feature["height"])), + ) except: counterNone += 1 - elif feature['bld_hght']: # Tries with possible building height data (other tag for height??) + elif feature[ + "bld_hght" + ]: # Tries with possible building height data (other tag for height??) try: - feature.setAttribute(feature.fieldNameIndex('bld_height'), float(str(feature['bld_hght']))) + feature.setAttribute( + feature.fieldNameIndex("bld_height"), + float(str(feature["bld_hght"])), + ) except: counterNone += 1 - elif feature['bld_levels']: # If no height or building height then make building height from stories + elif feature[ + "bld_levels" + ]: # If no height or building height then make building height from stories try: - temp = float(str(feature['bld_levels'])) * buildingLevelHeight - feature.setAttribute(feature.fieldNameIndex('bld_height'), temp) + temp = ( + float(str(feature["bld_levels"])) + * buildingLevelHeight + ) + feature.setAttribute( + feature.fieldNameIndex("bld_height"), temp + ) except: counterNone += 1 else: @@ -376,7 +560,7 @@ def renameField(srcLayer, oldFieldName, newFieldName): counter += 1 vlayer.commitChanges() counterDiff = counter - counterNone - + else: # If not OSM data, input polygon layer with building heights should be used vlayer = shapelayer @@ -393,10 +577,14 @@ def renameField(srcLayer, oldFieldName, newFieldName): # Zonal statistics vlayer.startEditing() - zoneStat = QgsZonalStatistics(vlayer, rlayer, "stats_", 1, QgsZonalStatistics.Statistic.Mean) + zoneStat = QgsZonalStatistics( + vlayer, rlayer, "stats_", 1, QgsZonalStatistics.Statistic.Mean + ) zoneStat.calculateStatistics(None) - vlayer.dataProvider().addAttributes([QgsField('height_asl', QVariant.Double, 'double', 5, 2)]) + vlayer.dataProvider().addAttributes( + [QgsField("height_asl", QVariant.Double, "double", 5, 2)] + ) vlayer.updateFields() context = QgsExpressionContext() @@ -405,60 +593,99 @@ def renameField(srcLayer, oldFieldName, newFieldName): for f in vlayer.getFeatures(): scope.setFeature(f) - exp = QgsExpression('stats_mean + ' + flname) - f.setAttribute(f.fieldNameIndex('height_asl'), exp.evaluate(context)) + exp = QgsExpression("stats_mean + " + flname) + f.setAttribute( + f.fieldNameIndex("height_asl"), exp.evaluate(context) + ) vlayer.updateFeature(f) vlayer.commitChanges() # Sort vlayer ascending to prevent lower buildings from overwriting higher buildings in some complexes - sortPoly = temp_dir + 'sortPoly.shp' + sortPoly = temp_dir + "sortPoly.shp" if useOsm: - sort_options = gdal.VectorTranslateOptions(options=safe([ - '-sql', 'SELECT * FROM multipolygons ORDER BY height_asl ASC'])) - gdal.VectorTranslate(str(sortPoly), str(osmPolygonPath), options=sort_options) + sort_options = gdal.VectorTranslateOptions( + options=safe( + [ + "-sql", + "SELECT * FROM multipolygons ORDER BY height_asl ASC", + ] + ) + ) + gdal.VectorTranslate( + str(sortPoly), str(osmPolygonPath), options=sort_options + ) else: - query = safe('SELECT * FROM "{table}" ORDER BY height_asl ASC').format(table=safe(str(polygon_ln))) + query = safe( + 'SELECT * FROM "{table}" ORDER BY height_asl ASC' + ).format(table=safe(str(polygon_ln))) - sort_options = gdal.VectorTranslateOptions(options=[ - '-select', 'height_asl', - '-sql', safe(query) - ]) - gdal.VectorTranslate(str(sortPoly), str(vlayer.source()), options=sort_options) + sort_options = gdal.VectorTranslateOptions( + options=["-select", "height_asl", "-sql", safe(query)] + ) + gdal.VectorTranslate( + str(sortPoly), str(vlayer.source()), options=sort_options + ) # Reads temp file with sorted polygons - sort_layer = QgsVectorLayer(sortPoly, 'sortPoly', 'ogr') + sort_layer = QgsVectorLayer(sortPoly, "sortPoly", "ogr") fileInfo = QFileInfo(sort_layer.source()) sort_ln = fileInfo.baseName() # Convert polygon layer to raster # Create the destination data source - rasterize_options = gdal.RasterizeOptions(options=[ - '-a', 'height_asl', - '-te', str(minx), str(miny), str(maxx), str(maxy), - '-tr', str(pixelResolution), str(pixelResolution), - '-l', str(sort_ln)]) - - gdal.Rasterize(temp_dir + 'clipdsm.tif', str(sort_layer.source()), options=rasterize_options) - - warp_options = gdal.WarpOptions(options=[ - '-dstnodata', '-9999', - '-q', - '-overwrite', - '-te', str(minx), str(miny), str(maxx), str(maxy), - '-tr', str(pixelResolution), str(pixelResolution), - '-of', 'GTiff']) - - gdal.Warp(temp_dir + 'clipdem.tif', filepath_dem, options=warp_options) + rasterize_options = gdal.RasterizeOptions( + options=[ + "-a", + "height_asl", + "-te", + str(minx), + str(miny), + str(maxx), + str(maxy), + "-tr", + str(pixelResolution), + str(pixelResolution), + "-l", + str(sort_ln), + ] + ) + + gdal.Rasterize( + temp_dir + "clipdsm.tif", + str(sort_layer.source()), + options=rasterize_options, + ) + + warp_options = gdal.WarpOptions( + options=[ + "-dstnodata", + "-9999", + "-q", + "-overwrite", + "-te", + str(minx), + str(miny), + str(maxx), + str(maxy), + "-tr", + str(pixelResolution), + str(pixelResolution), + "-of", + "GTiff", + ] + ) + + gdal.Warp(temp_dir + "clipdem.tif", filepath_dem, options=warp_options) feedback.setProgress(60) # Adding DSM to DEM # Read DEM - dem_raster = gdal.Open(temp_dir + 'clipdem.tif') + dem_raster = gdal.Open(temp_dir + "clipdem.tif") dem_array = np.array(dem_raster.ReadAsArray().astype(float)) - dsm_raster = gdal.Open(temp_dir + 'clipdsm.tif') + dsm_raster = gdal.Open(temp_dir + "clipdsm.tif") dsm_array = np.array(dsm_raster.ReadAsArray().astype(float)) indx = dsm_array.shape @@ -472,26 +699,53 @@ def renameField(srcLayer, oldFieldName, newFieldName): vlayer.startEditing() for f in vlayer.getFeatures(): geom = f.geometry() - possible_units_metre = ['metre', 'Metre', 'metres', 'Metres', 'meter', 'Meter', 'meters', 'Meters', 'm'] # Possible metre units - possible_units_feet = ['ft', 'US survey foot', 'feet', 'Feet', 'foot', 'Foot', 'ftUS', 'International foot'] # Possible foot units + possible_units_metre = [ + "metre", + "Metre", + "metres", + "Metres", + "meter", + "Meter", + "meters", + "Meters", + "m", + ] # Possible metre units + possible_units_feet = [ + "ft", + "US survey foot", + "feet", + "Feet", + "foot", + "Foot", + "ftUS", + "International foot", + ] # Possible foot units if dem_unit in possible_units_metre: sqUnit = 1 elif dem_unit in possible_units_feet: sqUnit = 10.76 - if int(geom.area()) > 50000*sqUnit: # Deleting large polygons + if ( + int(geom.area()) > 50000 * sqUnit + ): # Deleting large polygons vlayer.deleteFeature(f.id()) vlayer.updateFeature(f) vlayer.updateFields() vlayer.commitChanges() - QgsVectorFileWriter.writeAsVectorFormat(vlayer, str(outputShape), "UTF-8", QgsCoordinateTransform(), "ESRI Shapefile") + QgsVectorFileWriter.writeAsVectorFormat( + vlayer, + str(outputShape), + "UTF-8", + QgsCoordinateTransform(), + "ESRI Shapefile", + ) # If using other data than OSM, remove some fields elif not useOsm: vlayer.startEditing() - idx1 = vlayer.fields().indexFromName('stats_mean') + idx1 = vlayer.fields().indexFromName("stats_mean") vlayer.dataProvider().deleteAttributes([idx1]) vlayer.updateFields() - idx2 = vlayer.fields().indexFromName('height_asl') + idx2 = vlayer.fields().indexFromName("height_asl") vlayer.dataProvider().deleteAttributes([idx2]) vlayer.updateFields() vlayer.commitChanges() @@ -501,20 +755,31 @@ def renameField(srcLayer, oldFieldName, newFieldName): saveraster(dsm_raster, outputDSM, dsm_array) if useOsm: - feedback.setProgressText('DSM Generator: Operation successful! ' + str(counterDiff) + ' building polygons out of ' + str(counter) + ' contained height values.') + feedback.setProgressText( + "DSM Generator: Operation successful! " + + str(counterDiff) + + " building polygons out of " + + str(counter) + + " contained height values." + ) else: - feedback.setProgressText('DSM Generator: Operation successful!') + feedback.setProgressText("DSM Generator: Operation successful!") feedback.setProgress(100) - feedback.setProgressText("DSM Generator: Digital Surface Model successfully generated") + feedback.setProgressText( + "DSM Generator: Digital Surface Model successfully generated" + ) if len(outputShape) > 0: - return {self.OUTPUT_DSM: outputDSM, self.OUTPUT_POLYGON: outputShape} + return { + self.OUTPUT_DSM: outputDSM, + self.OUTPUT_POLYGON: outputShape, + } else: return {self.OUTPUT_DSM: outputDSM} def name(self): - return 'Spatial Data: DSM Generator' + return "Spatial Data: DSM Generator" def displayName(self): return self.tr(self.name()) @@ -523,36 +788,40 @@ def group(self): return self.tr(self.groupId()) def groupId(self): - return 'Pre-Processor' - - def shortHelpString(self): - return self.tr('Digital Surface Models (DSMs) is not always available for the area you want to investigate. ' - 'The DSM Generator can be used to create or alter a DSM by using information from a polygon ' - 'building footprint layer where a building height attribute is available. An option to acquire ' - 'building footprints, and also in some cases building height from Open Street Map data, is ' - 'available from this plugin.
' - '\n' - 'Required input:' - '
  • Digital Elevation Model (DEM) raster data in metres or feet.
  • ' - '
  • Either a polygon shapefile with building height information or use OSM data (tick Use Open Street Map).
  • ' - '
  • Building level height is used for OSM data to represent building height when only information on building stories is available.
  • ' - '
  • Pixel resolution is in same unit as the input DEM data.
  • ' - '
  • Output is a Digital Surface Model (DSM).
  • ' - '
  • Optional output is a polygon shapefile with the OSM data, if OSM data is being used.
' - '\n' - 'Full manual available via the Help-button.') + return "Pre-Processor" + + def shortHelpString(self): + return self.tr( + "Digital Surface Models (DSMs) is not always available for the area you want to investigate. " + "The DSM Generator can be used to create or alter a DSM by using information from a polygon " + "building footprint layer where a building height attribute is available. An option to acquire " + "building footprints, and also in some cases building height from Open Street Map data, is " + "available from this plugin.
" + "\n" + "Required input:" + "
  • Digital Elevation Model (DEM) raster data in metres or feet.
  • " + "
  • Either a polygon shapefile with building height information or use OSM data (tick Use Open Street Map).
  • " + "
  • Building level height is used for OSM data to represent building height when only information on building stories is available.
  • " + "
  • Pixel resolution is in same unit as the input DEM data.
  • " + "
  • Output is a Digital Surface Model (DSM).
  • " + "
  • Optional output is a polygon shapefile with the OSM data, if OSM data is being used.
" + "\n" + "Full manual available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/pre-processor/Spatial%20Data%20DSM%20Generator.html" return url def tr(self, string): - return QCoreApplication.translate('Processing', string) + return QCoreApplication.translate("Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/DSMGeneratorIcon.png") return icon def createInstance(self): - return ProcessingDSMGeneratorAlgorithm() \ No newline at end of file + return ProcessingDSMGeneratorAlgorithm() diff --git a/preprocessor/imagemorphparms_algorithm.py b/preprocessor/imagemorphparms_algorithm.py index b5a0d75..ab87ada 100644 --- a/preprocessor/imagemorphparms_algorithm.py +++ b/preprocessor/imagemorphparms_algorithm.py @@ -22,31 +22,33 @@ ***************************************************************************/ """ -__author__ = 'Fredrik Lindberg' -__date__ = '2020-04-02' -__copyright__ = '(C) 2020 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2020-04-02" +__copyright__ = "(C) 2020 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QVariant -from qgis.core import (QgsProcessing, - QgsProcessingAlgorithm, - QgsProcessingParameterString, - QgsProcessingParameterBoolean, - QgsProcessingParameterNumber, - QgsProcessingParameterFolderDestination, - QgsProcessingParameterRasterLayer, - QgsProcessingParameterEnum, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterField, - QgsProcessingException, - QgsFeature, - QgsVectorFileWriter, - QgsVectorDataProvider, - QgsField, - QgsProcessingParameterDefinition) +from qgis.core import ( + QgsProcessing, + QgsProcessingAlgorithm, + QgsProcessingParameterString, + QgsProcessingParameterBoolean, + QgsProcessingParameterNumber, + QgsProcessingParameterFolderDestination, + QgsProcessingParameterRasterLayer, + QgsProcessingParameterEnum, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterField, + QgsProcessingException, + QgsFeature, + QgsVectorFileWriter, + QgsVectorDataProvider, + QgsField, + QgsProcessingParameterDefinition, +) from qgis.PyQt.QtGui import QIcon from osgeo import gdal, ogr, osr @@ -67,128 +69,228 @@ class ProcessingImageMorphParmsAlgorithm(QgsProcessingAlgorithm): This algorithm is a processing version of Image Morphometric Calculator Point """ - INPUT_POLYGONLAYER = 'INPUT_POLYGONLAYER' - ID_FIELD = 'ID_FIELD' - SERACH_METHOD = 'SEARCH_METHOD' - INPUT_DISTANCE = 'INPUT_DISTANCE' - INPUT_INTERVAL = 'INPUT_INTERVAL' - INPUT_DSM = 'INPUT_DSM' - INPUT_DEM = 'INPUT_DEM' - INPUT_DSMBUILD = 'INPUT_DSMBUILD' - USE_DSMBUILD = 'USE_DSM_BUILD' - ROUGH = 'ROUGH' - FILE_PREFIX = 'FILE_PREFIX' - OUTPUT_DIR = 'OUTPUT_DIR' - IGNORE_NODATA = 'IGNORE_NODATA' - ATTR_TABLE = 'ATTR_TABLE' - CALC_SS = 'CALC_SS' - #SS_HEIGHTS = 'SS_HEIGHTS' - INPUT_CDSM = 'INPUT_CDSM' - - + INPUT_POLYGONLAYER = "INPUT_POLYGONLAYER" + ID_FIELD = "ID_FIELD" + SERACH_METHOD = "SEARCH_METHOD" + INPUT_DISTANCE = "INPUT_DISTANCE" + INPUT_INTERVAL = "INPUT_INTERVAL" + INPUT_DSM = "INPUT_DSM" + INPUT_DEM = "INPUT_DEM" + INPUT_DSMBUILD = "INPUT_DSMBUILD" + USE_DSMBUILD = "USE_DSM_BUILD" + ROUGH = "ROUGH" + FILE_PREFIX = "FILE_PREFIX" + OUTPUT_DIR = "OUTPUT_DIR" + IGNORE_NODATA = "IGNORE_NODATA" + ATTR_TABLE = "ATTR_TABLE" + CALC_SS = "CALC_SS" + # SS_HEIGHTS = 'SS_HEIGHTS' + INPUT_CDSM = "INPUT_CDSM" + def initAlgorithm(self, config): - self.rough = ((self.tr('Rule of thumb'), '0'), - (self.tr('Raupach (1994/95)'), '1'), - (self.tr('Simplified Bottema (1995)'), '2'), - (self.tr('MacDonald et al. (1998)'), '3'), - (self.tr('Millward-Hopkins et al. (2011)'), '4'), - (self.tr('Kanda et al. (2013)'), '5')) - self.search = ((self.tr('Throughout the grid extent'), '0'), - (self.tr('From grid centroid'), '1')) - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_POLYGONLAYER, - self.tr('Vector polygon grid'), [QgsProcessing.SourceType.TypeVectorPolygon])) - self.addParameter(QgsProcessingParameterField(self.ID_FIELD, - self.tr('ID field'),'', self.INPUT_POLYGONLAYER, QgsProcessingParameterField.DataType.Numeric)) - self.addParameter(QgsProcessingParameterEnum(self.SERACH_METHOD, - self.tr('Search method'), - options=[i[0] for i in self.search], defaultValue=0)) - self.addParameter(QgsProcessingParameterNumber(self.INPUT_DISTANCE, - self.tr('Search distance from grid cell centroid (m)'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(200), False, minValue=0)) - self.addParameter(QgsProcessingParameterNumber(self.INPUT_INTERVAL, - self.tr('Wind direction search interval (degree)'), - QgsProcessingParameterNumber.Type.Double, - QVariant(5), False, minValue=0.1, maxValue=360.)) - self.addParameter(QgsProcessingParameterBoolean(self.USE_DSMBUILD, - self.tr("Raster DSM (only 3D building or vegetation objects) exist"), defaultValue=False)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_DSM, - self.tr('Raster DSM (3D objects and ground)'), '', True)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_DEM, - self.tr('Raster DEM (only ground)'), '', True)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_DSMBUILD, - self.tr('Raster DSM (only 3D objects)'), '', True)) - self.addParameter(QgsProcessingParameterEnum(self.ROUGH, - self.tr('Roughness calculation method'), - options=[i[0] for i in self.rough], defaultValue=0)) - self.addParameter(QgsProcessingParameterString(self.FILE_PREFIX, - self.tr('File prefix'))) - self.addParameter(QgsProcessingParameterBoolean(self.IGNORE_NODATA, - self.tr("Ignore NoData pixels"), defaultValue=True)) - self.addParameter(QgsProcessingParameterBoolean(self.ATTR_TABLE, - self.tr("Add result to polygon grid attribute table"), defaultValue=False)) - self.addParameter(QgsProcessingParameterFolderDestination(self.OUTPUT_DIR, - self.tr('Output folder'))) + self.rough = ( + (self.tr("Rule of thumb"), "0"), + (self.tr("Raupach (1994/95)"), "1"), + (self.tr("Simplified Bottema (1995)"), "2"), + (self.tr("MacDonald et al. (1998)"), "3"), + (self.tr("Millward-Hopkins et al. (2011)"), "4"), + (self.tr("Kanda et al. (2013)"), "5"), + ) + self.search = ( + (self.tr("Throughout the grid extent"), "0"), + (self.tr("From grid centroid"), "1"), + ) + self.addParameter( + QgsProcessingParameterFeatureSource( + self.INPUT_POLYGONLAYER, + self.tr("Vector polygon grid"), + [QgsProcessing.SourceType.TypeVectorPolygon], + ) + ) + self.addParameter( + QgsProcessingParameterField( + self.ID_FIELD, + self.tr("ID field"), + "", + self.INPUT_POLYGONLAYER, + QgsProcessingParameterField.DataType.Numeric, + ) + ) + self.addParameter( + QgsProcessingParameterEnum( + self.SERACH_METHOD, + self.tr("Search method"), + options=[i[0] for i in self.search], + defaultValue=0, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.INPUT_DISTANCE, + self.tr("Search distance from grid cell centroid (m)"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(200), + False, + minValue=0, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.INPUT_INTERVAL, + self.tr("Wind direction search interval (degree)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(5), + False, + minValue=0.1, + maxValue=360.0, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.USE_DSMBUILD, + self.tr( + "Raster DSM (only 3D building or vegetation objects) exist" + ), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_DSM, + self.tr("Raster DSM (3D objects and ground)"), + "", + True, + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_DEM, self.tr("Raster DEM (only ground)"), "", True + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_DSMBUILD, + self.tr("Raster DSM (only 3D objects)"), + "", + True, + ) + ) + self.addParameter( + QgsProcessingParameterEnum( + self.ROUGH, + self.tr("Roughness calculation method"), + options=[i[0] for i in self.rough], + defaultValue=0, + ) + ) + self.addParameter( + QgsProcessingParameterString( + self.FILE_PREFIX, self.tr("File prefix") + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.IGNORE_NODATA, + self.tr("Ignore NoData pixels"), + defaultValue=True, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.ATTR_TABLE, + self.tr("Add result to polygon grid attribute table"), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterFolderDestination( + self.OUTPUT_DIR, self.tr("Output folder") + ) + ) # Advanced parameters (SS) - ss = QgsProcessingParameterBoolean(self.CALC_SS, - self.tr("Calculate parameters for SUEWS/SS"), defaultValue=False, optional=True) - ss.setFlags(ss.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + ss = QgsProcessingParameterBoolean( + self.CALC_SS, + self.tr("Calculate parameters for SUEWS/SS"), + defaultValue=False, + optional=True, + ) + ss.setFlags( + ss.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(ss) - sscdsm = QgsProcessingParameterRasterLayer(self.INPUT_CDSM, - self.tr('Raster vegetation DSM (CDSM)'), '', True) - sscdsm.setFlags(sscdsm.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + sscdsm = QgsProcessingParameterRasterLayer( + self.INPUT_CDSM, self.tr("Raster vegetation DSM (CDSM)"), "", True + ) + sscdsm.setFlags( + sscdsm.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(sscdsm) - self.plugin_dir = os.path.dirname(__file__) - if not (os.path.isdir(self.plugin_dir + '/data')): - os.mkdir(self.plugin_dir + '/data') - self.dir_poly = self.plugin_dir + '/data/poly_temp.shp' + if not (os.path.isdir(self.plugin_dir + "/data")): + os.mkdir(self.plugin_dir + "/data") + self.dir_poly = self.plugin_dir + "/data/poly_temp.shp" def processAlgorithm(self, parameters, context, feedback): - # InputParameters - inputPolygonlayer = self.parameterAsVectorLayer(parameters, self.INPUT_POLYGONLAYER, context) + # InputParameters + inputPolygonlayer = self.parameterAsVectorLayer( + parameters, self.INPUT_POLYGONLAYER, context + ) idField = self.parameterAsFields(parameters, self.ID_FIELD, context) - searchMethod = self.parameterAsString(parameters, self.SERACH_METHOD, context) - inputDistance = self.parameterAsDouble(parameters, self.INPUT_DISTANCE, context) - inputInterval = self.parameterAsDouble(parameters, self.INPUT_INTERVAL, context) - useDsmBuild = self.parameterAsBool(parameters, self.USE_DSMBUILD, context) + searchMethod = self.parameterAsString( + parameters, self.SERACH_METHOD, context + ) + inputDistance = self.parameterAsDouble( + parameters, self.INPUT_DISTANCE, context + ) + inputInterval = self.parameterAsDouble( + parameters, self.INPUT_INTERVAL, context + ) + useDsmBuild = self.parameterAsBool( + parameters, self.USE_DSMBUILD, context + ) dsmlayer = None demlayer = None ro = self.parameterAsString(parameters, self.ROUGH, context) - filePrefix = self.parameterAsString(parameters, self.FILE_PREFIX, context) + filePrefix = self.parameterAsString( + parameters, self.FILE_PREFIX, context + ) attrTable = self.parameterAsBool(parameters, self.ATTR_TABLE, context) - ignoreNodata = self.parameterAsBool(parameters, self.IGNORE_NODATA, context) - outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) + ignoreNodata = self.parameterAsBool( + parameters, self.IGNORE_NODATA, context + ) + outputDir = self.parameterAsString( + parameters, self.OUTPUT_DIR, context + ) calcSS = self.parameterAsBool(parameters, self.CALC_SS, context) - - if parameters['OUTPUT_DIR'] == 'TEMPORARY_OUTPUT': + + if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): os.mkdir(outputDir) - + # preparing headers etc. degree = float(inputInterval) pre = filePrefix - headerAniso = ' Wd pai fai zH zHmax zHstd zd z0 noOfPixels' - numformat = '%3d %4.3f %4.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.0f' - headerIso = ' id pai fai zH zHmax zHstd zd z0 wai ' #moved inside loop - numformat2 = '%3d %4.3f %4.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f' + headerAniso = " Wd pai fai zH zHmax zHstd zd z0 noOfPixels" + numformat = "%3d %4.3f %4.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.0f" + headerIso = " id pai fai zH zHmax zHstd zd z0 wai " # moved inside loop + numformat2 = "%3d %4.3f %4.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f" - #adding parameters for SUEWS/SS + # adding parameters for SUEWS/SS if calcSS: - headerSS = ' z paib bScale paiv vScale ' - numformatSS = '%3d %4.3f %4.3f %4.3f %4.3f' - - imp_point = 0 # only used in menu-based tool + headerSS = " z paib bScale paiv vScale " + numformatSS = "%3d %4.3f %4.3f %4.3f %4.3f" + + imp_point = 0 # only used in menu-based tool imid = int(searchMethod) arrmat = np.empty((1, 9)) # temporary fix for mac, ISSUE #15 pf = sys.platform - if pf == 'darwin' or pf == 'linux2' or pf == 'linux': - if not os.path.exists(outputDir + '/' + pre): - os.makedirs(outputDir + '/' + pre) + if pf == "darwin" or pf == "linux2" or pf == "linux": + if not os.path.exists(outputDir + "/" + pre): + os.makedirs(outputDir + "/" + pre) # poly = inputPolygonlayer poly_field = idField @@ -203,25 +305,25 @@ def processAlgorithm(self, parameters, context, feedback): # #Calculate Z0m and Zdm depending on the Z0 method if int(ro) == 0: - Roughnessmethod = 'RT' + Roughnessmethod = "RT" elif int(ro) == 1: - Roughnessmethod = 'Rau' + Roughnessmethod = "Rau" elif int(ro) == 2: - Roughnessmethod = 'Bot' + Roughnessmethod = "Bot" elif int(ro) == 3: - Roughnessmethod = 'Mac' + Roughnessmethod = "Mac" elif int(ro) == 4: - Roughnessmethod = 'Mho' + Roughnessmethod = "Mho" else: - Roughnessmethod = 'Kan' + Roughnessmethod = "Kan" # looping through each grid polygon - for f in vlayer.getFeatures(): + for f in vlayer.getFeatures(): feedback.setProgress(int((index * 100) / nGrids)) if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") break - + index += 1 attributes = f.attributes() @@ -230,32 +332,53 @@ def processAlgorithm(self, parameters, context, feedback): feature.setAttributes(attributes) feature.setGeometry(geometry) - if imid == 1: # from centroid point + if imid == 1: # from centroid point r = inputDistance y = f.geometry().centroid().asPoint().y() x = f.geometry().centroid().asPoint().x() bbox = (x - r, y + r, x + r, y - r) - else: # from cutline polygon + else: # from cutline polygon r = 0 # Used as info to separate from IMP point to grid - writer = QgsVectorFileWriter(self.dir_poly, "CP1250", fields, prov.wkbType(), - prov.crs(), "ESRI shapefile") - if writer.hasError() != QgsVectorFileWriter.WriterError.NoError: - raise QgsProcessingException("Error when creating shapefile: ", str(writer.hasError())) + writer = QgsVectorFileWriter( + self.dir_poly, + "CP1250", + fields, + prov.wkbType(), + prov.crs(), + "ESRI shapefile", + ) + if ( + writer.hasError() + != QgsVectorFileWriter.WriterError.NoError + ): + raise QgsProcessingException( + "Error when creating shapefile: ", + str(writer.hasError()), + ) writer.addFeature(feature) del writer VectorDriver = ogr.GetDriverByName("ESRI Shapefile") - Vector = VectorDriver.Open(self.dir_poly, 0) #self.dir_poly + Vector = VectorDriver.Open(self.dir_poly, 0) # self.dir_poly layer = Vector.GetLayer() feature = layer.GetFeature(0) geom = feature.GetGeometryRef() minX, maxX, minY, maxY = geom.GetEnvelope() - bbox = (minX, maxY, maxX, minY) # Reorder bbox to use with gdal_translate + bbox = ( + minX, + maxY, + maxX, + minY, + ) # Reorder bbox to use with gdal_translate Vector.Destroy() if useDsmBuild: # Only building heights - dsmlayer = self.parameterAsRasterLayer(parameters, self.INPUT_DSMBUILD, context) + dsmlayer = self.parameterAsRasterLayer( + parameters, self.INPUT_DSMBUILD, context + ) if dsmlayer is None: - raise QgsProcessingException("No valid building DSM raster layer is selected") + raise QgsProcessingException( + "No valid building DSM raster layer is selected" + ) provider = dsmlayer.dataProvider() filePath_dsm_build = str(provider.dataSourceUri()) @@ -263,13 +386,25 @@ def processAlgorithm(self, parameters, context, feedback): # added gdal.Warp() for irregular grids bigraster = gdal.Open(filePath_dsm_build) if imid == 1: - gdal.Translate(self.plugin_dir + '/data/clipdsm.tif', bigraster, projWin=bbox) + gdal.Translate( + self.plugin_dir + "/data/clipdsm.tif", + bigraster, + projWin=bbox, + ) else: - clip_spec = gdal.WarpOptions(format="GTiff", cutlineDSName=self.dir_poly, cropToCutline=True) - gdal.Warp(self.plugin_dir + '/data/clipdsm.tif', bigraster, options=clip_spec) + clip_spec = gdal.WarpOptions( + format="GTiff", + cutlineDSName=self.dir_poly, + cropToCutline=True, + ) + gdal.Warp( + self.plugin_dir + "/data/clipdsm.tif", + bigraster, + options=clip_spec, + ) bigraster = None - dataset = gdal.Open(self.plugin_dir + '/data/clipdsm.tif') + dataset = gdal.Open(self.plugin_dir + "/data/clipdsm.tif") dsm_array = dataset.ReadAsArray().astype(float) sizex = dsm_array.shape[0] sizey = dsm_array.shape[1] @@ -277,13 +412,21 @@ def processAlgorithm(self, parameters, context, feedback): ndDEM = -9999 else: # Both building ground heights - dsmlayer = self.parameterAsRasterLayer(parameters, self.INPUT_DSM, context) - demlayer = self.parameterAsRasterLayer(parameters, self.INPUT_DEM, context) + dsmlayer = self.parameterAsRasterLayer( + parameters, self.INPUT_DSM, context + ) + demlayer = self.parameterAsRasterLayer( + parameters, self.INPUT_DEM, context + ) if dsmlayer is None: - raise QgsProcessingException("No valid ground and building DSM raster layer is selected") + raise QgsProcessingException( + "No valid ground and building DSM raster layer is selected" + ) if demlayer is None: - raise QgsProcessingException("No valid ground DEM raster layer is selected") + raise QgsProcessingException( + "No valid ground DEM raster layer is selected" + ) provider = dsmlayer.dataProvider() filePath_dsm = str(provider.dataSourceUri()) @@ -293,30 +436,60 @@ def processAlgorithm(self, parameters, context, feedback): # added gdal.Warp() for irregular grids bigraster = gdal.Open(filePath_dsm) if imid == 1: - gdal.Translate(self.plugin_dir + '/data/clipdsm.tif', bigraster, projWin=bbox) + gdal.Translate( + self.plugin_dir + "/data/clipdsm.tif", + bigraster, + projWin=bbox, + ) else: - clip_spec = gdal.WarpOptions(format="GTiff", cutlineDSName=self.dir_poly, cropToCutline=True) - gdal.Warp(self.plugin_dir + '/data/clipdsm.tif', bigraster, options=clip_spec) + clip_spec = gdal.WarpOptions( + format="GTiff", + cutlineDSName=self.dir_poly, + cropToCutline=True, + ) + gdal.Warp( + self.plugin_dir + "/data/clipdsm.tif", + bigraster, + options=clip_spec, + ) bigraster = None bigraster = gdal.Open(filePath_dem) if imid == 1: - gdal.Translate(self.plugin_dir + '/data/clipdem.tif', bigraster, projWin=bbox) + gdal.Translate( + self.plugin_dir + "/data/clipdem.tif", + bigraster, + projWin=bbox, + ) else: - clip_spec = gdal.WarpOptions(format="GTiff", cutlineDSName=self.dir_poly, cropToCutline=True) - gdal.Warp(self.plugin_dir + '/data/clipdem.tif', bigraster, options=clip_spec) + clip_spec = gdal.WarpOptions( + format="GTiff", + cutlineDSName=self.dir_poly, + cropToCutline=True, + ) + gdal.Warp( + self.plugin_dir + "/data/clipdem.tif", + bigraster, + options=clip_spec, + ) bigraster = None - dataset = gdal.Open(self.plugin_dir + '/data/clipdsm.tif') + dataset = gdal.Open(self.plugin_dir + "/data/clipdsm.tif") dsm_array = dataset.ReadAsArray().astype(float) - dataset2 = gdal.Open(self.plugin_dir + '/data/clipdem.tif') + dataset2 = gdal.Open(self.plugin_dir + "/data/clipdem.tif") dem_array = dataset2.ReadAsArray().astype(float) ndDEM = dataset2.GetRasterBand(1).GetNoDataValue() - if not (dsm_array.shape[0] == dem_array.shape[0]) & (dsm_array.shape[1] == dem_array.shape[1]): - raise QgsProcessingException("All grids must be of same extent and resolution") - - if calcSS: #add vegetion (if present) for SUEWS/SS - cdsmlayer = self.parameterAsRasterLayer(parameters, self.INPUT_CDSM, context) + if not (dsm_array.shape[0] == dem_array.shape[0]) & ( + dsm_array.shape[1] == dem_array.shape[1] + ): + raise QgsProcessingException( + "All grids must be of same extent and resolution" + ) + + if calcSS: # add vegetion (if present) for SUEWS/SS + cdsmlayer = self.parameterAsRasterLayer( + parameters, self.INPUT_CDSM, context + ) if cdsmlayer is None: cdsm_array = dsm_array * 0.0 ndCDSM = -9999 @@ -325,62 +498,131 @@ def processAlgorithm(self, parameters, context, feedback): filePath_cdsm = str(provider.dataSourceUri()) bigraster = gdal.Open(filePath_cdsm) if imid == 1: - gdal.Translate(self.plugin_dir + '/data/clipdsm.tif', bigraster, projWin=bbox) + gdal.Translate( + self.plugin_dir + "/data/clipdsm.tif", + bigraster, + projWin=bbox, + ) else: - clip_spec = gdal.WarpOptions(format="GTiff", cutlineDSName=self.dir_poly, cropToCutline=True) - gdal.Warp(self.plugin_dir + '/data/clipcdsm.tif', bigraster, options=clip_spec) + clip_spec = gdal.WarpOptions( + format="GTiff", + cutlineDSName=self.dir_poly, + cropToCutline=True, + ) + gdal.Warp( + self.plugin_dir + "/data/clipcdsm.tif", + bigraster, + options=clip_spec, + ) bigraster = None - dataseti = gdal.Open(self.plugin_dir + '/data/clipcdsm.tif') + dataseti = gdal.Open( + self.plugin_dir + "/data/clipcdsm.tif" + ) ndCDSM = dataseti.GetRasterBand(1).GetNoDataValue() cdsm_array = dataseti.ReadAsArray().astype(float) - geotransform = dataset.GetGeoTransform() scale = 1 / geotransform[1] nd = dataset.GetRasterBand(1).GetNoDataValue() if nd is None: - feedback.pushWarning("NoData in DSM layer not set. Tick off 'Ignore NoData pixels' to make use of this tool or assign NoData value to your raster data.") + feedback.pushWarning( + "NoData in DSM layer not set. Tick off 'Ignore NoData pixels' to make use of this tool or assign NoData value to your raster data." + ) else: - feedback.setProgressText("NoData-value in DSM-layer: " + str(nd)) - nodata_test = (dsm_array == nd) + feedback.setProgressText( + "NoData-value in DSM-layer: " + str(nd) + ) + nodata_test = dsm_array == nd if ignoreNodata: - if np.sum(dsm_array) == (dsm_array.shape[0] * dsm_array.shape[1] * nd): - feedback.setProgressText("Grid " + str(f.attributes()[idx]) + " not calculated. Includes Only NoData Pixels") + if np.sum(dsm_array) == ( + dsm_array.shape[0] * dsm_array.shape[1] * nd + ): + feedback.setProgressText( + "Grid " + + str(f.attributes()[idx]) + + " not calculated. Includes Only NoData Pixels" + ) cal = 0 else: - feedback.setProgressText("Grid " + str(f.attributes()[idx]) + " being calculated.") + feedback.setProgressText( + "Grid " + + str(f.attributes()[idx]) + + " being calculated." + ) cal = 1 else: if nodata_test.any(): - feedback.setProgressText("Grid " + str(f.attributes()[idx]) + " not calculated. Includes NoData Pixels") + feedback.setProgressText( + "Grid " + + str(f.attributes()[idx]) + + " not calculated. Includes NoData Pixels" + ) cal = 0 else: cal = 1 - feedback.setProgressText("Grid " + str(f.attributes()[idx]) + " being calculated.") + feedback.setProgressText( + "Grid " + + str(f.attributes()[idx]) + + " being calculated." + ) if cal == 1: - #set nodata to same + # set nodata to same dsm_array[dsm_array == nd] = -9999 dem_array[dem_array == ndDEM] = -9999 - if calcSS: + if calcSS: cdsm_array[cdsm_array == ndCDSM] = -9999 - - #calculate morphometric params - immorphresult = morph.imagemorphparam_v2(dsm_array, dem_array, scale, imid, degree, feedback, imp_point) + + # calculate morphometric params + immorphresult = morph.imagemorphparam_v2( + dsm_array, + dem_array, + scale, + imid, + degree, + feedback, + imp_point, + ) zH = immorphresult["zH"] fai = immorphresult["fai"] pai = immorphresult["pai"] zMax = immorphresult["zHmax"] zSdev = immorphresult["zH_sd"] - - zd, z0 = rg.RoughnessCalcMany(Roughnessmethod, zH, fai, pai, zMax, zSdev) + + zd, z0 = rg.RoughnessCalcMany( + Roughnessmethod, zH, fai, pai, zMax, zSdev + ) # save to file - arr = np.concatenate((immorphresult["deg"], immorphresult["pai"], immorphresult["fai"], - immorphresult["zH"], immorphresult["zHmax"], immorphresult["zH_sd"], zd, z0, immorphresult["test"]), axis=1) - np.savetxt(outputDir + '/' + pre + '_' + 'IMPGrid_anisotropic_' + str(f.attributes()[idx]) + '.txt', arr, - fmt=numformat, delimiter=' ', header=headerAniso, comments='') + arr = np.concatenate( + ( + immorphresult["deg"], + immorphresult["pai"], + immorphresult["fai"], + immorphresult["zH"], + immorphresult["zHmax"], + immorphresult["zH_sd"], + zd, + z0, + immorphresult["test"], + ), + axis=1, + ) + np.savetxt( + outputDir + + "/" + + pre + + "_" + + "IMPGrid_anisotropic_" + + str(f.attributes()[idx]) + + ".txt", + arr, + fmt=numformat, + delimiter=" ", + header=headerAniso, + comments="", + ) del arr zHall = immorphresult["zH_all"] @@ -388,7 +630,9 @@ def processAlgorithm(self, parameters, context, feedback): paiall = immorphresult["pai_all"] zMaxall = immorphresult["zHmax_all"] zSdevall = immorphresult["zH_sd_all"] - zdall, z0all = rg.RoughnessCalc(Roughnessmethod, zHall, faiall, paiall, zMaxall, zSdevall) + zdall, z0all = rg.RoughnessCalc( + Roughnessmethod, zHall, faiall, paiall, zMaxall, zSdevall + ) # If zd and z0 are lower than open country, set to open country if zdall == 0.0: @@ -397,42 +641,89 @@ def processAlgorithm(self, parameters, context, feedback): z0all = 0.03 # If pai is larger than 0 and fai is zero, set fai to 0.001. Issue # 164 - if paiall > 0.: - if faiall == 0.: + if paiall > 0.0: + if faiall == 0.0: faiall = 0.001 # adding wai area to isotrophic (wall area index) - total = 100. / (int(dsm_array.shape[0] * dsm_array.shape[1])) + total = 100.0 / (int(dsm_array.shape[0] * dsm_array.shape[1])) numPixels = len(dsm_array[np.where(dsm_array != nd)]) buildDSM = np.copy(dsm_array) - np.copy(dem_array) buildDSM[buildDSM == nd] = 0 - buildDSM[(buildDSM < 3.)] = 0 # building should be higher than 2 meter. Changed to 3 meters #784 - walls = wa.findwalls(buildDSM, 0.5, feedback, total) # 0.5 meter difference in kernel filter identify a wall + buildDSM[(buildDSM < 3.0)] = ( + 0 # building should be higher than 2 meter. Changed to 3 meters #784 + ) + walls = wa.findwalls( + buildDSM, 0.5, feedback, total + ) # 0.5 meter difference in kernel filter identify a wall wallarea = np.sum(walls) - gridArea = numPixels * geotransform[1] * abs(geotransform[5]) # changed to work for irregular grids + gridArea = ( + numPixels * geotransform[1] * abs(geotransform[5]) + ) # changed to work for irregular grids wai = wallarea / gridArea - arr2 = np.array([[f.attributes()[idx], immorphresult["pai_all"], immorphresult["fai_all"], immorphresult["zH_all"], - immorphresult["zHmax_all"], immorphresult["zH_sd_all"], zdall, z0all, wai]]) + arr2 = np.array( + [ + [ + f.attributes()[idx], + immorphresult["pai_all"], + immorphresult["fai_all"], + immorphresult["zH_all"], + immorphresult["zHmax_all"], + immorphresult["zH_sd_all"], + zdall, + z0all, + wai, + ] + ] + ) arrmat = np.vstack([arrmat, arr2]) if calcSS: - #arrmatSS = np.empty((1, 5)) - ssResults = ss.ss_calc(buildDSM, cdsm_array, walls, numPixels, feedback) - arrSS = np.hstack([ssResults["z"], ssResults["paiZ_b"], ssResults["bScale"], ssResults["paiZ_v"], ssResults["vScale"]]) - np.savetxt(outputDir + '/' + pre + '_' + 'IMPGrid_SS_' + str(f.attributes()[idx]) + '.txt', arrSS, - fmt=numformatSS, delimiter=' ', header=headerSS, comments='') + # arrmatSS = np.empty((1, 5)) + ssResults = ss.ss_calc( + buildDSM, cdsm_array, walls, numPixels, feedback + ) + arrSS = np.hstack( + [ + ssResults["z"], + ssResults["paiZ_b"], + ssResults["bScale"], + ssResults["paiZ_v"], + ssResults["vScale"], + ] + ) + np.savetxt( + outputDir + + "/" + + pre + + "_" + + "IMPGrid_SS_" + + str(f.attributes()[idx]) + + ".txt", + arrSS, + fmt=numformatSS, + delimiter=" ", + header=headerSS, + comments="", + ) dataset = None dataset2 = None - arrmatsave = arrmat[1: arrmat.shape[0], :] - np.savetxt(outputDir + '/' + pre + '_' + 'IMPGrid_isotropic.txt', arrmatsave, - fmt=numformat2, delimiter=' ', header=headerIso, comments='') - - if attrTable: + arrmatsave = arrmat[1 : arrmat.shape[0], :] + np.savetxt( + outputDir + "/" + pre + "_" + "IMPGrid_isotropic.txt", + arrmatsave, + fmt=numformat2, + delimiter=" ", + header=headerIso, + comments="", + ) + + if attrTable: feedback.setProgressText("Adding result to layer attribute table") self.addattr(vlayer, arrmatsave, headerIso, pre, feedback, idx) @@ -445,12 +736,16 @@ def addattr(self, vlayer, matdata, header, pre, feedback, idx): if caps & QgsVectorDataProvider.Capability.AddAttributes: line_split = header.split() for x in range(1, len(line_split)): - vlayer.dataProvider().addAttributes([QgsField(pre + '_' + line_split[x], QVariant.Double)]) + vlayer.dataProvider().addAttributes( + [QgsField(pre + "_" + line_split[x], QVariant.Double)] + ) vlayer.commitChanges() vlayer.updateFields() attr_dict = {} else: - raise QgsProcessingException("Vector Layer does not support adding attributes") + raise QgsProcessingException( + "Vector Layer does not support adding attributes" + ) features = vlayer.getFeatures() @@ -460,11 +755,13 @@ def addattr(self, vlayer, matdata, header, pre, feedback, idx): wo = np.where(f.attributes()[idx] == matdata[:, 0]) if wo[0] >= 0: for x in range(1, matdata.shape[1]): - attr_dict[current_index_length + x - 1] = float(matdata[wo[0], x]) + attr_dict[current_index_length + x - 1] = float( + matdata[wo[0], x] + ) vlayer.dataProvider().changeAttributeValues({id: attr_dict}) - + def name(self): - return 'Urban Morphology: Morphometric Calculator (Grid)' + return "Urban Morphology: Morphometric Calculator (Grid)" def displayName(self): return self.tr(self.name()) @@ -473,33 +770,36 @@ def group(self): return self.tr(self.groupId()) def groupId(self): - return 'Pre-Processor' + return "Pre-Processor" def shortHelpString(self): - return self.tr('The Morphometric Calculator (Grid) plugin calculates various morphometric parameters based on digital surface models for ' - 'separate vector polygons. The polygons should preferable be squares or any other regular shape. To create such a grid, built in functions ' - 'in QGIS can be used (see Vector -> Research Tools -> Create Grid from the QGIS menu-bar). The morphometric parameters are used to describe the ' - 'roughness of a surface and are included in various local and mesoscale climate models (e.g. Grimmond and Oke 1999). They may vary depending ' - 'on what angle (wind direction) you are interested in. Thus, this plugin is able to derive the parameters for different directions. ' - 'Preferably, a ground and 3D-object DSM and DEM should be used as input data. The 3D objects are usually buildings but can also be 3D ' - 'vegetation (i.e. trees and bushes). It is also possible to derive the parameters from a 3D object DSM with no ground heights.\n' - '-------------\n' - 'Grimmond CSB and Oke TR (1999) Aerodynamic properties of urban areas derived from analysis of surface form. J Appl Meteorol 38: 1262-1292' - '\n' - 'Full manual available via the Help-button.') - + return self.tr( + "The Morphometric Calculator (Grid) plugin calculates various morphometric parameters based on digital surface models for " + "separate vector polygons. The polygons should preferable be squares or any other regular shape. To create such a grid, built in functions " + "in QGIS can be used (see Vector -> Research Tools -> Create Grid from the QGIS menu-bar). The morphometric parameters are used to describe the " + "roughness of a surface and are included in various local and mesoscale climate models (e.g. Grimmond and Oke 1999). They may vary depending " + "on what angle (wind direction) you are interested in. Thus, this plugin is able to derive the parameters for different directions. " + "Preferably, a ground and 3D-object DSM and DEM should be used as input data. The 3D objects are usually buildings but can also be 3D " + "vegetation (i.e. trees and bushes). It is also possible to derive the parameters from a 3D object DSM with no ground heights.\n" + "-------------\n" + "Grimmond CSB and Oke TR (1999) Aerodynamic properties of urban areas derived from analysis of surface form. J Appl Meteorol 38: 1262-1292" + "\n" + "Full manual available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/pre-processor/Urban%20Morphology%20Morphometric%20Calculator%20(Grid).html" return url def tr(self, string): - return QCoreApplication.translate('Processing', string) + return QCoreApplication.translate("Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/ImageMorphIcon.png") return icon def createInstance(self): - return ProcessingImageMorphParmsAlgorithm() \ No newline at end of file + return ProcessingImageMorphParmsAlgorithm() diff --git a/preprocessor/imagemorphparmspoint_algorithm.py b/preprocessor/imagemorphparmspoint_algorithm.py index 57257f3..123ae3c 100644 --- a/preprocessor/imagemorphparmspoint_algorithm.py +++ b/preprocessor/imagemorphparmspoint_algorithm.py @@ -22,32 +22,34 @@ ***************************************************************************/ """ -__author__ = 'Fredrik Lindberg' -__date__ = '2020-04-02' -__copyright__ = '(C) 2020 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2020-04-02" +__copyright__ = "(C) 2020 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QVariant -from qgis.core import (QgsProcessing, - QgsProcessingAlgorithm, - QgsProcessingParameterPoint, - QgsProcessingParameterString, - QgsProcessingParameterBoolean, - QgsProcessingParameterNumber, - QgsProcessingParameterFolderDestination, - QgsProcessingParameterRasterLayer, - QgsProcessingParameterEnum, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterVectorDestination, - QgsProcessingException, - QgsVectorLayer, - QgsFeature, - QgsGeometry, - QgsPointXY, - QgsVectorFileWriter) +from qgis.core import ( + QgsProcessing, + QgsProcessingAlgorithm, + QgsProcessingParameterPoint, + QgsProcessingParameterString, + QgsProcessingParameterBoolean, + QgsProcessingParameterNumber, + QgsProcessingParameterFolderDestination, + QgsProcessingParameterRasterLayer, + QgsProcessingParameterEnum, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterVectorDestination, + QgsProcessingException, + QgsVectorLayer, + QgsFeature, + QgsGeometry, + QgsPointXY, + QgsVectorFileWriter, +) from qgis.PyQt.QtGui import QIcon from osgeo import gdal @@ -66,93 +68,176 @@ class ProcessingImageMorphParmsPointAlgorithm(QgsProcessingAlgorithm): This algorithm is a processing version of Image Morphometric Calculator Point """ - USE_POINTLAYER = 'USE_POINTLAYER' - INPUT_POINT = 'INPUT_POINT' - INPUT_POINTLAYER = 'INPUT_POINTLAYER' - INPUT_DISTANCE = 'INPUT_DISTANCE' - INPUT_INTERVAL = 'INPUT_INTERVAL' - INPUT_DSM = 'INPUT_DSM' - INPUT_DEM = 'INPUT_DEM' - INPUT_DSMBUILD = 'INPUT_DSMBUILD' - USE_DSMBUILD = 'USE_DSM_BUILD' - ROUGH = 'ROUGH' - FILE_PREFIX = 'FILE_PREFIX' - OUTPUT_DIR = 'OUTPUT_DIR' - OUTPUT_POLYGON = 'OUTPUT_POLYGON' - #SAVE_POINT = 'SAVE_POINT' - OUTPUT_POINT = 'OUTPUT_POINT' - + USE_POINTLAYER = "USE_POINTLAYER" + INPUT_POINT = "INPUT_POINT" + INPUT_POINTLAYER = "INPUT_POINTLAYER" + INPUT_DISTANCE = "INPUT_DISTANCE" + INPUT_INTERVAL = "INPUT_INTERVAL" + INPUT_DSM = "INPUT_DSM" + INPUT_DEM = "INPUT_DEM" + INPUT_DSMBUILD = "INPUT_DSMBUILD" + USE_DSMBUILD = "USE_DSM_BUILD" + ROUGH = "ROUGH" + FILE_PREFIX = "FILE_PREFIX" + OUTPUT_DIR = "OUTPUT_DIR" + OUTPUT_POLYGON = "OUTPUT_POLYGON" + # SAVE_POINT = 'SAVE_POINT' + OUTPUT_POINT = "OUTPUT_POINT" + def initAlgorithm(self, config): - - self.rough = ((self.tr('Rule of thumb'), '0'), - (self.tr('Raupach (1994/95)'), '1'), - (self.tr('Simplified Bottema (1995)'), '2'), - (self.tr('MacDonald et al. (1998)'), '3'), - (self.tr('Millward-Hopkins et al. (2011)'), '4'), - (self.tr('Kanda et al. (2013)'), '5')) - self.addParameter(QgsProcessingParameterPoint(self.INPUT_POINT, - self.tr('Point of interest'), optional=True)) + + self.rough = ( + (self.tr("Rule of thumb"), "0"), + (self.tr("Raupach (1994/95)"), "1"), + (self.tr("Simplified Bottema (1995)"), "2"), + (self.tr("MacDonald et al. (1998)"), "3"), + (self.tr("Millward-Hopkins et al. (2011)"), "4"), + (self.tr("Kanda et al. (2013)"), "5"), + ) + self.addParameter( + QgsProcessingParameterPoint( + self.INPUT_POINT, self.tr("Point of interest"), optional=True + ) + ) # self.addParameter(QgsProcessingParameterBoolean(self.USE_POINTLAYER, - # self.tr("Obtain point of interest from point in vector layer"), defaultValue=False)) - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_POINTLAYER, - self.tr('Point vector layer'), [QgsProcessing.SourceType.TypeVectorPoint], optional=True)) - self.addParameter(QgsProcessingParameterNumber(self.INPUT_DISTANCE, - self.tr('Search distance (meter)'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(200), False, minValue=0)) - self.addParameter(QgsProcessingParameterNumber(self.INPUT_INTERVAL, - self.tr('Wind direction search interval (degree)'), - QgsProcessingParameterNumber.Type.Double, - QVariant(5), False, minValue=0.1, maxValue=360.)) - self.addParameter(QgsProcessingParameterBoolean(self.USE_DSMBUILD, - self.tr("Raster DSM (only 3D building or vegetation objects) exist"), defaultValue=False)) + # self.tr("Obtain point of interest from point in vector layer"), defaultValue=False)) + self.addParameter( + QgsProcessingParameterFeatureSource( + self.INPUT_POINTLAYER, + self.tr("Point vector layer"), + [QgsProcessing.SourceType.TypeVectorPoint], + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.INPUT_DISTANCE, + self.tr("Search distance (meter)"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(200), + False, + minValue=0, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.INPUT_INTERVAL, + self.tr("Wind direction search interval (degree)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(5), + False, + minValue=0.1, + maxValue=360.0, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.USE_DSMBUILD, + self.tr( + "Raster DSM (only 3D building or vegetation objects) exist" + ), + defaultValue=False, + ) + ) # self.addParameter(QgsProcessingParameterBoolean(self.USE_DSMBUILD, # self.tr("Raster DSM (only 3D building or vegetation objects) exist"), defaultValue=False)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_DSM, - self.tr('Raster DSM (3D objects and ground)'), '', True)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_DEM, - self.tr('Raster DEM (only ground)'), '', True)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_DSMBUILD, - self.tr('Raster DSM (only 3D objects)'), '', True)) - self.addParameter(QgsProcessingParameterEnum(self.ROUGH, - self.tr('Roughness calculation method'), - options=[i[0] for i in self.rough], defaultValue=0)) - self.addParameter(QgsProcessingParameterString(self.FILE_PREFIX, - self.tr('File prefix'))) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_DSM, + self.tr("Raster DSM (3D objects and ground)"), + "", + True, + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_DEM, self.tr("Raster DEM (only ground)"), "", True + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_DSMBUILD, + self.tr("Raster DSM (only 3D objects)"), + "", + True, + ) + ) + self.addParameter( + QgsProcessingParameterEnum( + self.ROUGH, + self.tr("Roughness calculation method"), + options=[i[0] for i in self.rough], + defaultValue=0, + ) + ) + self.addParameter( + QgsProcessingParameterString( + self.FILE_PREFIX, self.tr("File prefix") + ) + ) # self.addParameter(QgsProcessingParameterBoolean(self.SAVE_POINT, # self.tr("Save point of interest as new vector layer"), defaultValue=False)) - self.addParameter(QgsProcessingParameterVectorDestination(self.OUTPUT_POINT, - self.tr("Output point layer (point of interest)"), optional=True, - createByDefault=False)) - self.addParameter(QgsProcessingParameterVectorDestination(self.OUTPUT_POLYGON, - self.tr("Output polygon layer (area of interest)"), optional=True, - createByDefault=False)) - self.addParameter(QgsProcessingParameterFolderDestination(self.OUTPUT_DIR, - self.tr('Output folder'))) + self.addParameter( + QgsProcessingParameterVectorDestination( + self.OUTPUT_POINT, + self.tr("Output point layer (point of interest)"), + optional=True, + createByDefault=False, + ) + ) + self.addParameter( + QgsProcessingParameterVectorDestination( + self.OUTPUT_POLYGON, + self.tr("Output polygon layer (area of interest)"), + optional=True, + createByDefault=False, + ) + ) + self.addParameter( + QgsProcessingParameterFolderDestination( + self.OUTPUT_DIR, self.tr("Output folder") + ) + ) self.plugin_dir = os.path.dirname(__file__) - if not (os.path.isdir(self.plugin_dir + '/data')): - os.mkdir(self.plugin_dir + '/data') + if not (os.path.isdir(self.plugin_dir + "/data")): + os.mkdir(self.plugin_dir + "/data") def processAlgorithm(self, parameters, context, feedback): # InputParameters - usePointlayer = self. parameterAsBool(parameters, self.USE_POINTLAYER, context) + usePointlayer = self.parameterAsBool( + parameters, self.USE_POINTLAYER, context + ) inputPoint = None - inputPointLayer = self.parameterAsVectorLayer(parameters, self.INPUT_POINTLAYER, context) - inputDistance = self.parameterAsDouble(parameters, self.INPUT_DISTANCE, context) - inputInterval = self.parameterAsDouble(parameters, self.INPUT_INTERVAL, context) - useDsmBuild = self.parameterAsBool(parameters, self.USE_DSMBUILD, context) + inputPointLayer = self.parameterAsVectorLayer( + parameters, self.INPUT_POINTLAYER, context + ) + inputDistance = self.parameterAsDouble( + parameters, self.INPUT_DISTANCE, context + ) + inputInterval = self.parameterAsDouble( + parameters, self.INPUT_INTERVAL, context + ) + useDsmBuild = self.parameterAsBool( + parameters, self.USE_DSMBUILD, context + ) dsmlayer = None demlayer = None # dsm_build = None ro = self.parameterAsString(parameters, self.ROUGH, context) - filePrefix = self.parameterAsString(parameters, self.FILE_PREFIX, context) - outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) - outputPolygon = self.parameterAsOutputLayer(parameters, self.OUTPUT_POLYGON, context) - #savePoint = self.parameterAsBool(parameters, self.SAVE_POINT, context) + filePrefix = self.parameterAsString( + parameters, self.FILE_PREFIX, context + ) + outputDir = self.parameterAsString( + parameters, self.OUTPUT_DIR, context + ) + outputPolygon = self.parameterAsOutputLayer( + parameters, self.OUTPUT_POLYGON, context + ) + # savePoint = self.parameterAsBool(parameters, self.SAVE_POINT, context) outputPoint = None - - if parameters['OUTPUT_DIR'] == 'TEMPORARY_OUTPUT': + + if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): os.mkdir(outputDir) @@ -160,22 +245,30 @@ def processAlgorithm(self, parameters, context, feedback): # if lcgrid is not None: if inputPointLayer is None: feedback.setProgressText("Point location obtained manually") - inputPoint = self.parameterAsPoint(parameters, self.INPUT_POINT, context) + inputPoint = self.parameterAsPoint( + parameters, self.INPUT_POINT, context + ) x = float(inputPoint[0]) y = float(inputPoint[1]) else: # inputPointLayer = self.parameterAsVectorLayer(parameters, self.INPUT_POINTLAYER, context) - feedback.setProgressText("Point location obtained from point in vector layer") + feedback.setProgressText( + "Point location obtained from point in vector layer" + ) if not inputPointLayer.selectedFeatures(): if inputPointLayer.featureCount() == 0: - raise QgsProcessingException("No point found in the vector layer. Add one point in the your layer of manually choose one.") + raise QgsProcessingException( + "No point found in the vector layer. Add one point in the your layer of manually choose one." + ) else: for poi in inputPointLayer.getFeatures(): y = poi.geometry().centroid().asPoint().y() x = poi.geometry().centroid().asPoint().x() else: if inputPointLayer.selectedFeatureCount() != 1: - raise QgsProcessingException("More than one point of interest is selected. Select only one feature point and try again.") + raise QgsProcessingException( + "More than one point of interest is selected. Select only one feature point and try again." + ) for poi in inputPointLayer.selectedFeatures(): y = poi.geometry().centroid().asPoint().y() x = poi.geometry().centroid().asPoint().x() @@ -184,34 +277,48 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText("x = " + str(x)) feedback.setProgressText("y = " + str(y)) - + r = inputDistance - - if useDsmBuild: # Only building heights - dsmlayer = self.parameterAsRasterLayer(parameters, self.INPUT_DSMBUILD, context) + + if useDsmBuild: # Only building heights + dsmlayer = self.parameterAsRasterLayer( + parameters, self.INPUT_DSMBUILD, context + ) if dsmlayer is None: - raise QgsProcessingException("No valid building DSM raster layer is selected") + raise QgsProcessingException( + "No valid building DSM raster layer is selected" + ) provider = dsmlayer.dataProvider() filePath_dsm_build = str(provider.dataSourceUri()) bigraster = gdal.Open(filePath_dsm_build) bbox = (x - r, y + r, x + r, y - r) - gdal.Translate(self.plugin_dir + '/data/clipdsm.tif', bigraster, projWin=bbox) + gdal.Translate( + self.plugin_dir + "/data/clipdsm.tif", bigraster, projWin=bbox + ) bigraster = None - dataset = gdal.Open(self.plugin_dir + '/data/clipdsm.tif') + dataset = gdal.Open(self.plugin_dir + "/data/clipdsm.tif") dsm = dataset.ReadAsArray().astype(float) sizex = dsm.shape[0] sizey = dsm.shape[1] dem = np.zeros((sizex, sizey)) else: # Both building ground heights - dsmlayer = self.parameterAsRasterLayer(parameters, self.INPUT_DSM, context) - demlayer = self.parameterAsRasterLayer(parameters, self.INPUT_DEM, context) + dsmlayer = self.parameterAsRasterLayer( + parameters, self.INPUT_DSM, context + ) + demlayer = self.parameterAsRasterLayer( + parameters, self.INPUT_DEM, context + ) if dsmlayer is None: - raise QgsProcessingException("No valid ground and building DSM raster layer is selected") + raise QgsProcessingException( + "No valid ground and building DSM raster layer is selected" + ) if demlayer is None: - raise QgsProcessingException("No valid ground DEM raster layer is selected") + raise QgsProcessingException( + "No valid ground DEM raster layer is selected" + ) provider = dsmlayer.dataProvider() filePath_dsm = str(provider.dataSourceUri()) @@ -219,44 +326,57 @@ def processAlgorithm(self, parameters, context, feedback): filePath_dem = str(provider.dataSourceUri()) bigraster = gdal.Open(filePath_dsm) bbox = (x - r, y + r, x + r, y - r) - gdal.Translate(self.plugin_dir + '/data/clipdsm.tif', bigraster, projWin=bbox) + gdal.Translate( + self.plugin_dir + "/data/clipdsm.tif", bigraster, projWin=bbox + ) bigraster = gdal.Open(filePath_dem) bbox = (x - r, y + r, x + r, y - r) - gdal.Translate(self.plugin_dir + '/data/clipdem.tif', bigraster, projWin=bbox) + gdal.Translate( + self.plugin_dir + "/data/clipdem.tif", bigraster, projWin=bbox + ) - dataset = gdal.Open(self.plugin_dir + '/data/clipdsm.tif') + dataset = gdal.Open(self.plugin_dir + "/data/clipdsm.tif") dsm = dataset.ReadAsArray().astype(float) - dataset2 = gdal.Open(self.plugin_dir + '/data/clipdem.tif') + dataset2 = gdal.Open(self.plugin_dir + "/data/clipdem.tif") dem = dataset2.ReadAsArray().astype(float) - if not (dsm.shape[0] == dem.shape[0]) & (dsm.shape[1] == dem.shape[1]): - raise QgsProcessingException("All grids must be of same extent and resolution") + if not (dsm.shape[0] == dem.shape[0]) & ( + dsm.shape[1] == dem.shape[1] + ): + raise QgsProcessingException( + "All grids must be of same extent and resolution" + ) geotransform = dataset.GetGeoTransform() scale = 1 / geotransform[1] degree = float(inputInterval) nd = dataset.GetRasterBand(1).GetNoDataValue() - nodata_test = (dsm == nd) - + nodata_test = dsm == nd + if nodata_test.any(): - raise QgsProcessingException("NoData values present within the radius (" + str(int(r)) + - " m) from point of interest. Extend your raster(s) or move point.") + raise QgsProcessingException( + "NoData values present within the radius (" + + str(int(r)) + + " m) from point of interest. Extend your raster(s) or move point." + ) else: - immorphresult = morph.imagemorphparam_v2(dsm, dem, scale, 1, degree, feedback, 1) + immorphresult = morph.imagemorphparam_v2( + dsm, dem, scale, 1, degree, feedback, 1 + ) # #Calculate Z0m and Zdm depending on the Z0 method if int(ro) == 0: - Roughnessmethod = 'RT' + Roughnessmethod = "RT" elif int(ro) == 1: - Roughnessmethod = 'Rau' + Roughnessmethod = "Rau" elif int(ro) == 2: - Roughnessmethod = 'Bot' + Roughnessmethod = "Bot" elif int(ro) == 3: - Roughnessmethod = 'Mac' + Roughnessmethod = "Mac" elif int(ro) == 4: - Roughnessmethod = 'Mho' + Roughnessmethod = "Mho" else: - Roughnessmethod = 'Kan' + Roughnessmethod = "Kan" zH = immorphresult["zH"] fai = immorphresult["fai"] @@ -264,63 +384,111 @@ def processAlgorithm(self, parameters, context, feedback): zMax = immorphresult["zHmax"] zSdev = immorphresult["zH_sd"] - zd, z0 = rg.RoughnessCalcMany(Roughnessmethod, zH, fai, pai, zMax, zSdev) + zd, z0 = rg.RoughnessCalcMany( + Roughnessmethod, zH, fai, pai, zMax, zSdev + ) # save to file pre = filePrefix - header = ' Wd pai fai zH zHmax zHstd zd z0' - numformat = '%3d %4.3f %4.3f %5.3f %5.3f %5.3f %5.3f %5.3f' - arr = np.concatenate((immorphresult["deg"], immorphresult["pai"], immorphresult["fai"], - immorphresult["zH"], immorphresult["zHmax"], immorphresult["zH_sd"], zd, z0), axis=1) - np.savetxt(outputDir + '/' + pre + '_' + 'IMPPoint_anisotropic.txt', arr, - fmt=numformat, delimiter=' ', header=header, comments='') + header = " Wd pai fai zH zHmax zHstd zd z0" + numformat = "%3d %4.3f %4.3f %5.3f %5.3f %5.3f %5.3f %5.3f" + arr = np.concatenate( + ( + immorphresult["deg"], + immorphresult["pai"], + immorphresult["fai"], + immorphresult["zH"], + immorphresult["zHmax"], + immorphresult["zH_sd"], + zd, + z0, + ), + axis=1, + ) + np.savetxt( + outputDir + "/" + pre + "_" + "IMPPoint_anisotropic.txt", + arr, + fmt=numformat, + delimiter=" ", + header=header, + comments="", + ) zHall = immorphresult["zH_all"] faiall = immorphresult["fai_all"] paiall = immorphresult["pai_all"] zMaxall = immorphresult["zHmax_all"] zSdevall = immorphresult["zH_sd_all"] - zdall, z0all = rg.RoughnessCalc(Roughnessmethod, zHall, faiall, paiall, zMaxall, zSdevall) + zdall, z0all = rg.RoughnessCalc( + Roughnessmethod, zHall, faiall, paiall, zMaxall, zSdevall + ) # If zd and z0 are lower than open country, set to open country if zdall < 0.2: zdall = 0.2 if z0all < 0.03: z0all = 0.03 - + # If pai is larger than 0 and fai is zero, set fai to 0.001. Issue # 164 - if paiall > 0.: - if faiall == 0.: + if paiall > 0.0: + if faiall == 0.0: faiall = 0.001 # adding wai area to isotrophic (wall area index) - total = 100. / (int(dsm.shape[0] * dsm.shape[1])) - wallarea = np.sum(wa.findwalls(dsm, 2., feedback, total)) - gridArea = (abs(bbox[2]-bbox[0]))*(abs(bbox[1]-bbox[3])) + total = 100.0 / (int(dsm.shape[0] * dsm.shape[1])) + wallarea = np.sum(wa.findwalls(dsm, 2.0, feedback, total)) + gridArea = (abs(bbox[2] - bbox[0])) * (abs(bbox[1] - bbox[3])) wai = wallarea / gridArea - - header = ' pai fai zH zHmax zHstd zd z0 wai' - numformat = '%4.3f %4.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f' - arr2 = np.array([[immorphresult["pai_all"], immorphresult["fai_all"], immorphresult["zH_all"], - immorphresult["zHmax_all"], immorphresult["zH_sd_all"], zdall, z0all, wai]]) - np.savetxt(outputDir + '/' + pre + '_' + 'IMPPoint_isotropic.txt', arr2, - fmt=numformat, delimiter=' ', header=header, comments='') + + header = " pai fai zH zHmax zHstd zd z0 wai" + numformat = "%4.3f %4.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f" + arr2 = np.array( + [ + [ + immorphresult["pai_all"], + immorphresult["fai_all"], + immorphresult["zH_all"], + immorphresult["zHmax_all"], + immorphresult["zH_sd_all"], + zdall, + z0all, + wai, + ] + ] + ) + np.savetxt( + outputDir + "/" + pre + "_" + "IMPPoint_isotropic.txt", + arr2, + fmt=numformat, + delimiter=" ", + header=header, + comments="", + ) dataset = None dataset2 = None - + crs = str(dsmlayer.crs().authid()) self.create_poly_layer(outputPolygon, x, y, r, crs) - #if savePoint: - outputPoint = self.parameterAsOutputLayer(parameters, self.OUTPUT_POINT, context) + # if savePoint: + outputPoint = self.parameterAsOutputLayer( + parameters, self.OUTPUT_POINT, context + ) self.create_point_layer(outputPoint, x, y, crs) - results = {self.OUTPUT_DIR: outputDir, self.OUTPUT_POLYGON: outputPolygon, self.OUTPUT_POINT: outputPoint} - #else: + results = { + self.OUTPUT_DIR: outputDir, + self.OUTPUT_POLYGON: outputPolygon, + self.OUTPUT_POINT: outputPoint, + } + # else: # results = {self.OUTPUT_DIR: outputDir, self.OUTPUT_POLYGON: outputPolygon} return results def create_point_layer(self, outputPoint, x, y, crs): - uri = "Point?field=id:integer&field=x:double&field=y:double&index=yes&crs=" + crs + uri = ( + "Point?field=id:integer&field=x:double&field=y:double&index=yes&crs=" + + crs + ) self.poiLayer = QgsVectorLayer(uri, "Point of Interest", "memory") self.provider = self.poiLayer.dataProvider() # create the feature @@ -334,15 +502,21 @@ def create_point_layer(self, outputPoint, x, y, crs): fields = self.poiLayer.fields() - writer = QgsVectorFileWriter(outputPoint, "CP1250", fields, self.provider.wkbType(), - self.provider.crs(), "ESRI shapefile") + writer = QgsVectorFileWriter( + outputPoint, + "CP1250", + fields, + self.provider.wkbType(), + self.provider.crs(), + "ESRI shapefile", + ) self.poiLayer.selectAll() selection = self.poiLayer.selectedFeatures() for feature in selection: writer.addFeature(feature) del writer - + def create_poly_layer(self, outputPolygon, x, y, r, crs): uri = "Polygon?field=id:integer&index=yes&crs=" + crs self.polyLayer = QgsVectorLayer(uri, "Study area", "memory") @@ -354,7 +528,9 @@ def create_poly_layer(self, outputPolygon, x, y, r, crs): # Assign feature the buffered geometry radius = r - featurepoly.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x, y)).buffer(radius, 1000)) # fix issue #400 + featurepoly.setGeometry( + QgsGeometry.fromPointXY(QgsPointXY(x, y)).buffer(radius, 1000) + ) # fix issue #400 featurepoly.setAttributes([fc]) self.polyLayer.startEditing() self.polyLayer.addFeature(featurepoly) @@ -362,17 +538,23 @@ def create_poly_layer(self, outputPolygon, x, y, r, crs): prov = self.provider fields = self.polyLayer.fields() - writer = QgsVectorFileWriter(outputPolygon, "CP1250", fields, prov.wkbType(), - prov.crs(), "ESRI shapefile") + writer = QgsVectorFileWriter( + outputPolygon, + "CP1250", + fields, + prov.wkbType(), + prov.crs(), + "ESRI shapefile", + ) self.polyLayer.selectAll() selection = self.polyLayer.selectedFeatures() for feature in selection: writer.addFeature(feature) del writer - + def name(self): - return 'Urban Morphology: Morphometric Calculator (Point)' + return "Urban Morphology: Morphometric Calculator (Point)" def displayName(self): return self.tr(self.name()) @@ -381,25 +563,30 @@ def group(self): return self.tr(self.groupId()) def groupId(self): - return 'Pre-Processor' + return "Pre-Processor" def shortHelpString(self): - return self.tr('The Morphometric Calculator (Point) plugin calculates various morphometric parameters based on digital surface models. These morphometric parameters are used to describe the roughness of a surface and are included in various local and mesoscale climate models (e.g. Grimmond and Oke 1999). They may vary depending on what angle (wind direction) you are interested in. Thus, this plugin is able to derive the parameters for different directions. Preferably, a ground and 3D-object DSM and DEM should be used as input data. The 3D objects are usually buildings but can also be 3D vegetation (i.e. trees and bushes). It is also possible to derive the parameters from a 3D object DSM with no ground heights.\n' - '-------------\n' - 'Grimmond CSB and Oke TR (1999) Aerodynamic properties of urban areas derived from analysis of surface form. J Appl Meteorol 38: 1262-1292
' - '---------------\n' - 'Full manual available via the Help-button.') + return self.tr( + "The Morphometric Calculator (Point) plugin calculates various morphometric parameters based on digital surface models. These morphometric parameters are used to describe the roughness of a surface and are included in various local and mesoscale climate models (e.g. Grimmond and Oke 1999). They may vary depending on what angle (wind direction) you are interested in. Thus, this plugin is able to derive the parameters for different directions. Preferably, a ground and 3D-object DSM and DEM should be used as input data. The 3D objects are usually buildings but can also be 3D vegetation (i.e. trees and bushes). It is also possible to derive the parameters from a 3D object DSM with no ground heights.\n" + "-------------\n" + "Grimmond CSB and Oke TR (1999) Aerodynamic properties of urban areas derived from analysis of surface form. J Appl Meteorol 38: 1262-1292
" + "---------------\n" + "Full manual available via the Help-button." + ) + def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/pre-processor/Urban%20Morphology%20Morphometric%20Calculator%20(Point).html" return url def tr(self, string): - return QCoreApplication.translate('Processing', string) + return QCoreApplication.translate("Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/ImageMorphIconPoint.png") return icon def createInstance(self): - return ProcessingImageMorphParmsPointAlgorithm() \ No newline at end of file + return ProcessingImageMorphParmsPointAlgorithm() diff --git a/preprocessor/landcoverfraction_algorithm.py b/preprocessor/landcoverfraction_algorithm.py index 613576c..9c63ca5 100644 --- a/preprocessor/landcoverfraction_algorithm.py +++ b/preprocessor/landcoverfraction_algorithm.py @@ -22,30 +22,32 @@ ***************************************************************************/ """ -__author__ = 'Fredrik Lindberg' -__date__ = '2020-04-02' -__copyright__ = '(C) 2020 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2020-04-02" +__copyright__ = "(C) 2020 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QVariant -from qgis.core import (QgsProcessing, - QgsProcessingAlgorithm, - QgsProcessingParameterString, - QgsProcessingParameterBoolean, - QgsProcessingParameterNumber, - QgsProcessingParameterFolderDestination, - QgsProcessingParameterRasterLayer, - QgsProcessingParameterEnum, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterField, - QgsProcessingException, - QgsFeature, - QgsVectorFileWriter, - QgsVectorDataProvider, - QgsField) +from qgis.core import ( + QgsProcessing, + QgsProcessingAlgorithm, + QgsProcessingParameterString, + QgsProcessingParameterBoolean, + QgsProcessingParameterNumber, + QgsProcessingParameterFolderDestination, + QgsProcessingParameterRasterLayer, + QgsProcessingParameterEnum, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterField, + QgsProcessingException, + QgsFeature, + QgsVectorFileWriter, + QgsVectorDataProvider, + QgsField, +) from qgis.PyQt.QtGui import QIcon from osgeo import gdal, ogr @@ -63,120 +65,209 @@ class ProcessingLandCoverFractionAlgorithm(QgsProcessingAlgorithm): This algorithm is a processing version of Land Cover Fraction Grid """ - INPUT_POLYGONLAYER = 'INPUT_POLYGONLAYER' - ID_FIELD = 'ID_FIELD' - SERACH_METHOD = 'SEARCH_METHOD' - INPUT_DISTANCE = 'INPUT_DISTANCE' - INPUT_INTERVAL = 'INPUT_INTERVAL' - INPUT_LCGRID = 'INPUT_LCGRID' - TARGET_LC = 'TARGET_LC' - FILE_PREFIX = 'FILE_PREFIX' - OUTPUT_DIR = 'OUTPUT_DIR' - IGNORE_NODATA = 'IGNORE_NODATA' - ATTR_TABLE = 'ATTR_TABLE' - - + INPUT_POLYGONLAYER = "INPUT_POLYGONLAYER" + ID_FIELD = "ID_FIELD" + SERACH_METHOD = "SEARCH_METHOD" + INPUT_DISTANCE = "INPUT_DISTANCE" + INPUT_INTERVAL = "INPUT_INTERVAL" + INPUT_LCGRID = "INPUT_LCGRID" + TARGET_LC = "TARGET_LC" + FILE_PREFIX = "FILE_PREFIX" + OUTPUT_DIR = "OUTPUT_DIR" + IGNORE_NODATA = "IGNORE_NODATA" + ATTR_TABLE = "ATTR_TABLE" + def initAlgorithm(self, config): - - self.search = ((self.tr('Search throughout the grid extent (search distance not used)'), '0'), - (self.tr('Search from grid centroid'), '1')) - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_POLYGONLAYER, - self.tr('Vector polygon grid'), [QgsProcessing.SourceType.TypeVectorPolygon])) - self.addParameter(QgsProcessingParameterField(self.ID_FIELD, - self.tr('ID field'),'', self.INPUT_POLYGONLAYER, QgsProcessingParameterField.DataType.Numeric)) - self.addParameter(QgsProcessingParameterEnum(self.SERACH_METHOD, - self.tr('Search method'), - options=[i[0] for i in self.search], defaultValue=0)) - self.addParameter(QgsProcessingParameterNumber(self.INPUT_DISTANCE, - self.tr('Search distance from grid cell centroid (meter)'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(200), False, minValue=0)) - self.addParameter(QgsProcessingParameterNumber(self.INPUT_INTERVAL, - self.tr('Wind direction search interval (degree)'), - QgsProcessingParameterNumber.Type.Double, - QVariant(5), False, minValue=0.1, maxValue=360.)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_LCGRID, - self.tr('UMEP formatted land cover grid (see help for more info)'), None, False)) - self.addParameter(QgsProcessingParameterString(self.FILE_PREFIX, - self.tr('File prefix'))) - self.addParameter(QgsProcessingParameterBoolean(self.TARGET_LC, - self.tr("Calculate fractions for TARGET (9 classes instead of 7)"), defaultValue=False)) - self.addParameter(QgsProcessingParameterBoolean(self.IGNORE_NODATA, - self.tr("Ignore NoData pixels"), defaultValue=True)) - self.addParameter(QgsProcessingParameterBoolean(self.ATTR_TABLE, - self.tr("Add result to polygon grid attribute table"), defaultValue=False)) - self.addParameter(QgsProcessingParameterFolderDestination(self.OUTPUT_DIR, - self.tr('Output folder'))) + + self.search = ( + ( + self.tr( + "Search throughout the grid extent (search distance not used)" + ), + "0", + ), + (self.tr("Search from grid centroid"), "1"), + ) + self.addParameter( + QgsProcessingParameterFeatureSource( + self.INPUT_POLYGONLAYER, + self.tr("Vector polygon grid"), + [QgsProcessing.SourceType.TypeVectorPolygon], + ) + ) + self.addParameter( + QgsProcessingParameterField( + self.ID_FIELD, + self.tr("ID field"), + "", + self.INPUT_POLYGONLAYER, + QgsProcessingParameterField.DataType.Numeric, + ) + ) + self.addParameter( + QgsProcessingParameterEnum( + self.SERACH_METHOD, + self.tr("Search method"), + options=[i[0] for i in self.search], + defaultValue=0, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.INPUT_DISTANCE, + self.tr("Search distance from grid cell centroid (meter)"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(200), + False, + minValue=0, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.INPUT_INTERVAL, + self.tr("Wind direction search interval (degree)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(5), + False, + minValue=0.1, + maxValue=360.0, + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_LCGRID, + self.tr( + "UMEP formatted land cover grid (see help for more info)" + ), + None, + False, + ) + ) + self.addParameter( + QgsProcessingParameterString( + self.FILE_PREFIX, self.tr("File prefix") + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.TARGET_LC, + self.tr( + "Calculate fractions for TARGET (9 classes instead of 7)" + ), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.IGNORE_NODATA, + self.tr("Ignore NoData pixels"), + defaultValue=True, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.ATTR_TABLE, + self.tr("Add result to polygon grid attribute table"), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterFolderDestination( + self.OUTPUT_DIR, self.tr("Output folder") + ) + ) self.plugin_dir = os.path.dirname(__file__) - if not (os.path.isdir(self.plugin_dir + '/data')): - os.mkdir(self.plugin_dir + '/data') - self.dir_poly = self.plugin_dir + '/data/poly_temp.shp' + if not (os.path.isdir(self.plugin_dir + "/data")): + os.mkdir(self.plugin_dir + "/data") + self.dir_poly = self.plugin_dir + "/data/poly_temp.shp" def processAlgorithm(self, parameters, context, feedback): # InputParameters - inputPolygonlayer = self.parameterAsVectorLayer(parameters, self.INPUT_POLYGONLAYER, context) + inputPolygonlayer = self.parameterAsVectorLayer( + parameters, self.INPUT_POLYGONLAYER, context + ) idField = self.parameterAsFields(parameters, self.ID_FIELD, context) - searchMethod = self.parameterAsString(parameters, self.SERACH_METHOD, context) - inputDistance = self.parameterAsDouble(parameters, self.INPUT_DISTANCE, context) - inputInterval = self.parameterAsDouble(parameters, self.INPUT_INTERVAL, context) + searchMethod = self.parameterAsString( + parameters, self.SERACH_METHOD, context + ) + inputDistance = self.parameterAsDouble( + parameters, self.INPUT_DISTANCE, context + ) + inputInterval = self.parameterAsDouble( + parameters, self.INPUT_INTERVAL, context + ) dsmlayer = None - useTarget = self. parameterAsBool(parameters, self.TARGET_LC, context) - filePrefix = self.parameterAsString(parameters, self.FILE_PREFIX, context) + useTarget = self.parameterAsBool(parameters, self.TARGET_LC, context) + filePrefix = self.parameterAsString( + parameters, self.FILE_PREFIX, context + ) attrTable = self.parameterAsBool(parameters, self.ATTR_TABLE, context) - ignoreNodata = self.parameterAsBool(parameters, self.IGNORE_NODATA, context) - outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) - - if parameters['OUTPUT_DIR'] == 'TEMPORARY_OUTPUT': + ignoreNodata = self.parameterAsBool( + parameters, self.IGNORE_NODATA, context + ) + outputDir = self.parameterAsString( + parameters, self.OUTPUT_DIR, context + ) + + if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): os.mkdir(outputDir) r = inputDistance - + degree = float(inputInterval) pre = filePrefix - imp_point = 0 # set to 1 when user for LCF point + imp_point = 0 # set to 1 when user for LCF point imid = int(searchMethod) # arrmat = np.empty((1, 8)) if useTarget: iter = 9 - header = 'Wd Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water IrrGrass Concrete' - numformat = '%3d %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f' - header2 = 'ID Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water IrrGrass Concrete' - numformat2 = '%3d %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f' + header = "Wd Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water IrrGrass Concrete" + numformat = ( + "%3d %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f" + ) + header2 = "ID Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water IrrGrass Concrete" + numformat2 = ( + "%3d %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f" + ) else: iter = 7 - header = 'Wd Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water' - numformat = '%3d %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f' - header2 = 'ID Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water' - numformat2 = '%3d %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f' + header = "Wd Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water" + numformat = "%3d %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f" + header2 = "ID Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water" + numformat2 = "%3d %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f" arrmat = np.empty((1, int(iter + 1))) # temporary fix for mac, ISSUE #15 pf = sys.platform - if pf == 'darwin' or pf == 'linux2' or pf == 'linux': - if not os.path.exists(outputDir + '/' + pre): - os.makedirs(outputDir + '/' + pre) + if pf == "darwin" or pf == "linux2" or pf == "linux": + if not os.path.exists(outputDir + "/" + pre): + os.makedirs(outputDir + "/" + pre) - #poly = inputPolygonlayer + # poly = inputPolygonlayer vlayer = inputPolygonlayer poly_field = idField feedback.setProgressText("poly_field: " + str(poly_field)) prov = vlayer.dataProvider() fields = prov.fields() idx = vlayer.fields().indexFromName(poly_field[0]) - #dir_poly = self.plugin_dir + '/data/poly_temp.shp' + # dir_poly = self.plugin_dir + '/data/poly_temp.shp' nGrids = vlayer.featureCount() index = 1 feedback.setProgressText("Number of grids to analyse: " + str(nGrids)) feedback.setProgressText("idx: " + str(idx)) - dsmlayer = self.parameterAsRasterLayer(parameters, self.INPUT_LCGRID, context) + dsmlayer = self.parameterAsRasterLayer( + parameters, self.INPUT_LCGRID, context + ) if dsmlayer is None: - raise QgsProcessingException("No valid building land cover raster layer is selected") + raise QgsProcessingException( + "No valid building land cover raster layer is selected" + ) provider = dsmlayer.dataProvider() filePath_dsm_build = str(provider.dataSourceUri()) @@ -186,7 +277,7 @@ def processAlgorithm(self, parameters, context, feedback): if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") break - + index += 1 attributes = f.attributes() @@ -201,94 +292,176 @@ def processAlgorithm(self, parameters, context, feedback): x = f.geometry().centroid().asPoint().x() else: r = 0 # Uses as info to separate from IMP point to grid - writer = QgsVectorFileWriter(self.dir_poly, "CP1250", fields, prov.wkbType(), - prov.crs(), "ESRI shapefile") - if writer.hasError() != QgsVectorFileWriter.WriterError.NoError: - raise QgsProcessingException("Error when creating shapefile: ", str(writer.hasError())) + writer = QgsVectorFileWriter( + self.dir_poly, + "CP1250", + fields, + prov.wkbType(), + prov.crs(), + "ESRI shapefile", + ) + if ( + writer.hasError() + != QgsVectorFileWriter.WriterError.NoError + ): + raise QgsProcessingException( + "Error when creating shapefile: ", + str(writer.hasError()), + ) writer.addFeature(feature) del writer - + if imid == 1: bbox = (x - r, y + r, x + r, y - r) else: # Remove gdalwarp cuttoline with gdal.Translate. Cut to envelope of polygon feature VectorDriver = ogr.GetDriverByName("ESRI Shapefile") - Vector = VectorDriver.Open(self.dir_poly, 0) #self.dir_poly + Vector = VectorDriver.Open(self.dir_poly, 0) # self.dir_poly layer = Vector.GetLayer() feature = layer.GetFeature(0) geom = feature.GetGeometryRef() minX, maxX, minY, maxY = geom.GetEnvelope() - bbox = (minX, maxY, maxX, minY) # Reorder bbox to use with gdal_translate + bbox = ( + minX, + maxY, + maxX, + minY, + ) # Reorder bbox to use with gdal_translate Vector.Destroy() # Remove gdalwarp with gdal.Translate # added gdal.Warp() for irregular grids bigraster = gdal.Open(filePath_dsm_build) if imid == 1: - gdal.Translate(self.plugin_dir + '/data/clipdsm.tif', bigraster, projWin=bbox) + gdal.Translate( + self.plugin_dir + "/data/clipdsm.tif", + bigraster, + projWin=bbox, + ) else: clip_spec = gdal.WarpOptions( format="GTiff", cutlineDSName=self.dir_poly, - cropToCutline=True + cropToCutline=True, + ) + gdal.Warp( + self.plugin_dir + "/data/clipdsm.tif", + bigraster, + options=clip_spec, ) - gdal.Warp(self.plugin_dir + '/data/clipdsm.tif', bigraster, options=clip_spec) bigraster = None - dataset = gdal.Open(self.plugin_dir + '/data/clipdsm.tif') + dataset = gdal.Open(self.plugin_dir + "/data/clipdsm.tif") lcgrid = dataset.ReadAsArray().astype(float) nd = dataset.GetRasterBand(1).GetNoDataValue() - nodata_test = (lcgrid == nd) + nodata_test = lcgrid == nd if ignoreNodata: if np.sum(lcgrid) == (lcgrid.shape[0] * lcgrid.shape[1] * nd): - feedback.setProgressText("Grid " + str(f.attributes()[idx]) + " not calculated. Includes Only NoData Pixels") + feedback.setProgressText( + "Grid " + + str(f.attributes()[idx]) + + " not calculated. Includes Only NoData Pixels" + ) cal = 0 else: lcgrid[lcgrid == nd] = 0 - feedback.setProgressText("Grid " + str(f.attributes()[idx]) + " being calculated.") + feedback.setProgressText( + "Grid " + + str(f.attributes()[idx]) + + " being calculated." + ) cal = 1 else: if nodata_test.any(): - feedback.setProgressText("Grid " + str(f.attributes()[idx]) + " not calculated. Includes NoData Pixels") + feedback.setProgressText( + "Grid " + + str(f.attributes()[idx]) + + " not calculated. Includes NoData Pixels" + ) cal = 0 else: cal = 1 - feedback.setProgressText("Grid " + str(f.attributes()[idx]) + " being calculated.") + feedback.setProgressText( + "Grid " + + str(f.attributes()[idx]) + + " being calculated." + ) if cal == 1: - landcoverresult = land.landcover_v2(lcgrid, imid, degree, feedback, imp_point, iter) + landcoverresult = land.landcover_v2( + lcgrid, imid, degree, feedback, imp_point, iter + ) landcoverresult = self.resultcheck(landcoverresult) - arr = np.concatenate((landcoverresult["deg"], landcoverresult["lc_frac"]), axis=1) - np.savetxt(outputDir + '/' + pre + '_' + 'LCFG_anisotropic_result_' + str(f.attributes()[idx]) + '.txt', arr, - fmt=numformat, delimiter=' ', header=header, comments='') + arr = np.concatenate( + (landcoverresult["deg"], landcoverresult["lc_frac"]), + axis=1, + ) + np.savetxt( + outputDir + + "/" + + pre + + "_" + + "LCFG_anisotropic_result_" + + str(f.attributes()[idx]) + + ".txt", + arr, + fmt=numformat, + delimiter=" ", + header=header, + comments="", + ) del arr if useTarget: - arr2 = np.array([f.attributes()[idx], landcoverresult["lc_frac_all"][0, 0], landcoverresult["lc_frac_all"][0, 1], - landcoverresult["lc_frac_all"][0, 2], landcoverresult["lc_frac_all"][0, 3], landcoverresult["lc_frac_all"][0, 4], - landcoverresult["lc_frac_all"][0, 5], landcoverresult["lc_frac_all"][0, 6], landcoverresult["lc_frac_all"][0, 7], - landcoverresult["lc_frac_all"][0, 8]]) - else: - arr2 = np.array([f.attributes()[idx], landcoverresult["lc_frac_all"][0, 0], landcoverresult["lc_frac_all"][0, 1], - landcoverresult["lc_frac_all"][0, 2], landcoverresult["lc_frac_all"][0, 3], landcoverresult["lc_frac_all"][0, 4], - landcoverresult["lc_frac_all"][0, 5], landcoverresult["lc_frac_all"][0, 6]]) + arr2 = np.array( + [ + f.attributes()[idx], + landcoverresult["lc_frac_all"][0, 0], + landcoverresult["lc_frac_all"][0, 1], + landcoverresult["lc_frac_all"][0, 2], + landcoverresult["lc_frac_all"][0, 3], + landcoverresult["lc_frac_all"][0, 4], + landcoverresult["lc_frac_all"][0, 5], + landcoverresult["lc_frac_all"][0, 6], + landcoverresult["lc_frac_all"][0, 7], + landcoverresult["lc_frac_all"][0, 8], + ] + ) + else: + arr2 = np.array( + [ + f.attributes()[idx], + landcoverresult["lc_frac_all"][0, 0], + landcoverresult["lc_frac_all"][0, 1], + landcoverresult["lc_frac_all"][0, 2], + landcoverresult["lc_frac_all"][0, 3], + landcoverresult["lc_frac_all"][0, 4], + landcoverresult["lc_frac_all"][0, 5], + landcoverresult["lc_frac_all"][0, 6], + ] + ) arrmat = np.vstack([arrmat, arr2]) dataset = None - arrmatsave = arrmat[1: arrmat.shape[0], :] - np.savetxt(outputDir + '/' + pre + '_' + 'LCFG_isotropic.txt', arrmatsave, - fmt=numformat2, delimiter=' ', header=header2, comments='') + arrmatsave = arrmat[1 : arrmat.shape[0], :] + np.savetxt( + outputDir + "/" + pre + "_" + "LCFG_isotropic.txt", + arrmatsave, + fmt=numformat2, + delimiter=" ", + header=header2, + comments="", + ) if attrTable: - feedback.setProgressText("Adding result to layer attribute table") + feedback.setProgressText("Adding result to layer attribute table") self.addattr(vlayer, arrmatsave, header, pre, feedback, idx) return {self.OUTPUT_DIR: outputDir} - def addattr(self, vlayer, matdata, header, pre, feedback, idx): current_index_length = len(vlayer.dataProvider().attributeIndexes()) caps = vlayer.dataProvider().capabilities() @@ -296,12 +469,16 @@ def addattr(self, vlayer, matdata, header, pre, feedback, idx): if caps & QgsVectorDataProvider.Capability.AddAttributes: line_split = header.split() for x in range(1, len(line_split)): - vlayer.dataProvider().addAttributes([QgsField(pre + '_' + line_split[x], QVariant.Double)]) + vlayer.dataProvider().addAttributes( + [QgsField(pre + "_" + line_split[x], QVariant.Double)] + ) vlayer.commitChanges() vlayer.updateFields() attr_dict = {} else: - raise QgsProcessingException("Vector Layer does not support adding attributes") + raise QgsProcessingException( + "Vector Layer does not support adding attributes" + ) features = vlayer.getFeatures() @@ -311,12 +488,13 @@ def addattr(self, vlayer, matdata, header, pre, feedback, idx): wo = np.where(f.attributes()[idx] == matdata[:, 0]) if wo[0] >= 0: for x in range(1, matdata.shape[1]): - attr_dict[current_index_length + x - 1] = float(matdata[wo[0], x]) + attr_dict[current_index_length + x - 1] = float( + matdata[wo[0], x] + ) vlayer.dataProvider().changeAttributeValues({id: attr_dict}) - def resultcheck(self, landcoverresult): - total = 0. + total = 0.0 arr = landcoverresult["lc_frac_all"] for x in range(0, len(arr[0])): @@ -335,9 +513,9 @@ def resultcheck(self, landcoverresult): landcoverresult["lc_frac_all"] = arr return landcoverresult - + def name(self): - return 'Urban Land Cover: Land Cover Fraction (Grid)' + return "Urban Land Cover: Land Cover Fraction (Grid)" def displayName(self): return self.tr(self.name()) @@ -346,38 +524,42 @@ def group(self): return self.tr(self.groupId()) def groupId(self): - return 'Pre-Processor' + return "Pre-Processor" def shortHelpString(self): - return self.tr('The Land Cover Fraction (Grid) plugin calculates land cover fractions required for UMEP (see Land Cover Reclassifier) ' - 'from a point location based on a land cover raster grid. A land cover grid suitable for the processor in UMEP can be derived using ' - 'the Land Cover Classifier. The fraction will vary depending on what angle (wind direction) you are interested in. Thus, this plugin ' - 'is able to derive the land cover fractions for different directions. It is the same as the Land Cover Fraction (Point) except that ' - 'this plugin calculates the fractions for each polygon object in polygon vector layer. The polygons should preferable be squares but could ' - 'be any other regular shape. To create such a grid, built in functions in QGIS can be used (see Vector geometry -> Research Tools -> Create Grid in the ' - 'Processing Toolbox).\n' - '\n' - 'Standard land cover classes in UMEP: \n' - '1. Paved, 2. Buildings, 3. Evergreen trees, 4. Deciduous trees, 5. Grass, 6. Bare Soil, 7. Water.\n' - '\n' - 'Two more land cover classes is added when the tickbox "Calculate fractions for TARGET" is ticked in: 8. Grass (irrigated), 9. Concrete.\n' - '\n' - 'NOTE: It is possible to use the 7 standard LC-classes for the TARGET land cover fractions. See Pre-Processor>Urban Heat Island>TARGET Prepare for mor info.' - '\n' - '--------------\n' - 'Full manual available via the Help-button.') + return self.tr( + "The Land Cover Fraction (Grid) plugin calculates land cover fractions required for UMEP (see Land Cover Reclassifier) " + "from a point location based on a land cover raster grid. A land cover grid suitable for the processor in UMEP can be derived using " + "the Land Cover Classifier. The fraction will vary depending on what angle (wind direction) you are interested in. Thus, this plugin " + "is able to derive the land cover fractions for different directions. It is the same as the Land Cover Fraction (Point) except that " + "this plugin calculates the fractions for each polygon object in polygon vector layer. The polygons should preferable be squares but could " + "be any other regular shape. To create such a grid, built in functions in QGIS can be used (see Vector geometry -> Research Tools -> Create Grid in the " + "Processing Toolbox).\n" + "\n" + "Standard land cover classes in UMEP: \n" + "1. Paved, 2. Buildings, 3. Evergreen trees, 4. Deciduous trees, 5. Grass, 6. Bare Soil, 7. Water.\n" + "\n" + 'Two more land cover classes is added when the tickbox "Calculate fractions for TARGET" is ticked in: 8. Grass (irrigated), 9. Concrete.\n' + "\n" + "NOTE: It is possible to use the 7 standard LC-classes for the TARGET land cover fractions. See Pre-Processor>Urban Heat Island>TARGET Prepare for mor info." + "\n" + "--------------\n" + "Full manual available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/pre-processor/Urban%20Land%20Cover%20Land%20Cover%20Fraction%20(Grid).html" return url def tr(self, string): - return QCoreApplication.translate('Processing', string) + return QCoreApplication.translate("Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/LandCoverFractionGridIcon.png") return icon def createInstance(self): - return ProcessingLandCoverFractionAlgorithm() \ No newline at end of file + return ProcessingLandCoverFractionAlgorithm() diff --git a/preprocessor/landcoverfractionpoint_algorithm.py b/preprocessor/landcoverfractionpoint_algorithm.py index c4aaf4c..5de413b 100644 --- a/preprocessor/landcoverfractionpoint_algorithm.py +++ b/preprocessor/landcoverfractionpoint_algorithm.py @@ -22,31 +22,33 @@ ***************************************************************************/ """ -__author__ = 'Fredrik Lindberg' -__date__ = '2020-04-02' -__copyright__ = '(C) 2020 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2020-04-02" +__copyright__ = "(C) 2020 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QVariant -from qgis.core import (QgsProcessing, - QgsProcessingAlgorithm, - QgsProcessingParameterPoint, - QgsProcessingParameterString, - QgsProcessingParameterBoolean, - QgsProcessingParameterNumber, - QgsProcessingParameterFolderDestination, - QgsProcessingParameterRasterLayer, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterVectorDestination, - QgsProcessingException, - QgsVectorLayer, - QgsFeature, - QgsGeometry, - QgsPointXY, - QgsVectorFileWriter) +from qgis.core import ( + QgsProcessing, + QgsProcessingAlgorithm, + QgsProcessingParameterPoint, + QgsProcessingParameterString, + QgsProcessingParameterBoolean, + QgsProcessingParameterNumber, + QgsProcessingParameterFolderDestination, + QgsProcessingParameterRasterLayer, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterVectorDestination, + QgsProcessingException, + QgsVectorLayer, + QgsFeature, + QgsGeometry, + QgsPointXY, + QgsVectorFileWriter, +) from qgis.PyQt.QtGui import QIcon from osgeo import gdal @@ -55,7 +57,7 @@ import numpy as np import inspect from pathlib import Path -from ..util import landCoverFractions_v2 as land #changed to v2 +from ..util import landCoverFractions_v2 as land # changed to v2 class ProcessingLandCoverFractionPointAlgorithm(QgsProcessingAlgorithm): @@ -63,107 +65,186 @@ class ProcessingLandCoverFractionPointAlgorithm(QgsProcessingAlgorithm): This algorithm is a processing version of Image Morphometric Calculator Point """ - USE_POINTLAYER = 'USE_POINTLAYER' - INPUT_POINT = 'INPUT_POINT' - INPUT_POINTLAYER = 'INPUT_POINTLAYER' - INPUT_DISTANCE = 'INPUT_DISTANCE' - INPUT_INTERVAL = 'INPUT_INTERVAL' - INPUT_LCGRID = 'INPUT_LCGRID' - TARGET_LC = 'TARGET_LC' - FILE_PREFIX = 'FILE_PREFIX' - OUTPUT_DIR = 'OUTPUT_DIR' - OUTPUT_POLYGON = 'OUTPUT_POLYGON' - OUTPUT_POINT = 'OUTPUT_POINT' - + USE_POINTLAYER = "USE_POINTLAYER" + INPUT_POINT = "INPUT_POINT" + INPUT_POINTLAYER = "INPUT_POINTLAYER" + INPUT_DISTANCE = "INPUT_DISTANCE" + INPUT_INTERVAL = "INPUT_INTERVAL" + INPUT_LCGRID = "INPUT_LCGRID" + TARGET_LC = "TARGET_LC" + FILE_PREFIX = "FILE_PREFIX" + OUTPUT_DIR = "OUTPUT_DIR" + OUTPUT_POLYGON = "OUTPUT_POLYGON" + OUTPUT_POINT = "OUTPUT_POINT" + def initAlgorithm(self, config): - - self.addParameter(QgsProcessingParameterPoint(self.INPUT_POINT, - self.tr('Point of interest'), optional=True)) - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_POINTLAYER, - self.tr('Point vector layer'), [QgsProcessing.SourceType.TypeVectorPoint], optional=True)) - self.addParameter(QgsProcessingParameterNumber(self.INPUT_DISTANCE, - self.tr('Search distance (meter)'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(200), False, minValue=0)) - self.addParameter(QgsProcessingParameterNumber(self.INPUT_INTERVAL, - self.tr('Wind direction search interval (degree)'), - QgsProcessingParameterNumber.Type.Double, - QVariant(5), False, minValue=0.1, maxValue=360.)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_LCGRID, - self.tr('UMEP formatted land cover grid (see help for more info)'), None, False)) - self.addParameter(QgsProcessingParameterString(self.FILE_PREFIX, - self.tr('File prefix'))) - self.addParameter(QgsProcessingParameterBoolean(self.TARGET_LC, - self.tr("Calculate fractions for TARGET (9 classes instead of 7)"), defaultValue=False)) - self.addParameter(QgsProcessingParameterVectorDestination(self.OUTPUT_POINT, - self.tr("Output point layer (point of interest)"), optional=True, - createByDefault=False)) - self.addParameter(QgsProcessingParameterVectorDestination(self.OUTPUT_POLYGON, - self.tr("Output polygon layer (area of interest)"), optional=True, - createByDefault=False)) - self.addParameter(QgsProcessingParameterFolderDestination(self.OUTPUT_DIR, - self.tr('Output folder'))) + + self.addParameter( + QgsProcessingParameterPoint( + self.INPUT_POINT, self.tr("Point of interest"), optional=True + ) + ) + self.addParameter( + QgsProcessingParameterFeatureSource( + self.INPUT_POINTLAYER, + self.tr("Point vector layer"), + [QgsProcessing.SourceType.TypeVectorPoint], + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.INPUT_DISTANCE, + self.tr("Search distance (meter)"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(200), + False, + minValue=0, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.INPUT_INTERVAL, + self.tr("Wind direction search interval (degree)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(5), + False, + minValue=0.1, + maxValue=360.0, + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_LCGRID, + self.tr( + "UMEP formatted land cover grid (see help for more info)" + ), + None, + False, + ) + ) + self.addParameter( + QgsProcessingParameterString( + self.FILE_PREFIX, self.tr("File prefix") + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.TARGET_LC, + self.tr( + "Calculate fractions for TARGET (9 classes instead of 7)" + ), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterVectorDestination( + self.OUTPUT_POINT, + self.tr("Output point layer (point of interest)"), + optional=True, + createByDefault=False, + ) + ) + self.addParameter( + QgsProcessingParameterVectorDestination( + self.OUTPUT_POLYGON, + self.tr("Output polygon layer (area of interest)"), + optional=True, + createByDefault=False, + ) + ) + self.addParameter( + QgsProcessingParameterFolderDestination( + self.OUTPUT_DIR, self.tr("Output folder") + ) + ) self.plugin_dir = os.path.dirname(__file__) - if not (os.path.isdir(self.plugin_dir + '/data')): - os.mkdir(self.plugin_dir + '/data') + if not (os.path.isdir(self.plugin_dir + "/data")): + os.mkdir(self.plugin_dir + "/data") def processAlgorithm(self, parameters, context, feedback): # InputParameters inputPoint = None - inputPointLayer = self.parameterAsVectorLayer(parameters, self.INPUT_POINTLAYER, context) - inputDistance = self.parameterAsDouble(parameters, self.INPUT_DISTANCE, context) - inputInterval = self.parameterAsDouble(parameters, self.INPUT_INTERVAL, context) - lclayer = self.parameterAsRasterLayer(parameters, self.INPUT_LCGRID, context) - filePrefix = self.parameterAsString(parameters, self.FILE_PREFIX, context) - useTarget = self. parameterAsBool(parameters, self.TARGET_LC, context) - outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) - outputPolygon = self.parameterAsOutputLayer(parameters, self.OUTPUT_POLYGON, context) + inputPointLayer = self.parameterAsVectorLayer( + parameters, self.INPUT_POINTLAYER, context + ) + inputDistance = self.parameterAsDouble( + parameters, self.INPUT_DISTANCE, context + ) + inputInterval = self.parameterAsDouble( + parameters, self.INPUT_INTERVAL, context + ) + lclayer = self.parameterAsRasterLayer( + parameters, self.INPUT_LCGRID, context + ) + filePrefix = self.parameterAsString( + parameters, self.FILE_PREFIX, context + ) + useTarget = self.parameterAsBool(parameters, self.TARGET_LC, context) + outputDir = self.parameterAsString( + parameters, self.OUTPUT_DIR, context + ) + outputPolygon = self.parameterAsOutputLayer( + parameters, self.OUTPUT_POLYGON, context + ) outputPoint = None - - if parameters['OUTPUT_DIR'] == 'TEMPORARY_OUTPUT': + + if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): os.mkdir(outputDir) # Get POI if inputPointLayer is None: feedback.setProgressText("Point location obtained manually") - inputPoint = self.parameterAsPoint(parameters, self.INPUT_POINT, context) + inputPoint = self.parameterAsPoint( + parameters, self.INPUT_POINT, context + ) x = float(inputPoint[0]) y = float(inputPoint[1]) else: - feedback.setProgressText("Point location obtained from point in vector layer") + feedback.setProgressText( + "Point location obtained from point in vector layer" + ) if not inputPointLayer.selectedFeatures(): if inputPointLayer.featureCount() != 1: - raise QgsProcessingException("Point vector layer includes more than one object. Select one feature point and try again.") + raise QgsProcessingException( + "Point vector layer includes more than one object. Select one feature point and try again." + ) else: for poi in inputPointLayer.getFeatures(): x = poi.geometry().asPoint()[0] y = poi.geometry().asPoint()[1] else: if inputPointLayer.selectedFeatureCount() != 1: - raise QgsProcessingException("More than one point of interest is selected. Select only one feature point and try again.") + raise QgsProcessingException( + "More than one point of interest is selected. Select only one feature point and try again." + ) for poi in inputPointLayer.selectedFeatures(): x = poi.geometry().asPoint()[0] y = poi.geometry().asPoint()[1] feedback.setProgressText("x = " + str(x)) feedback.setProgressText("y = " + str(y)) - + r = inputDistance - + if lclayer is None: - raise QgsProcessingException("No valid building land cover raster layer is selected") + raise QgsProcessingException( + "No valid building land cover raster layer is selected" + ) provider = lclayer.dataProvider() filePath_lcgrid = str(provider.dataSourceUri()) bigraster = gdal.Open(filePath_lcgrid) bbox = (x - r, y + r, x + r, y - r) - gdal.Translate(self.plugin_dir + '/data/clipdsm.tif', bigraster, projWin=bbox) + gdal.Translate( + self.plugin_dir + "/data/clipdsm.tif", bigraster, projWin=bbox + ) bigraster = None - dataset = gdal.Open(self.plugin_dir + '/data/clipdsm.tif') + dataset = gdal.Open(self.plugin_dir + "/data/clipdsm.tif") lcgrid = dataset.ReadAsArray().astype(float) - + if useTarget: iter = 9 feedback.setProgressText("9 land cover classes is considered") @@ -173,53 +254,83 @@ def processAlgorithm(self, parameters, context, feedback): degree = float(inputInterval) nd = dataset.GetRasterBand(1).GetNoDataValue() - nodata_test = (lcgrid == nd) - + nodata_test = lcgrid == nd + if nodata_test.any(): - raise QgsProcessingException("NoData values present within the radius (" + str(int(r)) + - " m) from point of interest. Extend your raster(s) or more point.") + raise QgsProcessingException( + "NoData values present within the radius (" + + str(int(r)) + + " m) from point of interest. Extend your raster(s) or more point." + ) else: - landcoverresult = land.landcover_v2(lcgrid, 1, degree, feedback, 1, iter) + landcoverresult = land.landcover_v2( + lcgrid, 1, degree, feedback, 1, iter + ) ## save to file ## - #anisotropic + # anisotropic if useTarget: - header = 'Wd Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water IrrGrass Concrete' - numformat = '%3d %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f' + header = "Wd Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water IrrGrass Concrete" + numformat = ( + "%3d %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f" + ) else: - header = 'Wd Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water' - numformat = '%3d %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f' - - arr = np.concatenate((landcoverresult["deg"], landcoverresult["lc_frac"]), axis=1) - np.savetxt(outputDir + '/' + filePrefix + '_' + 'LCFPoint_anisotropic.txt', arr, - fmt=numformat, delimiter=' ', header=header, comments='') - - #isotropic + header = "Wd Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water" + numformat = "%3d %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f" + + arr = np.concatenate( + (landcoverresult["deg"], landcoverresult["lc_frac"]), axis=1 + ) + np.savetxt( + outputDir + "/" + filePrefix + "_" + "LCFPoint_anisotropic.txt", + arr, + fmt=numformat, + delimiter=" ", + header=header, + comments="", + ) + + # isotropic if useTarget: - header = 'Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water IrrGrass Concrete' - numformat = '%5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f' + header = "Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water IrrGrass Concrete" + numformat = "%5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f" else: - header = 'Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water' - numformat = '%5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f' + header = "Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water" + numformat = "%5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f" arr2 = np.array(landcoverresult["lc_frac_all"]) - np.savetxt(outputDir + '/' + filePrefix + '_' + 'LCFPoint_isotropic.txt', arr2, - fmt=numformat, delimiter=' ', header=header, comments='') + np.savetxt( + outputDir + "/" + filePrefix + "_" + "LCFPoint_isotropic.txt", + arr2, + fmt=numformat, + delimiter=" ", + header=header, + comments="", + ) dataset = None - + crs = str(lclayer.crs().authid()) self.create_poly_layer(outputPolygon, x, y, r, crs) - + # if savePoint: - outputPoint = self.parameterAsOutputLayer(parameters, self.OUTPUT_POINT, context) + outputPoint = self.parameterAsOutputLayer( + parameters, self.OUTPUT_POINT, context + ) self.create_point_layer(outputPoint, x, y, crs) - results = {self.OUTPUT_DIR: outputDir, self.OUTPUT_POLYGON: outputPolygon, self.OUTPUT_POINT: outputPoint} + results = { + self.OUTPUT_DIR: outputDir, + self.OUTPUT_POLYGON: outputPolygon, + self.OUTPUT_POINT: outputPoint, + } return results def create_point_layer(self, outputPoint, x, y, crs): - uri = "Point?field=id:integer&field=x:double&field=y:double&index=yes&crs=" + crs + uri = ( + "Point?field=id:integer&field=x:double&field=y:double&index=yes&crs=" + + crs + ) self.poiLayer = QgsVectorLayer(uri, "Point of Interest", "memory") self.provider = self.poiLayer.dataProvider() # create the feature @@ -233,15 +344,21 @@ def create_point_layer(self, outputPoint, x, y, crs): fields = self.poiLayer.fields() - writer = QgsVectorFileWriter(outputPoint, "CP1250", fields, self.provider.wkbType(), - self.provider.crs(), "ESRI shapefile") + writer = QgsVectorFileWriter( + outputPoint, + "CP1250", + fields, + self.provider.wkbType(), + self.provider.crs(), + "ESRI shapefile", + ) self.poiLayer.selectAll() selection = self.poiLayer.selectedFeatures() for feature in selection: writer.addFeature(feature) del writer - + def create_poly_layer(self, outputPolygon, x, y, r, crs): uri = "Polygon?field=id:integer&index=yes&crs=" + crs self.polyLayer = QgsVectorLayer(uri, "Study area", "memory") @@ -253,7 +370,9 @@ def create_poly_layer(self, outputPolygon, x, y, r, crs): # Assign feature the buffered geometry radius = r - featurepoly.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x, y)).buffer(radius, 1000)) # fix issue #400 + featurepoly.setGeometry( + QgsGeometry.fromPointXY(QgsPointXY(x, y)).buffer(radius, 1000) + ) # fix issue #400 featurepoly.setAttributes([fc]) self.polyLayer.startEditing() self.polyLayer.addFeature(featurepoly) @@ -261,17 +380,23 @@ def create_poly_layer(self, outputPolygon, x, y, r, crs): prov = self.provider fields = self.polyLayer.fields() - writer = QgsVectorFileWriter(outputPolygon, "CP1250", fields, prov.wkbType(), - prov.crs(), "ESRI shapefile") + writer = QgsVectorFileWriter( + outputPolygon, + "CP1250", + fields, + prov.wkbType(), + prov.crs(), + "ESRI shapefile", + ) self.polyLayer.selectAll() selection = self.polyLayer.selectedFeatures() for feature in selection: writer.addFeature(feature) del writer - + def name(self): - return 'Urban Land Cover: Land Cover Fraction (Point)' + return "Urban Land Cover: Land Cover Fraction (Point)" def displayName(self): return self.tr(self.name()) @@ -280,35 +405,39 @@ def group(self): return self.tr(self.groupId()) def groupId(self): - return 'Pre-Processor' + return "Pre-Processor" def shortHelpString(self): - return self.tr('The Land Cover Fraction (Point) plugin calculates land cover fractions required for UMEP (see Land Cover ' - 'Reclassifier) from a point location based on a land cover raster grid. A land cover raster grid suitable for the processor in ' - 'UMEP can be derived using the Land Cover Classifier. The fraction will vary depending on what angle (wind direction) ' - 'you are interested in. Thus, this plugin is able to derive the land cover fractions for different directions.\n' - '\n' - 'Standard land cover classes in UMEP: \n' - '1. Paved, 2. Buildings, 3. Evergreen trees, 4. Deciduous trees, 5. Grass, 6. Bare Soil, 7. Water.\n' - '\n' - 'Two more land cover classes is added when the tickbox "Calculate fractions for TARGET" is ticked in: 8. Grass (irrigated), 9. Concrete.\n' - '\n' - 'NOTE: It is possible to use the 7 standard LC-classes for the TARGET land cover fractions. See Pre-Processor>Urban Heat Island>TARGET Prepare for mor info.' - '\n' - '-------------\n' - 'Full manual available via the Help-button.') + return self.tr( + "The Land Cover Fraction (Point) plugin calculates land cover fractions required for UMEP (see Land Cover " + "Reclassifier) from a point location based on a land cover raster grid. A land cover raster grid suitable for the processor in " + "UMEP can be derived using the Land Cover Classifier. The fraction will vary depending on what angle (wind direction) " + "you are interested in. Thus, this plugin is able to derive the land cover fractions for different directions.\n" + "\n" + "Standard land cover classes in UMEP: \n" + "1. Paved, 2. Buildings, 3. Evergreen trees, 4. Deciduous trees, 5. Grass, 6. Bare Soil, 7. Water.\n" + "\n" + 'Two more land cover classes is added when the tickbox "Calculate fractions for TARGET" is ticked in: 8. Grass (irrigated), 9. Concrete.\n' + "\n" + "NOTE: It is possible to use the 7 standard LC-classes for the TARGET land cover fractions. See Pre-Processor>Urban Heat Island>TARGET Prepare for mor info." + "\n" + "-------------\n" + "Full manual available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/pre-processor/Urban%20Land%20Cover%20Land%20Cover%20Fraction%20(Point).html" return url def tr(self, string): - return QCoreApplication.translate('Processing', string) + return QCoreApplication.translate("Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/LandCoverFractionPointIcon.png") return icon def createInstance(self): - return ProcessingLandCoverFractionPointAlgorithm() \ No newline at end of file + return ProcessingLandCoverFractionPointAlgorithm() diff --git a/preprocessor/skyviewfactor_algorithm.py b/preprocessor/skyviewfactor_algorithm.py index 292ac02..94d2f79 100644 --- a/preprocessor/skyviewfactor_algorithm.py +++ b/preprocessor/skyviewfactor_algorithm.py @@ -22,23 +22,26 @@ ***************************************************************************/ """ -__author__ = 'Fredrik Lindberg' -__date__ = '2020-04-02' -__copyright__ = '(C) 2020 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2020-04-02" +__copyright__ = "(C) 2020 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QVariant -from qgis.core import (QgsProcessingAlgorithm, - QgsProcessingParameterBoolean, - QgsProcessingParameterNumber, - QgsProcessingParameterFolderDestination, - QgsProcessingParameterRasterDestination, - QgsProcessingException, - QgsProcessingParameterRasterLayer, - QgsProcessingParameterDefinition) +from qgis.core import ( + QgsProcessingAlgorithm, + QgsProcessingParameterBoolean, + QgsProcessingParameterNumber, + QgsProcessingParameterFolderDestination, + QgsProcessingParameterRasterDestination, + QgsProcessingException, + QgsProcessingParameterRasterLayer, + QgsProcessingParameterDefinition, +) + # from processing.gui.wrappers import WidgetWrapper from qgis.PyQt.QtGui import QIcon from osgeo import gdal, osr @@ -54,115 +57,217 @@ from ..functions import svf_functions as svf from ..functions import svf_for_voxels as svfv + class ProcessingSkyViewFactorAlgorithm(QgsProcessingAlgorithm): """ This algorithm is a processing version of SkyViewFactor """ - INPUT_DSM = 'INPUT_DSM' - INPUT_CDSM = 'INPUT_CDSM' - INPUT_TDSM = 'INPUT_TDSM' + INPUT_DSM = "INPUT_DSM" + INPUT_CDSM = "INPUT_CDSM" + INPUT_TDSM = "INPUT_TDSM" # USE_VEG = 'USE_VEG' - TRANS_VEG = 'TRANS_VEG' + TRANS_VEG = "TRANS_VEG" # TSDM_EXIST = 'TSDM_EXIST' - INPUT_THEIGHT = 'INPUT_THEIGHT' - ANISO = 'ANISO' - KMEANS = 'KMEANS' - CLUSTERS = 'CLUSTERS' - WALL_SCHEME = 'WALL_SCHEME' - INPUT_DEM = 'INPUT_DEM' - INPUT_SVFHEIGHT = 'INPUT_SVFHEIGHT' - OUTPUT_DIR = 'OUTPUT_DIR' - OUTPUT_FILE = 'OUTPUT_FILE' - + INPUT_THEIGHT = "INPUT_THEIGHT" + ANISO = "ANISO" + KMEANS = "KMEANS" + CLUSTERS = "CLUSTERS" + WALL_SCHEME = "WALL_SCHEME" + INPUT_DEM = "INPUT_DEM" + INPUT_SVFHEIGHT = "INPUT_SVFHEIGHT" + OUTPUT_DIR = "OUTPUT_DIR" + OUTPUT_FILE = "OUTPUT_FILE" + def initAlgorithm(self, config): - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_DSM, - self.tr('Input building and ground DSM'), None, False)) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_DSM, + self.tr("Input building and ground DSM"), + None, + False, + ) + ) # self.addParameter(QgsProcessingParameterBoolean(self.USE_VEG, # self.tr("Use vegetation DSMs"), defaultValue=False)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_CDSM, - self.tr('Vegetation canopy DSM'), '', True)) - self.addParameter(QgsProcessingParameterNumber(self.TRANS_VEG, - self.tr('Transmissivity of light through vegetation (%):'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(3), True, minValue=0, maxValue=100)) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_CDSM, self.tr("Vegetation canopy DSM"), "", True + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.TRANS_VEG, + self.tr("Transmissivity of light through vegetation (%):"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(3), + True, + minValue=0, + maxValue=100, + ) + ) # self.addParameter(QgsProcessingParameterBoolean(self.TSDM_EXIST, # self.tr("Trunk zone DSM exist"), defaultValue=False)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_TDSM, - self.tr('Vegetation trunk zone DSM'), '', True)) - self.addParameter(QgsProcessingParameterNumber(self.INPUT_THEIGHT, - self.tr("Trunk zone height (percent of canopy height)"), - QgsProcessingParameterNumber.Type.Double, - QVariant(25.0), - True, minValue=0.1, maxValue=99.9)) - self.addParameter(QgsProcessingParameterBoolean(self.ANISO, - self.tr("Use method with 153 shadow images instead of 655. Required for anisotropic sky scheme (SOLWEIG v2022a)\nand wall surface temperature scheme (SOLWEIG v2025a)"), - defaultValue=True)) - - # Wall parameterization - wall_scheme = QgsProcessingParameterBoolean(self.WALL_SCHEME, - self.tr("Use parameterization scheme for wall surface temperatures (Wallenberg et al. 2025)"), - defaultValue=False) - wall_scheme.setFlags(wall_scheme.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) - self.addParameter(wall_scheme) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_TDSM, self.tr("Vegetation trunk zone DSM"), "", True + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.INPUT_THEIGHT, + self.tr("Trunk zone height (percent of canopy height)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(25.0), + True, + minValue=0.1, + maxValue=99.9, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.ANISO, + self.tr( + "Use method with 153 shadow images instead of 655. Required for anisotropic sky scheme (SOLWEIG v2022a)\nand wall surface temperature scheme (SOLWEIG v2025a)" + ), + defaultValue=True, + ) + ) - wall_kmeans = QgsProcessingParameterBoolean(self.KMEANS, + # Wall parameterization + wall_scheme = QgsProcessingParameterBoolean( + self.WALL_SCHEME, + self.tr( + "Use parameterization scheme for wall surface temperatures (Wallenberg et al. 2025)" + ), + defaultValue=False, + ) + wall_scheme.setFlags( + wall_scheme.flags() + | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) + self.addParameter(wall_scheme) + + wall_kmeans = QgsProcessingParameterBoolean( + self.KMEANS, self.tr("Use K-Means to calculate SVF for walls (SOLWEIG v2025a)"), - defaultValue=True) - wall_kmeans.setFlags(wall_kmeans.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + defaultValue=True, + ) + wall_kmeans.setFlags( + wall_kmeans.flags() + | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(wall_kmeans) - - wall_clusters = QgsProcessingParameterNumber(self.CLUSTERS, - self.tr("Number of clusters used in K-Means (number of elevations)"), + + wall_clusters = QgsProcessingParameterNumber( + self.CLUSTERS, + self.tr( + "Number of clusters used in K-Means (number of elevations)" + ), QgsProcessingParameterNumber.Type.Integer, QVariant(5), - True, minValue=1, maxValue=100) - wall_clusters.setFlags(wall_clusters.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) - self.addParameter(wall_clusters) - - wall_dem = QgsProcessingParameterRasterLayer(self.INPUT_DEM, - self.tr('Input DEM used to calculate exact SVFs for wall surface temperature parameterization (SOLWEIG v2025a)'), '', True) - wall_dem.setFlags(wall_dem.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) - self.addParameter(wall_dem) - - wall_svfheight = QgsProcessingParameterNumber(self.INPUT_SVFHEIGHT, - self.tr("Elevation steps (m) used in SVF calculations for wall surface temperature parameterization scheme\nInterpolation will performed if steps are larger than horizontal pixel resolution"), + True, + minValue=1, + maxValue=100, + ) + wall_clusters.setFlags( + wall_clusters.flags() + | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) + self.addParameter(wall_clusters) + + wall_dem = QgsProcessingParameterRasterLayer( + self.INPUT_DEM, + self.tr( + "Input DEM used to calculate exact SVFs for wall surface temperature parameterization (SOLWEIG v2025a)" + ), + "", + True, + ) + wall_dem.setFlags( + wall_dem.flags() + | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) + self.addParameter(wall_dem) + + wall_svfheight = QgsProcessingParameterNumber( + self.INPUT_SVFHEIGHT, + self.tr( + "Elevation steps (m) used in SVF calculations for wall surface temperature parameterization scheme\nInterpolation will performed if steps are larger than horizontal pixel resolution" + ), QgsProcessingParameterNumber.Type.Double, QVariant(1.0), - True, minValue=0.5, maxValue=10) - wall_svfheight.setFlags(wall_svfheight.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) - self.addParameter(wall_svfheight) + True, + minValue=0.5, + maxValue=10, + ) + wall_svfheight.setFlags( + wall_svfheight.flags() + | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) + self.addParameter(wall_svfheight) # Output - self.addParameter(QgsProcessingParameterFolderDestination(self.OUTPUT_DIR, - 'Output folder for individual raster files')) - self.addParameter(QgsProcessingParameterRasterDestination(self.OUTPUT_FILE, - self.tr("Output sky view factor raster"), None, False)) + self.addParameter( + QgsProcessingParameterFolderDestination( + self.OUTPUT_DIR, "Output folder for individual raster files" + ) + ) + self.addParameter( + QgsProcessingParameterRasterDestination( + self.OUTPUT_FILE, + self.tr("Output sky view factor raster"), + None, + False, + ) + ) def processAlgorithm(self, parameters, context, feedback): # InputParameters - outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) - outputFile = self.parameterAsOutputLayer(parameters, self.OUTPUT_FILE, context) - dsmlayer = self.parameterAsRasterLayer(parameters, self.INPUT_DSM, context) + outputDir = self.parameterAsString( + parameters, self.OUTPUT_DIR, context + ) + outputFile = self.parameterAsOutputLayer( + parameters, self.OUTPUT_FILE, context + ) + dsmlayer = self.parameterAsRasterLayer( + parameters, self.INPUT_DSM, context + ) # useVegdem = self.parameterAsBool(parameters, self.USE_VEG, context) transVeg = self.parameterAsDouble(parameters, self.TRANS_VEG, context) - vegdsm = self.parameterAsRasterLayer(parameters, self.INPUT_CDSM, context) - vegdsm2 = self.parameterAsRasterLayer(parameters, self.INPUT_TDSM, context) + vegdsm = self.parameterAsRasterLayer( + parameters, self.INPUT_CDSM, context + ) + vegdsm2 = self.parameterAsRasterLayer( + parameters, self.INPUT_TDSM, context + ) # tdsmExists = self.parameterAsBool(parameters, self.TSDM_EXIST, context) - trunkr = self.parameterAsDouble(parameters, self.INPUT_THEIGHT, context) + trunkr = self.parameterAsDouble( + parameters, self.INPUT_THEIGHT, context + ) aniso = self.parameterAsBool(parameters, self.ANISO, context) - - # Wall parameterization settings - demlayer = self.parameterAsRasterLayer(parameters, self.INPUT_DEM, context) - svf_height = self.parameterAsDouble(parameters, self.INPUT_SVFHEIGHT, context) - - kmeans = self.parameterAsBool(parameters, self.KMEANS, context) # If K-means will be used or not (true or false) - clusters = self.parameterAsInt(parameters, self.CLUSTERS, context) + 1 # + 1 because ground areas will be one cluster when dsm - dem - wallScheme = self.parameterAsBool(parameters, self.WALL_SCHEME, context) - feedback.setProgressText('Initiating algorithm') - - if parameters['OUTPUT_DIR'] == 'TEMPORARY_OUTPUT': + # Wall parameterization settings + demlayer = self.parameterAsRasterLayer( + parameters, self.INPUT_DEM, context + ) + svf_height = self.parameterAsDouble( + parameters, self.INPUT_SVFHEIGHT, context + ) + + kmeans = self.parameterAsBool( + parameters, self.KMEANS, context + ) # If K-means will be used or not (true or false) + clusters = ( + self.parameterAsInt(parameters, self.CLUSTERS, context) + 1 + ) # + 1 because ground areas will be one cluster when dsm - dem + wallScheme = self.parameterAsBool( + parameters, self.WALL_SCHEME, context + ) + + feedback.setProgressText("Initiating algorithm") + + if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): os.mkdir(outputDir) @@ -173,7 +278,7 @@ def processAlgorithm(self, parameters, context, feedback): # response to issue #85 nd = gdal_dsm.GetRasterBand(1).GetNoDataValue() - dsm[dsm == nd] = 0. + dsm[dsm == nd] = 0.0 if dsm.min() < 0: dsm = dsm + np.abs(dsm.min()) @@ -190,26 +295,30 @@ def processAlgorithm(self, parameters, context, feedback): provider = demlayer.dataProvider() filepath_dem = str(provider.dataSourceUri()) gdal_dem = gdal.Open(filepath_dem) - dem = gdal_dem.ReadAsArray().astype(float) - + dem = gdal_dem.ReadAsArray().astype(float) + demsizex = dem.shape[0] demsizey = dem.shape[1] - if not (demsizex == sizex) & (demsizey == sizey): - raise QgsProcessingException("Error in DEM: All rasters must be of same extent and resolution") + if not (demsizex == sizex) & (demsizey == sizey): + raise QgsProcessingException( + "Error in DEM: All rasters must be of same extent and resolution" + ) else: - raise QgsProcessingException("DEM layer required for wall surface scheme!") + raise QgsProcessingException( + "DEM layer required for wall surface scheme!" + ) else: - dem = None + dem = None trans = transVeg / 100.0 if vegdsm: usevegdem = 1 - feedback.setProgressText('Vegetation scheme activated') + feedback.setProgressText("Vegetation scheme activated") # vegdsm = self.parameterAsRasterLayer(parameters, self.INPUT_CDSM, context) # if vegdsm is None: - # raise QgsProcessingException("Error: No valid vegetation DSM selected") + # raise QgsProcessingException("Error: No valid vegetation DSM selected") # load raster gdal.AllRegister() @@ -222,12 +331,14 @@ def processAlgorithm(self, parameters, context, feedback): vegsizey = vegdsm.shape[1] if not (vegsizex == sizex) & (vegsizey == sizey): - raise QgsProcessingException("Error in Vegetation Canopy DSM: All rasters must be of same extent and resolution") + raise QgsProcessingException( + "Error in Vegetation Canopy DSM: All rasters must be of same extent and resolution" + ) if vegdsm2: # vegdsm2 = self.parameterAsRasterLayer(parameters, self.INPUT_TDSM, context) # if vegdsm2 is None: - # raise QgsProcessingException("Error: No valid Trunk zone DSM selected") + # raise QgsProcessingException("Error: No valid Trunk zone DSM selected") # load raster gdal.AllRegister() @@ -242,37 +353,53 @@ def processAlgorithm(self, parameters, context, feedback): vegsizex = vegdsm2.shape[0] vegsizey = vegdsm2.shape[1] - if not (vegsizex == sizex) & (vegsizey == sizey): - raise QgsProcessingException("Error in Trunk Zone DSM: All rasters must be of same extent and resolution") + if not (vegsizex == sizex) & (vegsizey == sizey): + raise QgsProcessingException( + "Error in Trunk Zone DSM: All rasters must be of same extent and resolution" + ) else: rows = dsm.shape[0] cols = dsm.shape[1] vegdsm = np.zeros([rows, cols]) - vegdsm2 = 0. + vegdsm2 = 0.0 usevegdem = 0 if aniso == 1: - feedback.setProgressText('Calculating SVF using 153 iterations') - ret = svf.svfForProcessing153(dsm, vegdsm, vegdsm2, scale, usevegdem, pixel_resolution, wallScheme, dem, feedback) + feedback.setProgressText("Calculating SVF using 153 iterations") + ret = svf.svfForProcessing153( + dsm, + vegdsm, + vegdsm2, + scale, + usevegdem, + pixel_resolution, + wallScheme, + dem, + feedback, + ) else: - feedback.setProgressText('Calculating SVF using 655 iterations') - ret = svf.svfForProcessing655(dsm, vegdsm, vegdsm2, scale, usevegdem, feedback) + feedback.setProgressText("Calculating SVF using 655 iterations") + ret = svf.svfForProcessing655( + dsm, vegdsm, vegdsm2, scale, usevegdem, feedback + ) # print('Time to finish first SVF calculation = ' + str(run_time)) if wallScheme == 1: - voxelTable = ret['voxelTable'] - voxelTable = voxelTable[voxelTable[:, 2] != 0, :] # Remove where wall height is zero, i.e. there is no wall... - wallHeights = ret['walls'] + voxelTable = ret["voxelTable"] + voxelTable = voxelTable[ + voxelTable[:, 2] != 0, : + ] # Remove where wall height is zero, i.e. there is no wall... + wallHeights = ret["walls"] svfbu = ret["svf"] if usevegdem == 0: svftotal = svfbu - svfveg = ret['svfveg'] - svfaveg = ret['svfaveg'] + svfveg = ret["svfveg"] + svfaveg = ret["svfaveg"] else: svfveg = ret["svfveg"] svfaveg = ret["svfaveg"] trans = transVeg / 100.0 - svftotal = (svfbu - (1 - svfveg) * (1 - trans)) + svftotal = svfbu - (1 - svfveg) * (1 - trans) # Lägg till loop för att lägga till i tabellen svf_array = np.zeros((voxelTable.shape[0])) svf_height_array = np.zeros((voxelTable.shape[0])) @@ -281,40 +408,93 @@ def processAlgorithm(self, parameters, context, feedback): svfaveg_array = np.zeros((voxelTable.shape[0])) voxel_y = np.where(voxelTable[:, 1] == svf_height) for temp_y in voxel_y[0]: - svf_array[temp_y] = svftotal[int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6])] - svfbu_array[temp_y] = svfbu[int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6])] - svfveg_array[temp_y] = svfveg[int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6])] - svfaveg_array[temp_y] = svfaveg[int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6])] - svf_height_array[temp_y] = svf_height + svf_array[temp_y] = svftotal[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] + svfbu_array[temp_y] = svfbu[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] + svfveg_array[temp_y] = svfveg[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] + svfaveg_array[temp_y] = svfaveg[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] + svf_height_array[temp_y] = svf_height if kmeans: - voxelTable, cluster_heights = svfv.svf_kmeans(dsm, dem, vegdsm, vegdsm2, wallHeights, transVeg, scale, usevegdem, pixel_resolution, voxelTable, clusters, - svf_height, svf_array, svfbu_array, svfveg_array, svfaveg_array, svf_height_array, feedback) + voxelTable, cluster_heights = svfv.svf_kmeans( + dsm, + dem, + vegdsm, + vegdsm2, + wallHeights, + transVeg, + scale, + usevegdem, + pixel_resolution, + voxelTable, + clusters, + svf_height, + svf_array, + svfbu_array, + svfveg_array, + svfaveg_array, + svf_height_array, + feedback, + ) # Interpolate for voxels where SVF has not been calculated - voxelTable = svfv.interpolate_svf(voxelTable, cluster_heights, kmeans) + voxelTable = svfv.interpolate_svf( + voxelTable, cluster_heights, kmeans + ) # Loop for exact SVF at heights (increase DEM) # if demlayer: else: - feedback.setProgressText('Calculating SVF for wall surface temperature parameterization') - voxelTable = svfv.svf_for_voxels(dsm, dem, vegdsm, vegdsm2, transVeg, scale, usevegdem, pixel_resolution, voxelTable, - svf_height, svf_array, svfbu_array, svfveg_array, svfaveg_array, svf_height_array, feedback) + feedback.setProgressText( + "Calculating SVF for wall surface temperature parameterization" + ) + voxelTable = svfv.svf_for_voxels( + dsm, + dem, + vegdsm, + vegdsm2, + transVeg, + scale, + usevegdem, + pixel_resolution, + voxelTable, + svf_height, + svf_array, + svfbu_array, + svfveg_array, + svfaveg_array, + svf_height_array, + feedback, + ) # Remove rows where svfbu, sfveg and svfaveg is zero if usevegdem == 1: - voxelTable = voxelTable[((voxelTable[:,-3] > 0.) & (voxelTable[:,-2] > 0.) & (voxelTable[:,-1] > 0.)), :] + voxelTable = voxelTable[ + ( + (voxelTable[:, -3] > 0.0) + & (voxelTable[:, -2] > 0.0) + & (voxelTable[:, -1] > 0.0) + ), + :, + ] else: - voxelTable = voxelTable[((voxelTable[:,-3] > 0.)), :] - + voxelTable = voxelTable[((voxelTable[:, -3] > 0.0)), :] + # Store voxelTable, necessary? - ret['voxelTable'] = voxelTable + ret["voxelTable"] = voxelTable filename = outputFile # temporary fix for mac, ISSUE #15 pf = sys.platform - if pf == 'darwin' or pf == 'linux2' or pf == 'linux': + if pf == "darwin" or pf == "linux2" or pf == "linux": if not os.path.exists(outputDir): os.makedirs(outputDir) @@ -324,29 +504,29 @@ def processAlgorithm(self, parameters, context, feedback): svfbuS = ret["svfS"] svfbuW = ret["svfW"] svfbuN = ret["svfN"] - - misc.saveraster(gdal_dsm, outputDir + '/' + 'svf.tif', svfbu) - misc.saveraster(gdal_dsm, outputDir + '/' + 'svfE.tif', svfbuE) - misc.saveraster(gdal_dsm, outputDir + '/' + 'svfS.tif', svfbuS) - misc.saveraster(gdal_dsm, outputDir + '/' + 'svfW.tif', svfbuW) - misc.saveraster(gdal_dsm, outputDir + '/' + 'svfN.tif', svfbuN) - - if os.path.isfile(outputDir + '/' + 'svfs.zip'): - os.remove(outputDir + '/' + 'svfs.zip') - - zippo = zipfile.ZipFile(outputDir + '/' + 'svfs.zip', 'a') - zippo.write(outputDir + '/' + 'svf.tif', 'svf.tif') - zippo.write(outputDir + '/' + 'svfE.tif', 'svfE.tif') - zippo.write(outputDir + '/' + 'svfS.tif', 'svfS.tif') - zippo.write(outputDir + '/' + 'svfW.tif', 'svfW.tif') - zippo.write(outputDir + '/' + 'svfN.tif', 'svfN.tif') + + misc.saveraster(gdal_dsm, outputDir + "/" + "svf.tif", svfbu) + misc.saveraster(gdal_dsm, outputDir + "/" + "svfE.tif", svfbuE) + misc.saveraster(gdal_dsm, outputDir + "/" + "svfS.tif", svfbuS) + misc.saveraster(gdal_dsm, outputDir + "/" + "svfW.tif", svfbuW) + misc.saveraster(gdal_dsm, outputDir + "/" + "svfN.tif", svfbuN) + + if os.path.isfile(outputDir + "/" + "svfs.zip"): + os.remove(outputDir + "/" + "svfs.zip") + + zippo = zipfile.ZipFile(outputDir + "/" + "svfs.zip", "a") + zippo.write(outputDir + "/" + "svf.tif", "svf.tif") + zippo.write(outputDir + "/" + "svfE.tif", "svfE.tif") + zippo.write(outputDir + "/" + "svfS.tif", "svfS.tif") + zippo.write(outputDir + "/" + "svfW.tif", "svfW.tif") + zippo.write(outputDir + "/" + "svfN.tif", "svfN.tif") zippo.close() - os.remove(outputDir + '/' + 'svf.tif') - os.remove(outputDir + '/' + 'svfE.tif') - os.remove(outputDir + '/' + 'svfS.tif') - os.remove(outputDir + '/' + 'svfW.tif') - os.remove(outputDir + '/' + 'svfN.tif') + os.remove(outputDir + "/" + "svf.tif") + os.remove(outputDir + "/" + "svfE.tif") + os.remove(outputDir + "/" + "svfS.tif") + os.remove(outputDir + "/" + "svfW.tif") + os.remove(outputDir + "/" + "svfN.tif") if usevegdem == 0: svftotal = svfbu @@ -363,43 +543,63 @@ def processAlgorithm(self, parameters, context, feedback): svfWaveg = ret["svfWaveg"] svfNaveg = ret["svfNaveg"] - misc.saveraster(gdal_dsm, outputDir + '/' + 'svfveg.tif', svfveg) - misc.saveraster(gdal_dsm, outputDir + '/' + 'svfEveg.tif', svfEveg) - misc.saveraster(gdal_dsm, outputDir + '/' + 'svfSveg.tif', svfSveg) - misc.saveraster(gdal_dsm, outputDir + '/' + 'svfWveg.tif', svfWveg) - misc.saveraster(gdal_dsm, outputDir + '/' + 'svfNveg.tif', svfNveg) - misc.saveraster(gdal_dsm, outputDir + '/' + 'svfaveg.tif', svfaveg) - misc.saveraster(gdal_dsm, outputDir + '/' + 'svfEaveg.tif', svfEaveg) - misc.saveraster(gdal_dsm, outputDir + '/' + 'svfSaveg.tif', svfSaveg) - misc.saveraster(gdal_dsm, outputDir + '/' + 'svfWaveg.tif', svfWaveg) - misc.saveraster(gdal_dsm, outputDir + '/' + 'svfNaveg.tif', svfNaveg) - - zippo = zipfile.ZipFile(outputDir + '/' + 'svfs.zip', 'a') - zippo.write(outputDir + '/' + 'svfveg.tif', 'svfveg.tif') - zippo.write(outputDir + '/' + 'svfEveg.tif', 'svfEveg.tif') - zippo.write(outputDir + '/' + 'svfSveg.tif', 'svfSveg.tif') - zippo.write(outputDir + '/' + 'svfWveg.tif', 'svfWveg.tif') - zippo.write(outputDir + '/' + 'svfNveg.tif', 'svfNveg.tif') - zippo.write(outputDir + '/' + 'svfaveg.tif', 'svfaveg.tif') - zippo.write(outputDir + '/' + 'svfEaveg.tif', 'svfEaveg.tif') - zippo.write(outputDir + '/' + 'svfSaveg.tif', 'svfSaveg.tif') - zippo.write(outputDir + '/' + 'svfWaveg.tif', 'svfWaveg.tif') - zippo.write(outputDir + '/' + 'svfNaveg.tif', 'svfNaveg.tif') + misc.saveraster( + gdal_dsm, outputDir + "/" + "svfveg.tif", svfveg + ) + misc.saveraster( + gdal_dsm, outputDir + "/" + "svfEveg.tif", svfEveg + ) + misc.saveraster( + gdal_dsm, outputDir + "/" + "svfSveg.tif", svfSveg + ) + misc.saveraster( + gdal_dsm, outputDir + "/" + "svfWveg.tif", svfWveg + ) + misc.saveraster( + gdal_dsm, outputDir + "/" + "svfNveg.tif", svfNveg + ) + misc.saveraster( + gdal_dsm, outputDir + "/" + "svfaveg.tif", svfaveg + ) + misc.saveraster( + gdal_dsm, outputDir + "/" + "svfEaveg.tif", svfEaveg + ) + misc.saveraster( + gdal_dsm, outputDir + "/" + "svfSaveg.tif", svfSaveg + ) + misc.saveraster( + gdal_dsm, outputDir + "/" + "svfWaveg.tif", svfWaveg + ) + misc.saveraster( + gdal_dsm, outputDir + "/" + "svfNaveg.tif", svfNaveg + ) + + zippo = zipfile.ZipFile(outputDir + "/" + "svfs.zip", "a") + zippo.write(outputDir + "/" + "svfveg.tif", "svfveg.tif") + zippo.write(outputDir + "/" + "svfEveg.tif", "svfEveg.tif") + zippo.write(outputDir + "/" + "svfSveg.tif", "svfSveg.tif") + zippo.write(outputDir + "/" + "svfWveg.tif", "svfWveg.tif") + zippo.write(outputDir + "/" + "svfNveg.tif", "svfNveg.tif") + zippo.write(outputDir + "/" + "svfaveg.tif", "svfaveg.tif") + zippo.write(outputDir + "/" + "svfEaveg.tif", "svfEaveg.tif") + zippo.write(outputDir + "/" + "svfSaveg.tif", "svfSaveg.tif") + zippo.write(outputDir + "/" + "svfWaveg.tif", "svfWaveg.tif") + zippo.write(outputDir + "/" + "svfNaveg.tif", "svfNaveg.tif") zippo.close() - os.remove(outputDir + '/' + 'svfveg.tif') - os.remove(outputDir + '/' + 'svfEveg.tif') - os.remove(outputDir + '/' + 'svfSveg.tif') - os.remove(outputDir + '/' + 'svfWveg.tif') - os.remove(outputDir + '/' + 'svfNveg.tif') - os.remove(outputDir + '/' + 'svfaveg.tif') - os.remove(outputDir + '/' + 'svfEaveg.tif') - os.remove(outputDir + '/' + 'svfSaveg.tif') - os.remove(outputDir + '/' + 'svfWaveg.tif') - os.remove(outputDir + '/' + 'svfNaveg.tif') + os.remove(outputDir + "/" + "svfveg.tif") + os.remove(outputDir + "/" + "svfEveg.tif") + os.remove(outputDir + "/" + "svfSveg.tif") + os.remove(outputDir + "/" + "svfWveg.tif") + os.remove(outputDir + "/" + "svfNveg.tif") + os.remove(outputDir + "/" + "svfaveg.tif") + os.remove(outputDir + "/" + "svfEaveg.tif") + os.remove(outputDir + "/" + "svfSaveg.tif") + os.remove(outputDir + "/" + "svfWaveg.tif") + os.remove(outputDir + "/" + "svfNaveg.tif") trans = transVeg / 100.0 - svftotal = (svfbu - (1 - svfveg) * (1 - trans)) + svftotal = svfbu - (1 - svfveg) * (1 - trans) misc.saveraster(gdal_dsm, filename, svftotal) @@ -413,22 +613,33 @@ def processAlgorithm(self, parameters, context, feedback): # wallshvemat = ret["wallshvemat"] # facesunmat = ret["facesunmat"] - np.savez_compressed(outputDir + '/' + "shadowmats.npz", shadowmat=shmat, vegshadowmat=vegshmat, vbshmat=vbshvegshmat) #, - # vbshvegshmat=vbshvegshmat, wallshmat=wallshmat, wallsunmat=wallsunmat, - # facesunmat=facesunmat, wallshvemat=wallshvemat) - + np.savez_compressed( + outputDir + "/" + "shadowmats.npz", + shadowmat=shmat, + vegshadowmat=vegshmat, + vbshmat=vbshvegshmat, + ) # , + # vbshvegshmat=vbshvegshmat, wallshmat=wallshmat, wallsunmat=wallsunmat, + # facesunmat=facesunmat, wallshvemat=wallshvemat) + if wallScheme == 1: - voxelId = ret['voxelIds'] - voxelTable = ret['voxelTable'] + voxelId = ret["voxelIds"] + voxelTable = ret["voxelTable"] - np.savez_compressed(outputDir + '/' + 'wallScheme.npz', voxelId=voxelId, voxelTable=voxelTable) + np.savez_compressed( + outputDir + "/" + "wallScheme.npz", + voxelId=voxelId, + voxelTable=voxelTable, + ) - feedback.setProgressText("Sky View Factor: SVF grid(s) successfully generated") + feedback.setProgressText( + "Sky View Factor: SVF grid(s) successfully generated" + ) return {self.OUTPUT_DIR: outputDir, self.OUTPUT_FILE: outputFile} - + def name(self): - return 'Urban Geometry: Sky View Factor' + return "Urban Geometry: Sky View Factor" def displayName(self): return self.tr(self.name()) @@ -437,32 +648,36 @@ def group(self): return self.tr(self.groupId()) def groupId(self): - return 'Pre-Processor' + return "Pre-Processor" def shortHelpString(self): - return self.tr('The Sky View Factor algorithm can be used to generate pixel wise sky view factor (SVF) ' - 'using ground and building digital surface models (DSM). Optionally, vegetation DSMs could also be used. ' - 'By definition, SVF is the ratio of the radiation received (or emitted) by a planar surface to the ' - 'radiation emitted (or received) by the entire hemispheric environment (Watson and Johnson 1987). ' - 'It is a dimensionless measure between zero and one, representing totally obstructed and free spaces, ' - 'respectively. The methodology that is used to generate SVF here is described in Lindberg and Grimmond (2010).\n' - '-------------\n' - 'Lindberg F, Grimmond CSB (2010) Continuous sky view factor maps from high resolution urban digital elevation models. Clim Res 42:177–183\n' - 'Watson ID, Johnson GT (1987) Graphical estimation of skyview-factors in urban environments. J Climatol 7: 193–197' - '------------\n' - 'Full manual available via the Help-button.') + return self.tr( + "The Sky View Factor algorithm can be used to generate pixel wise sky view factor (SVF) " + "using ground and building digital surface models (DSM). Optionally, vegetation DSMs could also be used. " + "By definition, SVF is the ratio of the radiation received (or emitted) by a planar surface to the " + "radiation emitted (or received) by the entire hemispheric environment (Watson and Johnson 1987). " + "It is a dimensionless measure between zero and one, representing totally obstructed and free spaces, " + "respectively. The methodology that is used to generate SVF here is described in Lindberg and Grimmond (2010).\n" + "-------------\n" + "Lindberg F, Grimmond CSB (2010) Continuous sky view factor maps from high resolution urban digital elevation models. Clim Res 42:177–183\n" + "Watson ID, Johnson GT (1987) Graphical estimation of skyview-factors in urban environments. J Climatol 7: 193–197" + "------------\n" + "Full manual available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/pre-processor/Urban%20Geometry%20Sky%20View%20Factor%20Calculator.html" return url def tr(self, string): - return QCoreApplication.translate('Processing', string) + return QCoreApplication.translate("Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/icon_svf.png") return icon def createInstance(self): - return ProcessingSkyViewFactorAlgorithm() \ No newline at end of file + return ProcessingSkyViewFactorAlgorithm() diff --git a/preprocessor/targetprepare_algorithm.py b/preprocessor/targetprepare_algorithm.py index 4de07f4..c410318 100644 --- a/preprocessor/targetprepare_algorithm.py +++ b/preprocessor/targetprepare_algorithm.py @@ -22,28 +22,31 @@ ***************************************************************************/ """ -__author__ = 'Fredrik Lindberg' -__date__ = '2022-02-07' -__copyright__ = '(C) 2020 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2022-02-07" +__copyright__ = "(C) 2020 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QVariant -from qgis.core import (QgsProcessing, - QgsProcessingAlgorithm, - QgsProcessingParameterFile, - QgsProcessingParameterString, - QgsProcessingParameterNumber, - QgsProcessingParameterFolderDestination, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterField, - QgsProcessingException, - QgsProcessingParameterBoolean) +from qgis.core import ( + QgsProcessing, + QgsProcessingAlgorithm, + QgsProcessingParameterFile, + QgsProcessingParameterString, + QgsProcessingParameterNumber, + QgsProcessingParameterFolderDestination, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterField, + QgsProcessingException, + QgsProcessingParameterBoolean, +) from qgis.PyQt.QtGui import QIcon from osgeo import osr + # from osgeo.gdalconst import * import os import numpy as np @@ -51,6 +54,7 @@ from pathlib import Path import sys import json + # from ..util.umep_uwg_export_component import create_uwgdict, get_uwg_file @@ -59,66 +63,119 @@ class ProcessingTARGETPrepareAlgorithm(QgsProcessingAlgorithm): This algorithm is a processing version of UWG Prepare """ - INPUT_POLYGONLAYER = 'INPUT_POLYGONLAYER' - ID_FIELD = 'ID_FIELD' - INPUT_MORPH = 'INPUT_MORPH' - INPUT_LC = 'INPUT_LC' - UMEP_LC = 'UMEP_LC' - FRAC_IRR = 'FRAC_IRR' - FRAC_CONC = 'FRAC_CONC' - SITE_NAME = 'SITE_NAME' - OUTPUT_DIR = 'OUTPUT_DIR' - - + INPUT_POLYGONLAYER = "INPUT_POLYGONLAYER" + ID_FIELD = "ID_FIELD" + INPUT_MORPH = "INPUT_MORPH" + INPUT_LC = "INPUT_LC" + UMEP_LC = "UMEP_LC" + FRAC_IRR = "FRAC_IRR" + FRAC_CONC = "FRAC_CONC" + SITE_NAME = "SITE_NAME" + OUTPUT_DIR = "OUTPUT_DIR" + def initAlgorithm(self, config): - - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_POLYGONLAYER, - self.tr('Vector polygon grid'), [QgsProcessing.SourceType.TypeVectorPolygon])) - self.addParameter(QgsProcessingParameterField(self.ID_FIELD, - self.tr('ID field'),'', self.INPUT_POLYGONLAYER, QgsProcessingParameterField.DataType.Numeric)) - self.addParameter(QgsProcessingParameterFile(self.INPUT_MORPH, - self.tr('Building morphology file (.txt)'), extension='txt')) - self.addParameter(QgsProcessingParameterFile(self.INPUT_LC, - self.tr('Land cover fraction file (.txt)'), extension='txt')) - self.addParameter(QgsProcessingParameterBoolean(self.UMEP_LC, - self.tr("Use standard UMEP land cover grid (fractions below is used)"), defaultValue=True)) - self.addParameter(QgsProcessingParameterNumber(self.FRAC_IRR, - self.tr('Fraction Irrigated grass taken from Grass land cover class'), - QgsProcessingParameterNumber.Type.Double, - QVariant(0.20), False, minValue=0.0, maxValue=1.0)) - self.addParameter(QgsProcessingParameterNumber(self.FRAC_CONC, - self.tr('Fraction Concrete taken from Paved land cover class'), - QgsProcessingParameterNumber.Type.Double, - QVariant(0.25), False, minValue=0.0, maxValue=1.0)) - self.addParameter(QgsProcessingParameterString(self.SITE_NAME, - self.tr('Site name'))) - self.addParameter(QgsProcessingParameterFolderDestination(self.OUTPUT_DIR, - self.tr('Output folder'))) - self.plugin_dir = os.path.dirname(__file__) + self.addParameter( + QgsProcessingParameterFeatureSource( + self.INPUT_POLYGONLAYER, + self.tr("Vector polygon grid"), + [QgsProcessing.SourceType.TypeVectorPolygon], + ) + ) + self.addParameter( + QgsProcessingParameterField( + self.ID_FIELD, + self.tr("ID field"), + "", + self.INPUT_POLYGONLAYER, + QgsProcessingParameterField.DataType.Numeric, + ) + ) + self.addParameter( + QgsProcessingParameterFile( + self.INPUT_MORPH, + self.tr("Building morphology file (.txt)"), + extension="txt", + ) + ) + self.addParameter( + QgsProcessingParameterFile( + self.INPUT_LC, + self.tr("Land cover fraction file (.txt)"), + extension="txt", + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.UMEP_LC, + self.tr( + "Use standard UMEP land cover grid (fractions below is used)" + ), + defaultValue=True, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.FRAC_IRR, + self.tr( + "Fraction Irrigated grass taken from Grass land cover class" + ), + QgsProcessingParameterNumber.Type.Double, + QVariant(0.20), + False, + minValue=0.0, + maxValue=1.0, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.FRAC_CONC, + self.tr("Fraction Concrete taken from Paved land cover class"), + QgsProcessingParameterNumber.Type.Double, + QVariant(0.25), + False, + minValue=0.0, + maxValue=1.0, + ) + ) + self.addParameter( + QgsProcessingParameterString(self.SITE_NAME, self.tr("Site name")) + ) + self.addParameter( + QgsProcessingParameterFolderDestination( + self.OUTPUT_DIR, self.tr("Output folder") + ) + ) + self.plugin_dir = os.path.dirname(__file__) def processAlgorithm(self, parameters, context, feedback): - # InputParameters - inputPolygonlayer = self.parameterAsVectorLayer(parameters, self.INPUT_POLYGONLAYER, context) + # InputParameters + inputPolygonlayer = self.parameterAsVectorLayer( + parameters, self.INPUT_POLYGONLAYER, context + ) idField = self.parameterAsFields(parameters, self.ID_FIELD, context) - morphFile = self.parameterAsString(parameters, self.INPUT_MORPH, context) + morphFile = self.parameterAsString( + parameters, self.INPUT_MORPH, context + ) lcFile = self.parameterAsString(parameters, self.INPUT_LC, context) - umepLC = self. parameterAsBool(parameters, self.UMEP_LC, context) + umepLC = self.parameterAsBool(parameters, self.UMEP_LC, context) fracIrr = self.parameterAsDouble(parameters, self.FRAC_IRR, context) fracConc = self.parameterAsDouble(parameters, self.FRAC_CONC, context) siteName = self.parameterAsString(parameters, self.SITE_NAME, context) - outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) + outputDir = self.parameterAsString( + parameters, self.OUTPUT_DIR, context + ) - if parameters['OUTPUT_DIR'] == 'TEMPORARY_OUTPUT': + if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): os.mkdir(outputDir) - + # temporary fix for mac, ISSUE #15 pf = sys.platform - if pf == 'darwin' or pf == 'linux2' or pf == 'linux': - if not os.path.exists(outputDir + '/' + siteName): - os.makedirs(outputDir + '/' + siteName) + if pf == "darwin" or pf == "linux2" or pf == "linux": + if not os.path.exists(outputDir + "/" + siteName): + os.makedirs(outputDir + "/" + siteName) poly_field = idField vlayer = inputPolygonlayer @@ -128,38 +185,51 @@ def processAlgorithm(self, parameters, context, feedback): map_units = vlayer.crs().mapUnits() if not map_units == 0 or map_units == 1 or map_units == 2: - raise QgsProcessingException("Could not identify the map units of the polygon layer CRS.") + raise QgsProcessingException( + "Could not identify the map units of the polygon layer CRS." + ) arrmat = np.empty((1, 10)) - header = 'FID,roof,road,watr,conc,Veg,dry,irr,H,W' - numformat = '%3d, %5.3f, %5.3f, %5.3f, %5.3f, %5.3f, %5.3f, %5.3f, %5.3f, %5.3f' + header = "FID,roof,road,watr,conc,Veg,dry,irr,H,W" + numformat = "%3d, %5.3f, %5.3f, %5.3f, %5.3f, %5.3f, %5.3f, %5.3f, %5.3f, %5.3f" # Test for metre units in vector CRS grid_crs = osr.SpatialReference() grid_crs.ImportFromWkt(vlayer.crs().toWkt()) - grid_unit = grid_crs.GetAttrValue('UNIT') - feedback.setProgressText("Length unit of vector layer: " + str(grid_unit)) - possible_units_metre = ['metre', 'Metre', 'metres', 'Metres', 'meter', 'Meter', 'meters', 'Meters', 'm'] + grid_unit = grid_crs.GetAttrValue("UNIT") + feedback.setProgressText( + "Length unit of vector layer: " + str(grid_unit) + ) + possible_units_metre = [ + "metre", + "Metre", + "metres", + "Metres", + "meter", + "Meter", + "meters", + "Meters", + "m", + ] if not grid_unit in possible_units_metre: - raise QgsProcessingException('Error! Vector polygon projection is not in meters. Please reproject.') + raise QgsProcessingException( + "Error! Vector polygon projection is not in meters. Please reproject." + ) - #Get resolution of grid (res) + # Get resolution of grid (res) for feature in vlayer.getFeatures(): geom = feature.geometry() bb = geom.boundingBox() - res = np.round(bb.xMaximum() - bb.xMinimum()) + res = np.round(bb.xMaximum() - bb.xMinimum()) break - #Saving parameters file - with open(self.plugin_dir + '/parametersfortarget.json', "r") as jsn: + # Saving parameters file + with open(self.plugin_dir + "/parametersfortarget.json", "r") as jsn: param = json.load(jsn) - param['res']['value'] = res - - - + param["res"]["value"] = res - #Start loop of polygon grids + # Start loop of polygon grids ##land cover and morphology index = 0 for feature in vlayer.getFeatures(): @@ -178,21 +248,25 @@ def processAlgorithm(self, parameters, context, feedback): if feat_id == int(split[0]): if umepLC: conc = split[1] * fracConc - road = split[1] - conc + road = split[1] - conc irr = split[5] * fracIrr - dry = split [6] + split[5] - irr # bare soil is classed as dry grass + dry = ( + split[6] + split[5] - irr + ) # bare soil is classed as dry grass else: road = split[1] conc = split[9] - dry = split[5] + split[6] # bare soil is classed as dry grass + dry = ( + split[5] + split[6] + ) # bare soil is classed as dry grass irr = split[8] roof = split[2] veg = split[3] + split[4] watr = split[7] - + break - + with open(morphFile) as file: next(file) for line in file: @@ -201,58 +275,90 @@ def processAlgorithm(self, parameters, context, feedback): H = split[3] wai = split[8] pai = split[1] - if pai == 1: #response to #109 - feedback.pushWarning('Building fraction = 1 in grid: ' + str(feat_id) + '. This is unlikely. Check your DSM and DEM. Land cover roof fraction will be used.') + if pai == 1: # response to #109 + feedback.pushWarning( + "Building fraction = 1 in grid: " + + str(feat_id) + + ". This is unlikely. Check your DSM and DEM. Land cover roof fraction will be used." + ) pai = roof if pai == 1: - feedback.pushWarning('Building fraction form land cover = 1 in grid: ' + str(feat_id) + '. This is unlikely. Check your data.') + feedback.pushWarning( + "Building fraction form land cover = 1 in grid: " + + str(feat_id) + + ". This is unlikely. Check your data." + ) pai = 0.99 - if pai == 0 or wai == 0: #response to #112 + if pai == 0 or wai == 0: # response to #112 W = res else: - HW = (wai*pai)/(2*pai*(1-pai)) + HW = (wai * pai) / (2 * pai * (1 - pai)) W = H / HW if W > res: - W = res #W cannot be larger than grid resolution - + W = res # W cannot be larger than grid resolution + break - + arr = [feat_id, roof, road, watr, conc, veg, dry, irr, H, W] arrmat = np.vstack([arrmat, arr]) - arrmatsave = arrmat[1: arrmat.shape[0], :] + arrmatsave = arrmat[1 : arrmat.shape[0], :] print(arrmatsave) # adding zavg (and z0m later) in parameter file. Weighted avg from lc-file zavg = 0 fracsum = sum(arrmatsave[:, 1]) - for i in range(0,index): - zavg = zavg + arrmatsave[i, 8] * (arrmatsave[i, 1] / fracsum) - - param['zavg']['value'] = zavg - - jsonout = json.dumps(param, indent=4)#'C:/temp/targettests/my_site/parameterstest.json' + for i in range(0, index): + zavg = zavg + arrmatsave[i, 8] * (arrmatsave[i, 1] / fracsum) + + param["zavg"]["value"] = zavg + + jsonout = json.dumps( + param, indent=4 + ) #'C:/temp/targettests/my_site/parameterstest.json' path = os.path.join(outputDir, siteName) - os.makedirs(path, exist_ok=True) # create directory if it doesn’t exist. Response to #767 - with open(path + '/parameters.json', "w") as jsn2: + os.makedirs( + path, exist_ok=True + ) # create directory if it doesn’t exist. Response to #767 + with open(path + "/parameters.json", "w") as jsn2: jsn2.write(jsonout) - #creating folders and saving - if not os.path.exists(outputDir + '/' + siteName + '/' + 'input' + '/' + 'LC'): - os.makedirs(outputDir + '/' + siteName + '/' + 'input' + '/' + 'LC') - - if not os.path.exists(outputDir + '/' + siteName + '/' + 'input' + '/' + 'MET'): - os.makedirs(outputDir + '/' + siteName + '/' + 'input' + '/' + 'MET') + # creating folders and saving + if not os.path.exists( + outputDir + "/" + siteName + "/" + "input" + "/" + "LC" + ): + os.makedirs( + outputDir + "/" + siteName + "/" + "input" + "/" + "LC" + ) + + if not os.path.exists( + outputDir + "/" + siteName + "/" + "input" + "/" + "MET" + ): + os.makedirs( + outputDir + "/" + siteName + "/" + "input" + "/" + "MET" + ) + + np.savetxt( + outputDir + + "/" + + siteName + + "/" + + "input" + + "/" + + "LC" + + "/lc_target.txt", + arrmatsave, + fmt=numformat, + header=header, + comments="", + ) - np.savetxt(outputDir + '/' + siteName + '/' + 'input' + '/' + 'LC' + '/lc_target.txt', arrmatsave, - fmt=numformat, header=header, comments='') - feedback.setProgressText("Process finished") return {self.OUTPUT_DIR: outputDir} def name(self): - return 'Urban Heat Island: TARGET Prepare' + return "Urban Heat Island: TARGET Prepare" def displayName(self): return self.tr(self.name()) @@ -261,33 +367,37 @@ def group(self): return self.tr(self.groupId()) def groupId(self): - return 'Pre-Processor' + return "Pre-Processor" def shortHelpString(self): - return self.tr('THIS PLUGIN IS EXPERIMENTAL' - '\n' - 'TARGET (The Air-temperature Response to Green blue-infrastructure Evaluaition Tool) is a simple modelling framework used to examine ' - 'intra urban climate. It has specifically been developed as an efficient, easy-to-use model albe to investigate ' - 'heat mitigation effects of green and blue infrastructure within urban areas but can also be used to model the canopy urban heat island ' - '(Broadbent et al. 2019). ' - 'Possibilities to model mutiple grids or a single location is available.\n' - '\n' - 'This particular tool prepare input data used for the TARGET model found in the Processor in UMEP for processing.' - '\n' - '----------------------\n' - 'Full manual is available via the Help-button.') + return self.tr( + "THIS PLUGIN IS EXPERIMENTAL" + "\n" + "TARGET (The Air-temperature Response to Green blue-infrastructure Evaluaition Tool) is a simple modelling framework used to examine " + "intra urban climate. It has specifically been developed as an efficient, easy-to-use model albe to investigate " + "heat mitigation effects of green and blue infrastructure within urban areas but can also be used to model the canopy urban heat island " + '(Broadbent et al. 2019). ' + "Possibilities to model mutiple grids or a single location is available.\n" + "\n" + "This particular tool prepare input data used for the TARGET model found in the Processor in UMEP for processing." + "\n" + "----------------------\n" + "Full manual is available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/pre-processor/Urban%20Heat%20Island%20TARGET%20Prepare.html" return url def tr(self, string): - return QCoreApplication.translate('Processing', string) + return QCoreApplication.translate("Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/icon_uwg.png") return icon def createInstance(self): - return ProcessingTARGETPrepareAlgorithm() \ No newline at end of file + return ProcessingTARGETPrepareAlgorithm() diff --git a/preprocessor/treegenerator_algorithm.py b/preprocessor/treegenerator_algorithm.py index 7089abf..06cfaae 100644 --- a/preprocessor/treegenerator_algorithm.py +++ b/preprocessor/treegenerator_algorithm.py @@ -22,24 +22,26 @@ ***************************************************************************/ """ -__author__ = 'Fredrik Lindberg' -__date__ = '2020-04-02' -__copyright__ = '(C) 2020 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2020-04-02" +__copyright__ = "(C) 2020 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QVariant -from qgis.core import (QgsProcessing, - QgsProcessingAlgorithm, - QgsProcessingParameterRasterLayer, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterField, - QgsProcessingParameterRasterDestination, - QgsProcessingException, - QgsFeature, - QgsUnitTypes) +from qgis.core import ( + QgsProcessing, + QgsProcessingAlgorithm, + QgsProcessingParameterRasterLayer, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterField, + QgsProcessingParameterRasterDestination, + QgsProcessingException, + QgsFeature, + QgsUnitTypes, +) from qgis.PyQt.QtGui import QIcon from osgeo import gdal, osr @@ -60,84 +62,160 @@ class ProcessingTreeGeneratorAlgorithm(QgsProcessingAlgorithm): This algorithm is a processing version of TreeGenerator """ - INPUT_POINTLAYER = 'INPUT_POINTLAYER' - TREE_TYPE = 'TREE_TYPE' - TOT_HEIGHT = 'TOT_HEIGHT' - TRUNK_HEIGHT = 'TRUNK_HEIGHT' - DIA = 'DIA' - INPUT_DSM = 'INPUT_DSM' - INPUT_DEM = 'INPUT_DEM' - INPUT_BUILD = 'INPUT_BUILD' - INPUT_CDSM = 'INPUT_CDSM' - INPUT_TDSM = 'INPUT_TDSM' - - CDSM_GRID_OUT = 'CDSM_GRID_OUT' - TDSM_GRID_OUT = 'TDSM_GRID_OUT' - - + INPUT_POINTLAYER = "INPUT_POINTLAYER" + TREE_TYPE = "TREE_TYPE" + TOT_HEIGHT = "TOT_HEIGHT" + TRUNK_HEIGHT = "TRUNK_HEIGHT" + DIA = "DIA" + INPUT_DSM = "INPUT_DSM" + INPUT_DEM = "INPUT_DEM" + INPUT_BUILD = "INPUT_BUILD" + INPUT_CDSM = "INPUT_CDSM" + INPUT_TDSM = "INPUT_TDSM" + + CDSM_GRID_OUT = "CDSM_GRID_OUT" + TDSM_GRID_OUT = "TDSM_GRID_OUT" + def initAlgorithm(self, config): - - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_POINTLAYER, - self.tr('Point vector layer'), - [QgsProcessing.SourceType.TypeVectorPoint], - optional=False)) - self.addParameter(QgsProcessingParameterField(self.TREE_TYPE, - self.tr('Tree type/shape (1=conifer, 2=decidouos)'), - '', - self.INPUT_POINTLAYER, - QgsProcessingParameterField.DataType.Numeric)) - self.addParameter(QgsProcessingParameterField(self.TOT_HEIGHT, - self.tr('Total height (m)'), - '', - self.INPUT_POINTLAYER, - QgsProcessingParameterField.DataType.Numeric)) - self.addParameter(QgsProcessingParameterField(self.TRUNK_HEIGHT, - self.tr('Trunk zone height (m)'), - '', - self.INPUT_POINTLAYER, - QgsProcessingParameterField.DataType.Numeric)) - self.addParameter(QgsProcessingParameterField(self.DIA, - self.tr('Diameter (m)'), - '', - self.INPUT_POINTLAYER, - QgsProcessingParameterField.DataType.Numeric)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_BUILD, - self.tr('Building grid'), '', optional=True)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_DSM, - self.tr('Raster DSM (3D objects and ground). Use if building grid is unavailable.'), '', optional=True)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_DEM, - self.tr('Raster DEM (only ground). Use if building grid is unavailable.'), '', optional=True)) - - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_CDSM, - self.tr('Merge with existing vegetation Canopy (C)DSM'), '', optional=True)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_TDSM, - self.tr('Merge with existing vegetation Trunk Zone (T)DSM'), '', optional=True)) - - self.addParameter(QgsProcessingParameterRasterDestination(self.CDSM_GRID_OUT, - self.tr("Output CDSM"), - None, - optional=False)) - self.addParameter(QgsProcessingParameterRasterDestination(self.TDSM_GRID_OUT, - self.tr("Output TDSM"), - None, - optional=False)) + + self.addParameter( + QgsProcessingParameterFeatureSource( + self.INPUT_POINTLAYER, + self.tr("Point vector layer"), + [QgsProcessing.SourceType.TypeVectorPoint], + optional=False, + ) + ) + self.addParameter( + QgsProcessingParameterField( + self.TREE_TYPE, + self.tr("Tree type/shape (1=conifer, 2=decidouos)"), + "", + self.INPUT_POINTLAYER, + QgsProcessingParameterField.DataType.Numeric, + ) + ) + self.addParameter( + QgsProcessingParameterField( + self.TOT_HEIGHT, + self.tr("Total height (m)"), + "", + self.INPUT_POINTLAYER, + QgsProcessingParameterField.DataType.Numeric, + ) + ) + self.addParameter( + QgsProcessingParameterField( + self.TRUNK_HEIGHT, + self.tr("Trunk zone height (m)"), + "", + self.INPUT_POINTLAYER, + QgsProcessingParameterField.DataType.Numeric, + ) + ) + self.addParameter( + QgsProcessingParameterField( + self.DIA, + self.tr("Diameter (m)"), + "", + self.INPUT_POINTLAYER, + QgsProcessingParameterField.DataType.Numeric, + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_BUILD, self.tr("Building grid"), "", optional=True + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_DSM, + self.tr( + "Raster DSM (3D objects and ground). Use if building grid is unavailable." + ), + "", + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_DEM, + self.tr( + "Raster DEM (only ground). Use if building grid is unavailable." + ), + "", + optional=True, + ) + ) + + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_CDSM, + self.tr("Merge with existing vegetation Canopy (C)DSM"), + "", + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_TDSM, + self.tr("Merge with existing vegetation Trunk Zone (T)DSM"), + "", + optional=True, + ) + ) + + self.addParameter( + QgsProcessingParameterRasterDestination( + self.CDSM_GRID_OUT, + self.tr("Output CDSM"), + None, + optional=False, + ) + ) + self.addParameter( + QgsProcessingParameterRasterDestination( + self.TDSM_GRID_OUT, + self.tr("Output TDSM"), + None, + optional=False, + ) + ) def processAlgorithm(self, parameters, context, feedback): # InputParameters - inputPointLayer = self.parameterAsVectorLayer(parameters, self.INPUT_POINTLAYER, context) - ttype_field = self.parameterAsFields(parameters, self.TREE_TYPE, context) - trunk_field = self.parameterAsFields(parameters, self.TRUNK_HEIGHT, context) - tot_field = self.parameterAsFields(parameters, self.TOT_HEIGHT, context) + inputPointLayer = self.parameterAsVectorLayer( + parameters, self.INPUT_POINTLAYER, context + ) + ttype_field = self.parameterAsFields( + parameters, self.TREE_TYPE, context + ) + trunk_field = self.parameterAsFields( + parameters, self.TRUNK_HEIGHT, context + ) + tot_field = self.parameterAsFields( + parameters, self.TOT_HEIGHT, context + ) dia_field = self.parameterAsFields(parameters, self.DIA, context) - build = self.parameterAsRasterLayer(parameters, self.INPUT_BUILD, context) + build = self.parameterAsRasterLayer( + parameters, self.INPUT_BUILD, context + ) dsm = self.parameterAsRasterLayer(parameters, self.INPUT_DSM, context) dem = self.parameterAsRasterLayer(parameters, self.INPUT_DEM, context) - cdsm = self.parameterAsRasterLayer(parameters, self.INPUT_CDSM, context) - tdsm = self.parameterAsRasterLayer(parameters, self.INPUT_TDSM, context) - outputCDSM = self.parameterAsOutputLayer(parameters, self.CDSM_GRID_OUT, context) - outputTDSM = self.parameterAsOutputLayer(parameters, self.TDSM_GRID_OUT, context) - - if build: # Only building + cdsm = self.parameterAsRasterLayer( + parameters, self.INPUT_CDSM, context + ) + tdsm = self.parameterAsRasterLayer( + parameters, self.INPUT_TDSM, context + ) + outputCDSM = self.parameterAsOutputLayer( + parameters, self.CDSM_GRID_OUT, context + ) + outputTDSM = self.parameterAsOutputLayer( + parameters, self.TDSM_GRID_OUT, context + ) + + if build: # Only building dsm = None dem = None @@ -149,9 +227,13 @@ def processAlgorithm(self, parameters, context, feedback): else: # Both building ground heights build = None if dsm is None: - raise QgsProcessingException("No valid ground and building DSM raster layer is selected") + raise QgsProcessingException( + "No valid ground and building DSM raster layer is selected" + ) if dem is None: - raise QgsProcessingException("No valid ground DEM raster layer is selected") + raise QgsProcessingException( + "No valid ground DEM raster layer is selected" + ) provider = dsm.dataProvider() filePath_dsm = str(provider.dataSourceUri()) @@ -163,12 +245,16 @@ def processAlgorithm(self, parameters, context, feedback): dataset2 = gdal.Open(filePath_dem) dem_array = dataset2.ReadAsArray().astype(float) - if not (dsm_array.shape[0] == dem_array.shape[0]) & (dsm_array.shape[1] == dem_array.shape[1]): - raise QgsProcessingException("All grids must be of same pixel resolution") + if not (dsm_array.shape[0] == dem_array.shape[0]) & ( + dsm_array.shape[1] == dem_array.shape[1] + ): + raise QgsProcessingException( + "All grids must be of same pixel resolution" + ) build_array = dsm_array - dem_array - build_array[build_array < 2.] = 1. - build_array[build_array >= 2.] = 0. + build_array[build_array < 2.0] = 1.0 + build_array[build_array >= 2.0] = 0.0 sizey = build_array.shape[0] sizex = build_array.shape[1] @@ -182,7 +268,9 @@ def processAlgorithm(self, parameters, context, feedback): # tdsm = self.layerComboManagerCDSM.currentLayer() if tdsm is None: # raise QgsProcessingException("No valid vegetation TDSM raster layer is selected. Both CDSM and TDSM must be selected if merging with existing.") - feedback.setProgressText("No TDSM raster layer is selected. Creating new TDSM raster layer.") + feedback.setProgressText( + "No TDSM raster layer is selected. Creating new TDSM raster layer." + ) tdsm_array = np.zeros((sizey, sizex)) # return else: @@ -195,7 +283,7 @@ def processAlgorithm(self, parameters, context, feedback): else: cdsm_array = np.zeros((sizey, sizex)) tdsm_array = np.zeros((sizey, sizex)) - + geotransform = dataset.GetGeoTransform() scale = 1 / geotransform[1] @@ -205,15 +293,37 @@ def processAlgorithm(self, parameters, context, feedback): unit_temp = crs_temp.mapUnits() else: crs_temp = dsm.crs() - unit_temp = crs_temp.mapUnits() + unit_temp = crs_temp.mapUnits() - # print(QgsUnitTypes.toString(unit_temp)) + # print(QgsUnitTypes.toString(unit_temp)) temp_crs = osr.SpatialReference() temp_crs.ImportFromWkt(dataset.GetProjection()) - temp_unit = temp_crs.GetAttrValue('UNIT') - possible_units = ['metre', 'Metre', 'metres', 'Metres', 'meter', 'Meter', 'meters', 'Meters', 'm', 'ft', 'US survey foot', 'feet', 'Feet', 'foot', 'Foot', 'ftUS', 'International foot'] # Possible units + temp_unit = temp_crs.GetAttrValue("UNIT") + possible_units = [ + "metre", + "Metre", + "metres", + "Metres", + "meter", + "Meter", + "meters", + "Meters", + "m", + "ft", + "US survey foot", + "feet", + "Feet", + "foot", + "Foot", + "ftUS", + "International foot", + ] # Possible units if not temp_unit in possible_units: - raise QgsProcessingException('Error! Raster data is currently in ' + QgsUnitTypes.toString(unit_temp) + '. Meters or feet required. Please reproject.') + raise QgsProcessingException( + "Error! Raster data is currently in " + + QgsUnitTypes.toString(unit_temp) + + ". Meters or feet required. Please reproject." + ) return # Get attributes @@ -227,28 +337,46 @@ def processAlgorithm(self, parameters, context, feedback): width = dataset.RasterXSize height = dataset.RasterYSize minx = geotransform[0] - miny = geotransform[3] + width * geotransform[4] + height * geotransform[5] + miny = ( + geotransform[3] + + width * geotransform[4] + + height * geotransform[5] + ) rows = build_array.shape[0] cols = build_array.shape[1] # Check CRS of raster and vector layers. Needs to be the same. if build: if cdsm: - if not ((build.crs().authid() == vlayer.crs().authid()) & (build.crs().authid() == cdsm.crs().authid())): - raise QgsProcessingException("Error! Check the coordinate systems of your input data. Have to match!") + if not ( + (build.crs().authid() == vlayer.crs().authid()) + & (build.crs().authid() == cdsm.crs().authid()) + ): + raise QgsProcessingException( + "Error! Check the coordinate systems of your input data. Have to match!" + ) return else: if not (build.crs().authid() == vlayer.crs().authid()): - raise QgsProcessingException("Error! Check the coordinate systems of your input data. Have to match!") + raise QgsProcessingException( + "Error! Check the coordinate systems of your input data. Have to match!" + ) return else: if cdsm: - if not ((dsm.crs().authid() == vlayer.crs().authid()) & (dsm.crs().authid() == cdsm.crs().authid())): - raise QgsProcessingException("Error! Check the coordinate systems of your input data. Have to match!") + if not ( + (dsm.crs().authid() == vlayer.crs().authid()) + & (dsm.crs().authid() == cdsm.crs().authid()) + ): + raise QgsProcessingException( + "Error! Check the coordinate systems of your input data. Have to match!" + ) return else: if not (dsm.crs().authid() == vlayer.crs().authid()): - raise QgsProcessingException("Error! Check the coordinate systems of your input data. Have to match!") + raise QgsProcessingException( + "Error! Check the coordinate systems of your input data. Have to match!" + ) return index = 1 @@ -287,10 +415,24 @@ def processAlgorithm(self, parameters, context, feedback): # Check if there are trees with a tree canopy diameter smaller than the pixel resolution of the input raster data if dia < geotransform[1]: - raise QgsProcessingException("Error! You have tree canopy diameters that are smaller than the pixel resolution.") - - cdsm_array, tdsm_array = makevegdems.vegunitsgeneration(build_array, cdsm_array, tdsm_array, ttype, height, - trunk, dia, rowa, cola, sizex, sizey, scale) + raise QgsProcessingException( + "Error! You have tree canopy diameters that are smaller than the pixel resolution." + ) + + cdsm_array, tdsm_array = makevegdems.vegunitsgeneration( + build_array, + cdsm_array, + tdsm_array, + ttype, + height, + trunk, + dia, + rowa, + cola, + sizex, + sizey, + scale, + ) saverasternd(dataset, outputCDSM, cdsm_array) saverasternd(dataset, outputTDSM, tdsm_array) @@ -298,9 +440,9 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText("Processing finished.") return {self.CDSM_GRID_OUT: outputCDSM, self.TDSM_GRID_OUT: outputTDSM} - + def name(self): - return 'Spatial Data: Tree Generator' + return "Spatial Data: Tree Generator" def displayName(self): return self.tr(self.name()) @@ -309,30 +451,34 @@ def group(self): return self.tr(self.groupId()) def groupId(self): - return 'Pre-Processor' + return "Pre-Processor" def shortHelpString(self): - return self.tr('Information 3d vegetation is not a common spatial information available. ' - 'The Tree Generator can be used to create or alter a raster-ased vegetation Canopy ' - 'Digital Surface Model (CDSM) and Trunk Zone (T)DSM (see Help for abbreviations). ' - 'Information from a point layer where the location of the points specifies the tree ' - 'positions and the attributes sets the shape of the trees, is used to produce ' - 'a 3d vegetation raster needed for e.g. Mean radiant temperature modelling (SOLWEIG) ' - 'or Urban Energy Balance modelling (SUEWS) in UMEP.\n' - '---------------\n' - 'Full manual available via the Help-button.') - + return self.tr( + "Information 3d vegetation is not a common spatial information available. " + "The Tree Generator can be used to create or alter a raster-ased vegetation Canopy " + "Digital Surface Model (CDSM) and Trunk Zone (T)DSM (see Help for abbreviations). " + "Information from a point layer where the location of the points specifies the tree " + "positions and the attributes sets the shape of the trees, is used to produce " + "a 3d vegetation raster needed for e.g. Mean radiant temperature modelling (SOLWEIG) " + "or Urban Energy Balance modelling (SUEWS) in UMEP.\n" + "---------------\n" + "Full manual available via the Help-button." + ) + def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/pre-processor/Spatial%20Data%20Tree%20Generator.html" return url def tr(self, string): - return QCoreApplication.translate('Pre-Processing', string) + return QCoreApplication.translate("Pre-Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/icon_tree.png") return icon def createInstance(self): - return ProcessingTreeGeneratorAlgorithm() \ No newline at end of file + return ProcessingTreeGeneratorAlgorithm() diff --git a/preprocessor/urock_prepare_algorithm.py b/preprocessor/urock_prepare_algorithm.py index e3d7540..b2fc8de 100644 --- a/preprocessor/urock_prepare_algorithm.py +++ b/preprocessor/urock_prepare_algorithm.py @@ -4,7 +4,7 @@ /*************************************************************************** URockPrepare A QGIS plugin - This plugin generates URock spatial inputs: building and vegetation vector layers with height attribute + This plugin generates URock spatial inputs: building and vegetation vector layers with height attribute using rasters (DEM, DSM, CDSM) and building footprint Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ ------------------- @@ -23,34 +23,37 @@ ***************************************************************************/ """ -__author__ = 'Jérémy Bernard / University of Gothenburg' -__date__ = '2022-07-15' -__copyright__ = '(C) 2022 by Jérémy Bernard / University of Gothenburg' +__author__ = "Jérémy Bernard / University of Gothenburg" +__date__ = "2022-07-15" +__copyright__ = "(C) 2022 by Jérémy Bernard / University of Gothenburg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" import os import re import tempfile from qgis.PyQt.QtCore import QCoreApplication -from qgis.core import (QgsProcessing, - QgsProcessingAlgorithm, - QgsProcessingParameterField, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterString, - QgsProcessingParameterRasterLayer, - QgsProcessingParameterVectorDestination, - QgsCoordinateReferenceSystem, - QgsProperty, - QgsProcessingException) +from qgis.core import ( + QgsProcessing, + QgsProcessingAlgorithm, + QgsProcessingParameterField, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterString, + QgsProcessingParameterRasterLayer, + QgsProcessingParameterVectorDestination, + QgsCoordinateReferenceSystem, + QgsProperty, + QgsProcessingException, +) import processing from qgis.PyQt.QtGui import QIcon import inspect from pathlib import Path from ..functions.URock.GlobalVariables import * + class URockPrepareAlgorithm(QgsProcessingAlgorithm): """ This is an example algorithm that takes a vector layer and @@ -70,12 +73,12 @@ class URockPrepareAlgorithm(QgsProcessingAlgorithm): # calling from the QGIS console. # Input variables - INPUT_BUILD_FOOTPRINT = 'INPUT_BUILD_FOOTPRINT' + INPUT_BUILD_FOOTPRINT = "INPUT_BUILD_FOOTPRINT" INPUT_BUILD_DEM = "INPUT_BUILD_DEM" INPUT_BUILD_DSM = "INPUT_BUILD_DSM" INPUT_VEG_CDSM = "INPUT_VEG_CDSM" INPUT_VEG_POINTS = "INPUT_VEG_POINTS" - + # OTHER PARAMETERS HEIGHT_VEG_FIELD = "HEIGHT_VEG_FIELD" RADIUS_VEG_FIELD = "RADIUS_VEG_FIELD" @@ -83,10 +86,10 @@ class URockPrepareAlgorithm(QgsProcessingAlgorithm): OUTPUT_BUILD_HEIGHT_FIELD = "OUTPUT_BUILD_HEIGHT_FIELD" OUTPUT_VEG_HEIGHT_FIELD = "OUTPUT_VEG_HEIGHT_FIELD" - # Output variables + # Output variables OUTPUT_BUILDING_FILE = "BUILDINGS_WITH_HEIGHT" OUTPUT_VEGETATION_FILE = "VEGETATION_WITH_HEIGHT" - + def initAlgorithm(self, config): """ Here we define the inputs and output of the algorithm, along @@ -97,82 +100,118 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterFeatureSource( self.INPUT_BUILD_FOOTPRINT, - self.tr('Buildings footprint'), + self.tr("Buildings footprint"), [QgsProcessing.SourceType.TypeVectorPolygon], - optional = True)) + optional=True, + ) + ) self.addParameter( QgsProcessingParameterRasterLayer( self.INPUT_BUILD_DSM, - self.tr('Buildings raster DSM (3D objects + ground or only 3D objects) [m asl or m agl]'), + self.tr( + "Buildings raster DSM (3D objects + ground or only 3D objects) [m asl or m agl]" + ), None, - optional = True)) + optional=True, + ) + ) self.addParameter( QgsProcessingParameterRasterLayer( self.INPUT_BUILD_DEM, - self.tr('DEM (ground - only if building DSM is 3D objects + ground) [m asl]'), + self.tr( + "DEM (ground - only if building DSM is 3D objects + ground) [m asl]" + ), None, - optional = True)) + optional=True, + ) + ) self.addParameter( QgsProcessingParameterRasterLayer( self.INPUT_VEG_CDSM, - self.tr('Vegetation raster DSM (3D canopy) [m agl]'), + self.tr("Vegetation raster DSM (3D canopy) [m agl]"), None, - optional = True)) + optional=True, + ) + ) self.addParameter( QgsProcessingParameterFeatureSource( self.INPUT_VEG_POINTS, - self.tr('Vegetation point data (trunk location and max height)'), + self.tr( + "Vegetation point data (trunk location and max height)" + ), [QgsProcessing.SourceType.TypeVectorPoint], - optional = True)) - - + optional=True, + ) + ) + # Some input parameters self.addParameter( QgsProcessingParameterField( self.HEIGHT_VEG_FIELD, - self.tr('Vegetation height field'), + self.tr("Vegetation height field"), None, self.INPUT_VEG_POINTS, QgsProcessingParameterField.DataType.Numeric, - optional = True)) + optional=True, + ) + ) self.addParameter( QgsProcessingParameterField( self.RADIUS_VEG_FIELD, - self.tr('Horizontal vegetation radius field'), + self.tr("Horizontal vegetation radius field"), None, self.INPUT_VEG_POINTS, QgsProcessingParameterField.DataType.Numeric, - optional = True)) + optional=True, + ) + ) self.addParameter( QgsProcessingParameterString( self.VEGETATION_ASPECT, - self.tr('Tree height / tree crown radius ratio used if either height or radius value is missing'), - defaultValue = 0.75, - optional = True)) + self.tr( + "Tree height / tree crown radius ratio used if either height or radius value is missing" + ), + defaultValue=0.75, + optional=True, + ) + ) # We add several output parameters self.addParameter( QgsProcessingParameterVectorDestination( self.OUTPUT_BUILDING_FILE, - self.tr('Output building vector file'), - defaultValue = os.path.join(TEMPO_DIRECTORY, f"build_vector{OUTPUT_VECTOR_EXTENSION}"))) + self.tr("Output building vector file"), + defaultValue=os.path.join( + TEMPO_DIRECTORY, f"build_vector{OUTPUT_VECTOR_EXTENSION}" + ), + ) + ) self.addParameter( QgsProcessingParameterString( self.OUTPUT_BUILD_HEIGHT_FIELD, - self.tr('Attribute name for building height in output table'), - defaultValue = 'ROOF_HEIGHT', - optional = True)) + self.tr("Attribute name for building height in output table"), + defaultValue="ROOF_HEIGHT", + optional=True, + ) + ) self.addParameter( QgsProcessingParameterVectorDestination( self.OUTPUT_VEGETATION_FILE, - self.tr('Output vegetation vector file'),optional=True,createByDefault=False)) #,defaultValue = os.path.join(TEMPO_DIRECTORY, f"veg_vector{OUTPUT_VECTOR_EXTENSION}") + self.tr("Output vegetation vector file"), + optional=True, + createByDefault=False, + ) + ) # ,defaultValue = os.path.join(TEMPO_DIRECTORY, f"veg_vector{OUTPUT_VECTOR_EXTENSION}") self.addParameter( QgsProcessingParameterString( self.OUTPUT_VEG_HEIGHT_FIELD, - self.tr('Attribute name for vegetation height in output table'), - defaultValue = 'VEG_HEIGHT', - optional = True)) - + self.tr( + "Attribute name for vegetation height in output table" + ), + defaultValue="VEG_HEIGHT", + optional=True, + ) + ) def processAlgorithm(self, parameters, context, feedback): """ @@ -180,44 +219,76 @@ def processAlgorithm(self, parameters, context, feedback): """ # Get the tmp directory to save some intermediate results with a known file name tmp_dir = tempfile.gettempdir() - + # Get building and vegetation layers - inputBuildinglayer = self.parameterAsVectorLayer(parameters, self.INPUT_BUILD_FOOTPRINT, context) - inputVeglayer = self.parameterAsVectorLayer(parameters, self.INPUT_VEG_POINTS, context) + inputBuildinglayer = self.parameterAsVectorLayer( + parameters, self.INPUT_BUILD_FOOTPRINT, context + ) + inputVeglayer = self.parameterAsVectorLayer( + parameters, self.INPUT_VEG_POINTS, context + ) # Get vegetation height attribute - heightVegField = self.parameterAsString(parameters, self.HEIGHT_VEG_FIELD, context) - radiusVegField = self.parameterAsString(parameters, self.RADIUS_VEG_FIELD, context) - + heightVegField = self.parameterAsString( + parameters, self.HEIGHT_VEG_FIELD, context + ) + radiusVegField = self.parameterAsString( + parameters, self.RADIUS_VEG_FIELD, context + ) + # Get some other parameters related to vegetation - vegetationAspect = self.parameterAsDouble(parameters, self.VEGETATION_ASPECT, context) - + vegetationAspect = self.parameterAsDouble( + parameters, self.VEGETATION_ASPECT, context + ) + # Get building raster layers - build_dsm = self.parameterAsRasterLayer(parameters, self.INPUT_BUILD_DSM, context) - build_dem = self.parameterAsRasterLayer(parameters, self.INPUT_BUILD_DEM, context) - veg_dsm = self.parameterAsRasterLayer(parameters, self.INPUT_VEG_CDSM, context) - + build_dsm = self.parameterAsRasterLayer( + parameters, self.INPUT_BUILD_DSM, context + ) + build_dem = self.parameterAsRasterLayer( + parameters, self.INPUT_BUILD_DEM, context + ) + veg_dsm = self.parameterAsRasterLayer( + parameters, self.INPUT_VEG_CDSM, context + ) + # Get output file paths - outputBuildFilepath = self.parameterAsOutputLayer(parameters, self.OUTPUT_BUILDING_FILE, context) - buildingHeightField = self.parameterAsString(parameters, self.OUTPUT_BUILD_HEIGHT_FIELD, context) - outputVegFilepath = self.parameterAsOutputLayer(parameters, self.OUTPUT_VEGETATION_FILE, context) - vegetHeightField = self.parameterAsString(parameters, self.OUTPUT_VEG_HEIGHT_FIELD, context) - + outputBuildFilepath = self.parameterAsOutputLayer( + parameters, self.OUTPUT_BUILDING_FILE, context + ) + buildingHeightField = self.parameterAsString( + parameters, self.OUTPUT_BUILD_HEIGHT_FIELD, context + ) + outputVegFilepath = self.parameterAsOutputLayer( + parameters, self.OUTPUT_VEGETATION_FILE, context + ) + vegetHeightField = self.parameterAsString( + parameters, self.OUTPUT_VEG_HEIGHT_FIELD, context + ) + # If output not set, create temporary files for building and vegetation veg_out_basepath = outputVegFilepath.split(".")[0] veg_out_ext = outputVegFilepath.split(".")[-1].lower() build_out_basepath = outputBuildFilepath.split(".")[0] build_out_ext = outputBuildFilepath.split(".")[-1].lower() - if veg_out_ext == 'file': - outputVegFilepath = os.path.join(tmp_dir, f"veg_vector{OUTPUT_VECTOR_EXTENSION}") - elif ((veg_out_ext != "geojson") and (veg_out_ext != "shp")): - outputVegFilepath = veg_out_basepath + OUTPUT_VECTOR_EXTENSION - feedback.pushWarning(f'.gpkg format is currently not available, output vegetation file extension has been changed to {OUTPUT_VECTOR_EXTENSION}') - if build_out_ext == 'file': - outputBuildFilepath = os.path.join(tmp_dir, f"build_vector{OUTPUT_VECTOR_EXTENSION}") - elif ((build_out_ext != "geojson") and (build_out_ext != "shp")): - outputBuildFilepath = build_out_basepath + OUTPUT_VECTOR_EXTENSION - feedback.pushWarning(f'.gpkg format is currently not available, output building file extension has been changed to {OUTPUT_VECTOR_EXTENSION}') + if veg_out_ext == "file": + outputVegFilepath = os.path.join( + tmp_dir, f"veg_vector{OUTPUT_VECTOR_EXTENSION}" + ) + elif (veg_out_ext != "geojson") and (veg_out_ext != "shp"): + outputVegFilepath = veg_out_basepath + OUTPUT_VECTOR_EXTENSION + feedback.pushWarning( + f".gpkg format is currently not available, output vegetation file extension has been changed to {OUTPUT_VECTOR_EXTENSION}" + ) + if build_out_ext == "file": + outputBuildFilepath = os.path.join( + tmp_dir, f"build_vector{OUTPUT_VECTOR_EXTENSION}" + ) + elif (build_out_ext != "geojson") and (build_out_ext != "shp"): + outputBuildFilepath = build_out_basepath + OUTPUT_VECTOR_EXTENSION + feedback.pushWarning( + f".gpkg format is currently not available, output building file extension has been changed to {OUTPUT_VECTOR_EXTENSION}" + ) # BUILDING LAYER CREATION # Create the building vector layer if at least building footprint and building dsm have been provided @@ -226,163 +297,269 @@ def processAlgorithm(self, parameters, context, feedback): srid_vbuild = inputBuildinglayer.crs().postgisSrid() srid_dsm_build = build_dsm.crs().postgisSrid() if srid_vbuild != srid_dsm_build: - build_dsm = processing.run("gdal:warpreproject", - {'INPUT': build_dsm, - 'SOURCE_CRS':QgsCoordinateReferenceSystem('EPSG:{0}'.format(srid_dsm_build)), - 'TARGET_CRS':QgsCoordinateReferenceSystem('EPSG:{0}'.format(srid_vbuild)), - 'RESAMPLING':0,'NODATA':None,'TARGET_RESOLUTION':None, - 'OPTIONS':'','DATA_TYPE':0,'TARGET_EXTENT':None, - 'TARGET_EXTENT_CRS':None,'MULTITHREADING':False, - 'EXTRA':'','OUTPUT':os.path.join(tmp_dir, - "build_dsm")})["OUTPUT"] - # Set file name since they are used in raster calculator formula later - build_dsm_fieldname = "build_dsm" + build_dsm = processing.run( + "gdal:warpreproject", + { + "INPUT": build_dsm, + "SOURCE_CRS": QgsCoordinateReferenceSystem( + "EPSG:{0}".format(srid_dsm_build) + ), + "TARGET_CRS": QgsCoordinateReferenceSystem( + "EPSG:{0}".format(srid_vbuild) + ), + "RESAMPLING": 0, + "NODATA": None, + "TARGET_RESOLUTION": None, + "OPTIONS": "", + "DATA_TYPE": 0, + "TARGET_EXTENT": None, + "TARGET_EXTENT_CRS": None, + "MULTITHREADING": False, + "EXTRA": "", + "OUTPUT": os.path.join(tmp_dir, "build_dsm"), + }, + )["OUTPUT"] + # Set file name since they are used in raster calculator formula later + build_dsm_fieldname = "build_dsm" else: - # Get file name since they are used in raster calculator formula later - build_dsm_filename = str(build_dsm.dataProvider().dataSourceUri()).split(os.sep)[-1].split(".")[0] - build_dsm_fieldname = re.sub('[-0123456789]', '', build_dsm_filename)[0:11] + # Get file name since they are used in raster calculator formula later + build_dsm_filename = ( + str(build_dsm.dataProvider().dataSourceUri()) + .split(os.sep)[-1] + .split(".")[0] + ) + build_dsm_fieldname = re.sub( + "[-0123456789]", "", build_dsm_filename + )[0:11] # Create DSM above ground if a DEM is provided if build_dem: # Reproject the building DEM to the building vector projection if needed srid_dem_build = build_dem.crs().postgisSrid() if srid_vbuild != srid_dem_build: - build_dem = processing.run("gdal:warpreproject", - {'INPUT': build_dem, - 'SOURCE_CRS':QgsCoordinateReferenceSystem('EPSG:{0}'.format(srid_dem_build)), - 'TARGET_CRS':QgsCoordinateReferenceSystem('EPSG:{0}'.format(srid_vbuild)), - 'RESAMPLING':0,'NODATA':None,'TARGET_RESOLUTION':None, - 'OPTIONS':'','DATA_TYPE':0,'TARGET_EXTENT':None, - 'TARGET_EXTENT_CRS':None,'MULTITHREADING':False, - 'EXTRA':'','OUTPUT':os.path.join(tmp_dir, - "build_dem")})["OUTPUT"] - # Set file name since they are used in raster calculator formula later - build_dem_fieldname = "build_dem" + build_dem = processing.run( + "gdal:warpreproject", + { + "INPUT": build_dem, + "SOURCE_CRS": QgsCoordinateReferenceSystem( + "EPSG:{0}".format(srid_dem_build) + ), + "TARGET_CRS": QgsCoordinateReferenceSystem( + "EPSG:{0}".format(srid_vbuild) + ), + "RESAMPLING": 0, + "NODATA": None, + "TARGET_RESOLUTION": None, + "OPTIONS": "", + "DATA_TYPE": 0, + "TARGET_EXTENT": None, + "TARGET_EXTENT_CRS": None, + "MULTITHREADING": False, + "EXTRA": "", + "OUTPUT": os.path.join(tmp_dir, "build_dem"), + }, + )["OUTPUT"] + # Set file name since they are used in raster calculator formula later + build_dem_fieldname = "build_dem" else: # Get file name since they are used in raster calculator formula later - build_dem_filename = str(build_dem.dataProvider().dataSourceUri()).split(os.sep)[-1].split(".")[0] - build_dem_fieldname = re.sub('[-0123456789]', '', build_dem_filename)[0:11] - + build_dem_filename = ( + str(build_dem.dataProvider().dataSourceUri()) + .split(os.sep)[-1] + .split(".")[0] + ) + build_dem_fieldname = re.sub( + "[-0123456789]", "", build_dem_filename + )[0:11] + # Calculate the difference between DSM and DEM (set negative values to 0) - diff_expression = '(\"{0}@1\">\"{1}@1\") * (\"{0}@1\"-\"{1}@1\") + (\"{0}@1\" <= \"{1}@1\") * 0'.format(build_dsm_fieldname, - build_dem_fieldname) - - build_dsm = processing.run("gdal:rastercalculator", - {'INPUT_A':build_dsm, - 'BAND_A':1, - 'INPUT_B':build_dem, - 'BAND_B':1, - 'INPUT_C':None, - 'BAND_C':None, - 'INPUT_D':None, - 'BAND_D':None, - 'INPUT_E':None, - 'BAND_E':None, - 'INPUT_F':None, - 'BAND_F':None, - 'FORMULA':'(A > B) * (A - B) + (A <= B) * 0', - 'NO_DATA':None, - 'RTYPE':5, - 'OPTIONS':'', - 'EXTRA':'','OUTPUT':'TEMPORARY_OUTPUT'})["OUTPUT"] - + diff_expression = '("{0}@1">"{1}@1") * ("{0}@1"-"{1}@1") + ("{0}@1" <= "{1}@1") * 0'.format( + build_dsm_fieldname, build_dem_fieldname + ) + + build_dsm = processing.run( + "gdal:rastercalculator", + { + "INPUT_A": build_dsm, + "BAND_A": 1, + "INPUT_B": build_dem, + "BAND_B": 1, + "INPUT_C": None, + "BAND_C": None, + "INPUT_D": None, + "BAND_D": None, + "INPUT_E": None, + "BAND_E": None, + "INPUT_F": None, + "BAND_F": None, + "FORMULA": "(A > B) * (A - B) + (A <= B) * 0", + "NO_DATA": None, + "RTYPE": 5, + "OPTIONS": "", + "EXTRA": "", + "OUTPUT": "TEMPORARY_OUTPUT", + }, + )["OUTPUT"] + # Make valid all vector geometries - tempoBuildinglayer = processing.run("native:fixgeometries", - {'INPUT':inputBuildinglayer, - 'OUTPUT':'TEMPORARY_OUTPUT'})["OUTPUT"] - + tempoBuildinglayer = processing.run( + "native:fixgeometries", + {"INPUT": inputBuildinglayer, "OUTPUT": "TEMPORARY_OUTPUT"}, + )["OUTPUT"] + # Calculate the median height of the DSM within each building footprint - tempoBuildinglayer2 = processing.run("native:zonalstatisticsfb", - {'INPUT':tempoBuildinglayer, - 'INPUT_RASTER':build_dsm, - 'RASTER_BAND':1, - 'COLUMN_PREFIX':'height_', - 'STATISTICS':[3], - 'OUTPUT':'TEMPORARY_OUTPUT'})["OUTPUT"] - + tempoBuildinglayer2 = processing.run( + "native:zonalstatisticsfb", + { + "INPUT": tempoBuildinglayer, + "INPUT_RASTER": build_dsm, + "RASTER_BAND": 1, + "COLUMN_PREFIX": "height_", + "STATISTICS": [3], + "OUTPUT": "TEMPORARY_OUTPUT", + }, + )["OUTPUT"] + # Rename the building height attribute - outputBuildFilepath = processing.run("native:renametablefield", - {'INPUT':tempoBuildinglayer2,'FIELD':'height_median', - 'NEW_NAME':buildingHeightField, - 'OUTPUT':outputBuildFilepath})["OUTPUT"] - + outputBuildFilepath = processing.run( + "native:renametablefield", + { + "INPUT": tempoBuildinglayer2, + "FIELD": "height_median", + "NEW_NAME": buildingHeightField, + "OUTPUT": outputBuildFilepath, + }, + )["OUTPUT"] + # In case user provide a DSM and a DEM but no building footprint elif build_dsm: - feedback.pushWarning('You have provided a DSM but no building footprint. Please note that if you want to create a building layer for URock, you need to provide a building footprint in this preprocessing') - + feedback.pushWarning( + "You have provided a DSM but no building footprint. Please note that if you want to create a building layer for URock, you need to provide a building footprint in this preprocessing" + ) # VEGETATION LAYER CREATION # Create the vegetation vector layer if vegetation DSM has been provided if veg_dsm and inputVeglayer: - raise QgsProcessingException('A single vegetation input should be provided, either DSM or vector') + raise QgsProcessingException( + "A single vegetation input should be provided, either DSM or vector" + ) elif veg_dsm or inputVeglayer: # Rasterize the DSM if veg_dsm: # Get input raster vegetation srid srid_vveg = veg_dsm.crs().postgisSrid() - if not srid_vveg: - feedback.pushWarning('Note that your vegetation layer has no SRID, thus the output has also no SRID') - + if not srid_vveg: + feedback.pushWarning( + "Note that your vegetation layer has no SRID, thus the output has also no SRID" + ) + # Round raster values in order to have less vegetation polygons - veg_dsm = processing.run("native:roundrastervalues", - {'INPUT':veg_dsm,'BAND':1, - 'ROUNDING_DIRECTION':1,'DECIMAL_PLACES':0, - 'OUTPUT':'TEMPORARY_OUTPUT', - 'BASE_N':10})["OUTPUT"] + veg_dsm = processing.run( + "native:roundrastervalues", + { + "INPUT": veg_dsm, + "BAND": 1, + "ROUNDING_DIRECTION": 1, + "DECIMAL_PLACES": 0, + "OUTPUT": "TEMPORARY_OUTPUT", + "BASE_N": 10, + }, + )["OUTPUT"] # Rasterize by height value class - # First round raster values - veg_dsm_rounded = processing.run("native:roundrastervalues", - {'INPUT':veg_dsm, - 'BAND':1, - 'ROUNDING_DIRECTION':1, - 'DECIMAL_PLACES':0, - 'OUTPUT':'TEMPORARY_OUTPUT', - 'BASE_N':10})["OUTPUT"] - - # Then vectorized - veg_vect = processing.run("native:pixelstopolygons", - {'INPUT_RASTER':veg_dsm_rounded, - 'RASTER_BAND':1, - 'FIELD_NAME':'VALUE', - 'OUTPUT':'TEMPORARY_OUTPUT'})["OUTPUT"] - - # Last, groupby and union by height values - tempoVegFilepath = processing.run("native:dissolve", - {'INPUT':veg_vect, - 'FIELD':['VALUE'], - 'OUTPUT':'TEMPORARY_OUTPUT'})["OUTPUT"] - + # First round raster values + veg_dsm_rounded = processing.run( + "native:roundrastervalues", + { + "INPUT": veg_dsm, + "BAND": 1, + "ROUNDING_DIRECTION": 1, + "DECIMAL_PLACES": 0, + "OUTPUT": "TEMPORARY_OUTPUT", + "BASE_N": 10, + }, + )["OUTPUT"] + + # Then vectorized + veg_vect = processing.run( + "native:pixelstopolygons", + { + "INPUT_RASTER": veg_dsm_rounded, + "RASTER_BAND": 1, + "FIELD_NAME": "VALUE", + "OUTPUT": "TEMPORARY_OUTPUT", + }, + )["OUTPUT"] + + # Last, groupby and union by height values + tempoVegFilepath = processing.run( + "native:dissolve", + { + "INPUT": veg_vect, + "FIELD": ["VALUE"], + "OUTPUT": "TEMPORARY_OUTPUT", + }, + )["OUTPUT"] + # Remove vegetation height = 0 m - tempoVegFilepath2 = processing.run("native:extractbyattribute", - {'INPUT':tempoVegFilepath,'FIELD':'VALUE', - 'OPERATOR':2,'VALUE':'0', - 'OUTPUT':'TEMPORARY_OUTPUT'})["OUTPUT"] + tempoVegFilepath2 = processing.run( + "native:extractbyattribute", + { + "INPUT": tempoVegFilepath, + "FIELD": "VALUE", + "OPERATOR": 2, + "VALUE": "0", + "OUTPUT": "TEMPORARY_OUTPUT", + }, + )["OUTPUT"] # Set the final table a projection - tempoVegFilepath3 = processing.run("native:assignprojection", - {'INPUT':tempoVegFilepath2, - 'CRS':QgsCoordinateReferenceSystem('EPSG:{0}'.format(srid_vveg)), - 'OUTPUT':'TEMPORARY_OUTPUT'})["OUTPUT"] - + tempoVegFilepath3 = processing.run( + "native:assignprojection", + { + "INPUT": tempoVegFilepath2, + "CRS": QgsCoordinateReferenceSystem( + "EPSG:{0}".format(srid_vveg) + ), + "OUTPUT": "TEMPORARY_OUTPUT", + }, + )["OUTPUT"] + # Rename the vegetation height attribute - outputVegFilepath = processing.run("native:renametablefield", - {'INPUT':tempoVegFilepath3,'FIELD':'VALUE', - 'NEW_NAME':vegetHeightField, - 'OUTPUT':outputVegFilepath})["OUTPUT"] - + outputVegFilepath = processing.run( + "native:renametablefield", + { + "INPUT": tempoVegFilepath3, + "FIELD": "VALUE", + "NEW_NAME": vegetHeightField, + "OUTPUT": outputVegFilepath, + }, + )["OUTPUT"] + # Create vegetation patches from vegetation points elif inputVeglayer: if not radiusVegField and not heightVegField: - raise QgsProcessingException('At least tree crown radius or tree height attribute should be provided') + raise QgsProcessingException( + "At least tree crown radius or tree height attribute should be provided" + ) else: if radiusVegField and not heightVegField: - distanceExpression = 'case when {0} is null then 0 else {0} end'.format(radiusVegField) - heightExpression = 'case when {0} is null then 0 else {0}/{1} end'.format(radiusVegField, - vegetationAspect) + distanceExpression = ( + "case when {0} is null then 0 else {0} end".format( + radiusVegField + ) + ) + heightExpression = "case when {0} is null then 0 else {0}/{1} end".format( + radiusVegField, vegetationAspect + ) elif not radiusVegField and heightVegField: - distanceExpression = 'case when {0} is null then 0 else {0}*{1} end'.format(radiusVegField, - vegetationAspect) - heightExpression = 'case when {0} is null then 0 else {0} end'.format(heightVegField) + distanceExpression = "case when {0} is null then 0 else {0}*{1} end".format( + radiusVegField, vegetationAspect + ) + heightExpression = ( + "case when {0} is null then 0 else {0} end".format( + heightVegField + ) + ) elif radiusVegField and heightVegField: distanceExpression = """ case when {0} is null and {1} is null then 0 @@ -390,39 +567,54 @@ def processAlgorithm(self, parameters, context, feedback): then {1}*{2} else {0} end - """.format(radiusVegField, - heightVegField, - vegetationAspect) + """.format( + radiusVegField, heightVegField, vegetationAspect + ) heightExpression = """ case when {0} is null and {1} is null then 0 when {1} is null and {0} is not null then {0}/{2} else {1} end - """.format(radiusVegField, - heightVegField, - vegetationAspect) - # Apply a buffer using radius (or height) fields - tempoVegetationlayer = processing.run("native:buffer", - {'INPUT':inputVeglayer, - 'DISTANCE':QgsProperty.fromExpression(distanceExpression), - 'SEGMENTS':5,'END_CAP_STYLE':0,'JOIN_STYLE':0, - 'MITER_LIMIT':2,'DISSOLVE':False, - 'OUTPUT':'TEMPORARY_OUTPUT'})["OUTPUT"] + """.format( + radiusVegField, heightVegField, vegetationAspect + ) + # Apply a buffer using radius (or height) fields + tempoVegetationlayer = processing.run( + "native:buffer", + { + "INPUT": inputVeglayer, + "DISTANCE": QgsProperty.fromExpression( + distanceExpression + ), + "SEGMENTS": 5, + "END_CAP_STYLE": 0, + "JOIN_STYLE": 0, + "MITER_LIMIT": 2, + "DISSOLVE": False, + "OUTPUT": "TEMPORARY_OUTPUT", + }, + )["OUTPUT"] # Calculates tree height when field do not exists or to null (using radius field) - outputVegFilepath = processing.run("native:fieldcalculator", - {'INPUT':tempoVegetationlayer, - 'FIELD_NAME':vegetHeightField, - 'FIELD_TYPE':0,'FIELD_LENGTH':0, - 'FIELD_PRECISION':0, - 'FORMULA':heightExpression, - 'OUTPUT':outputVegFilepath})["OUTPUT"] - - + outputVegFilepath = processing.run( + "native:fieldcalculator", + { + "INPUT": tempoVegetationlayer, + "FIELD_NAME": vegetHeightField, + "FIELD_TYPE": 0, + "FIELD_LENGTH": 0, + "FIELD_PRECISION": 0, + "FORMULA": heightExpression, + "OUTPUT": outputVegFilepath, + }, + )["OUTPUT"] + # Return the output file names if veg_dsm: - return {self.OUTPUT_BUILDING_FILE: outputBuildFilepath, - self.OUTPUT_VEGETATION_FILE: outputVegFilepath} + return { + self.OUTPUT_BUILDING_FILE: outputBuildFilepath, + self.OUTPUT_VEGETATION_FILE: outputVegFilepath, + } else: return {self.OUTPUT_BUILDING_FILE: outputBuildFilepath} @@ -434,7 +626,7 @@ def name(self): lowercase alphanumeric characters only and no spaces or other formatting characters. """ - return 'Urban Wind Field: URock Prepare' + return "Urban Wind Field: URock Prepare" def displayName(self): """ @@ -458,27 +650,31 @@ def groupId(self): contain lowercase alphanumeric characters only and no spaces or other formatting characters. """ - return 'Pre-Processor' + return "Pre-Processor" def tr(self, string): - return QCoreApplication.translate('Processing', string) + return QCoreApplication.translate("Processing", string) def shortHelpString(self): - return self.tr('The urock_prepare is used to create building '+ - 'and vegetation polygon vector layers.'+ - ' The output can directly be used as input of the URock plugin. '+ - 'Only a single vegetation layer should be provided as input, '+ - 'either a vegetation DSM or vegetation point data (trunk location).' - '\n' - '---------------\n' - 'Full manual available via the Help-button.') + return self.tr( + "The urock_prepare is used to create building " + + "and vegetation polygon vector layers." + + " The output can directly be used as input of the URock plugin. " + + "Only a single vegetation layer should be provided as input, " + + "either a vegetation DSM or vegetation point data (trunk location)." + "\n" + "---------------\n" + "Full manual available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/pre-processor/Urban%20Wind%20Field%20URock%20Prepare.html" return url - + def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/urock.png") return icon diff --git a/preprocessor/uwgprepare_algorithm.py b/preprocessor/uwgprepare_algorithm.py index 7cf6e4a..48c7c54 100644 --- a/preprocessor/uwgprepare_algorithm.py +++ b/preprocessor/uwgprepare_algorithm.py @@ -22,26 +22,28 @@ ***************************************************************************/ """ -__author__ = 'Fredrik Lindberg' -__date__ = '2022-02-07' -__copyright__ = '(C) 2020 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2022-02-07" +__copyright__ = "(C) 2020 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QVariant -from qgis.core import (QgsProcessing, - QgsProcessingAlgorithm, - QgsProcessingParameterFile, - QgsProcessingParameterString, - QgsProcessingParameterNumber, - QgsProcessingParameterFolderDestination, - QgsProcessingParameterEnum, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterField, - QgsProcessingException, - QgsVectorLayer) +from qgis.core import ( + QgsProcessing, + QgsProcessingAlgorithm, + QgsProcessingParameterFile, + QgsProcessingParameterString, + QgsProcessingParameterNumber, + QgsProcessingParameterFolderDestination, + QgsProcessingParameterEnum, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterField, + QgsProcessingException, + QgsVectorLayer, +) from qgis.PyQt.QtGui import QIcon from osgeo.gdalconst import * @@ -59,62 +61,109 @@ class ProcessingUWGPrepareAlgorithm(QgsProcessingAlgorithm): This algorithm is a processing version of UWG Prepare """ - INPUT_POLYGONLAYER = 'INPUT_POLYGONLAYER' - ID_FIELD = 'ID_FIELD' - INPUT_POLYGONLAYERTYPOLOGY = 'INPUT_POLYGONLAYERTYPOLOGY' - INPUT_MORPH = 'INPUT_MORPH' - INPUT_LC = 'INPUT_LC' - INPUT_RURAL = 'INPUT_RURAL' - CLIMATEZONE = 'CLIMATEZONE' - FILE_PREFIX = 'FILE_PREFIX' - OUTPUT_DIR = 'OUTPUT_DIR' - - + INPUT_POLYGONLAYER = "INPUT_POLYGONLAYER" + ID_FIELD = "ID_FIELD" + INPUT_POLYGONLAYERTYPOLOGY = "INPUT_POLYGONLAYERTYPOLOGY" + INPUT_MORPH = "INPUT_MORPH" + INPUT_LC = "INPUT_LC" + INPUT_RURAL = "INPUT_RURAL" + CLIMATEZONE = "CLIMATEZONE" + FILE_PREFIX = "FILE_PREFIX" + OUTPUT_DIR = "OUTPUT_DIR" + def initAlgorithm(self, config): - - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_POLYGONLAYER, - self.tr('Vector polygon grid'), [QgsProcessing.SourceType.TypeVectorPolygon])) - self.addParameter(QgsProcessingParameterField(self.ID_FIELD, - self.tr('ID field'),'', self.INPUT_POLYGONLAYER, QgsProcessingParameterField.DataType.Numeric)) - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_POLYGONLAYERTYPOLOGY, - self.tr('Building type polygon layer'), [QgsProcessing.SourceType.TypeVectorPolygon], optional=True)) - - self.addParameter(QgsProcessingParameterFile(self.INPUT_MORPH, - self.tr('Building morphology file (.txt)'), extension='txt')) - self.addParameter(QgsProcessingParameterFile(self.INPUT_LC, - self.tr('Land cover file (.txt)'), extension='txt')) - - self.zone = ((self.tr('1A; Very hot, humid; (Miami, FL)'), '0'), - (self.tr('1B; Hot, dry; '), '1'), - (self.tr('2A; Hot, Humid; (Huston, TX)'), '2'), - (self.tr('3A; Warm, Humid; (Atalanta, GA)'), '3'), - (self.tr('3B; Warm, Dry; (Las Vegas, NV)'), '4'), - (self.tr('3C; Warm, Marine; (San Francisco, CA)'), '5'), - (self.tr('4A; Mild, Humid; (Baltimore, MD)'), '6'), - (self.tr('4B; Mild, Dry; (Albuquerque, NM)'), '7'), - (self.tr('4C; Mild, Marine; (Seattle, WA)'), '8'), - (self.tr('5A; Cold, Humid; (Chicago, IL)'), '9'), - (self.tr('5B; Cold, Dry; (Boulder, CO)'), '10'), - (self.tr('5C; Cold, Marine;'), '11'), - (self.tr('6A; Cold, Humid; (Minneapolis, MN)'), '12'), - (self.tr('6B; Cold, Dry; (Helena, MT)'), '13'), - (self.tr('7; Very Cold; (Duluth, MN)'), '14'), - (self.tr('8; Sub-Artic; (Fairbanks, AK)'), '15')) - - self.addParameter(QgsProcessingParameterEnum(self.CLIMATEZONE, - self.tr('Climate zone'), - options=[i[0] for i in self.zone], defaultValue=0)) - - self.addParameter(QgsProcessingParameterNumber(self.INPUT_RURAL, - self.tr('Fraction vegetation at rural site'), - QgsProcessingParameterNumber.Type.Double, - QVariant(0.9), False, minValue=0.0, maxValue=1.0)) - - self.addParameter(QgsProcessingParameterString(self.FILE_PREFIX, - self.tr('File code'))) - - self.addParameter(QgsProcessingParameterFolderDestination(self.OUTPUT_DIR, - self.tr('Output folder'))) + + self.addParameter( + QgsProcessingParameterFeatureSource( + self.INPUT_POLYGONLAYER, + self.tr("Vector polygon grid"), + [QgsProcessing.SourceType.TypeVectorPolygon], + ) + ) + self.addParameter( + QgsProcessingParameterField( + self.ID_FIELD, + self.tr("ID field"), + "", + self.INPUT_POLYGONLAYER, + QgsProcessingParameterField.DataType.Numeric, + ) + ) + self.addParameter( + QgsProcessingParameterFeatureSource( + self.INPUT_POLYGONLAYERTYPOLOGY, + self.tr("Building type polygon layer"), + [QgsProcessing.SourceType.TypeVectorPolygon], + optional=True, + ) + ) + + self.addParameter( + QgsProcessingParameterFile( + self.INPUT_MORPH, + self.tr("Building morphology file (.txt)"), + extension="txt", + ) + ) + self.addParameter( + QgsProcessingParameterFile( + self.INPUT_LC, + self.tr("Land cover file (.txt)"), + extension="txt", + ) + ) + + self.zone = ( + (self.tr("1A; Very hot, humid; (Miami, FL)"), "0"), + (self.tr("1B; Hot, dry; "), "1"), + (self.tr("2A; Hot, Humid; (Huston, TX)"), "2"), + (self.tr("3A; Warm, Humid; (Atalanta, GA)"), "3"), + (self.tr("3B; Warm, Dry; (Las Vegas, NV)"), "4"), + (self.tr("3C; Warm, Marine; (San Francisco, CA)"), "5"), + (self.tr("4A; Mild, Humid; (Baltimore, MD)"), "6"), + (self.tr("4B; Mild, Dry; (Albuquerque, NM)"), "7"), + (self.tr("4C; Mild, Marine; (Seattle, WA)"), "8"), + (self.tr("5A; Cold, Humid; (Chicago, IL)"), "9"), + (self.tr("5B; Cold, Dry; (Boulder, CO)"), "10"), + (self.tr("5C; Cold, Marine;"), "11"), + (self.tr("6A; Cold, Humid; (Minneapolis, MN)"), "12"), + (self.tr("6B; Cold, Dry; (Helena, MT)"), "13"), + (self.tr("7; Very Cold; (Duluth, MN)"), "14"), + (self.tr("8; Sub-Artic; (Fairbanks, AK)"), "15"), + ) + + self.addParameter( + QgsProcessingParameterEnum( + self.CLIMATEZONE, + self.tr("Climate zone"), + options=[i[0] for i in self.zone], + defaultValue=0, + ) + ) + + self.addParameter( + QgsProcessingParameterNumber( + self.INPUT_RURAL, + self.tr("Fraction vegetation at rural site"), + QgsProcessingParameterNumber.Type.Double, + QVariant(0.9), + False, + minValue=0.0, + maxValue=1.0, + ) + ) + + self.addParameter( + QgsProcessingParameterString( + self.FILE_PREFIX, self.tr("File code") + ) + ) + + self.addParameter( + QgsProcessingParameterFolderDestination( + self.OUTPUT_DIR, self.tr("Output folder") + ) + ) self.plugin_dir = os.path.dirname(__file__) @@ -123,32 +172,43 @@ def initAlgorithm(self, config): # self.dir_poly = self.plugin_dir + '/data/poly_temp.shp' def processAlgorithm(self, parameters, context, feedback): - # InputParameters - inputPolygonlayer = self.parameterAsVectorLayer(parameters, self.INPUT_POLYGONLAYER, context) + # InputParameters + inputPolygonlayer = self.parameterAsVectorLayer( + parameters, self.INPUT_POLYGONLAYER, context + ) idField = self.parameterAsFields(parameters, self.ID_FIELD, context) - polyBT = self.parameterAsVectorLayer(parameters, self.INPUT_POLYGONLAYERTYPOLOGY, context) - morphFile = self.parameterAsString(parameters, self.INPUT_MORPH, context) + polyBT = self.parameterAsVectorLayer( + parameters, self.INPUT_POLYGONLAYERTYPOLOGY, context + ) + morphFile = self.parameterAsString( + parameters, self.INPUT_MORPH, context + ) lcFile = self.parameterAsString(parameters, self.INPUT_LC, context) - rurVegCover = self.parameterAsDouble(parameters, self.INPUT_RURAL, context) - climateZone = self.parameterAsString(parameters, self.CLIMATEZONE, context) + rurVegCover = self.parameterAsDouble( + parameters, self.INPUT_RURAL, context + ) + climateZone = self.parameterAsString( + parameters, self.CLIMATEZONE, context + ) prefix = self.parameterAsString(parameters, self.FILE_PREFIX, context) - outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) + outputDir = self.parameterAsString( + parameters, self.OUTPUT_DIR, context + ) - - if parameters['OUTPUT_DIR'] == 'TEMPORARY_OUTPUT': + if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): os.mkdir(outputDir) - if not (os.path.isdir(self.plugin_dir + '/tempdata')): - os.mkdir(self.plugin_dir + '/tempdata') + if not (os.path.isdir(self.plugin_dir + "/tempdata")): + os.mkdir(self.plugin_dir + "/tempdata") pre = prefix - + # temporary fix for mac, ISSUE #15 pf = sys.platform - if pf == 'darwin' or pf == 'linux2' or pf == 'linux': - if not os.path.exists(outputDir + '/' + pre): - os.makedirs(outputDir + '/' + pre) + if pf == "darwin" or pf == "linux2" or pf == "linux": + if not os.path.exists(outputDir + "/" + pre): + os.makedirs(outputDir + "/" + pre) poly_field = idField vlayer = inputPolygonlayer @@ -156,69 +216,80 @@ def processAlgorithm(self, parameters, context, feedback): index = 1 feedback.setProgressText("Number of grids to process: " + str(nGrids)) - path=vlayer.dataProvider().dataSourceUri() - if path.rfind('|') > 0: - polygonpath = path [:path.rfind('|')] # work around. Probably other solution exists + path = vlayer.dataProvider().dataSourceUri() + if path.rfind("|") > 0: + polygonpath = path[ + : path.rfind("|") + ] # work around. Probably other solution exists else: polygonpath = path map_units = vlayer.crs().mapUnits() if not map_units == 0 or map_units == 1 or map_units == 2: - raise QgsProcessingException("Could not identify the map units of the polygon layer CRS.") + raise QgsProcessingException( + "Could not identify the map units of the polygon layer CRS." + ) if polyBT is None: - feedback.pushWarning('No valid building type polygon layer is selected. All buildings are classified as mid-rise residental buildings.') + feedback.pushWarning( + "No valid building type polygon layer is selected. All buildings are classified as mid-rise residental buildings." + ) vlayerBT = None else: vlayerBT = polyBT - pathBT=vlayerBT.dataProvider().dataSourceUri() - if pathBT.rfind('|') > 0: - polygonpathBT = pathBT [:pathBT.rfind('|')] # work around. Probably other solution exists + pathBT = vlayerBT.dataProvider().dataSourceUri() + if pathBT.rfind("|") > 0: + polygonpathBT = pathBT[ + : pathBT.rfind("|") + ] # work around. Probably other solution exists else: polygonpathBT = pathBT a = {} - a['0'] = '1A' - a['1'] = '1B' - a['2'] = '2A' - a['3'] = '3A' - a['4'] = '3B' - a['5'] = '3C' - a['6'] = '4A' - a['7'] = '4B' - a['8'] = '4C' - a['9'] = '5A' - a['10'] = '5B' - a['11'] = '5C' - a['12'] = '6A' - a['13'] = '6B' - a['14'] = '7' - a['15'] = '8' + a["0"] = "1A" + a["1"] = "1B" + a["2"] = "2A" + a["3"] = "3A" + a["4"] = "3B" + a["5"] = "3C" + a["6"] = "4A" + a["7"] = "4B" + a["8"] = "4C" + a["9"] = "5A" + a["10"] = "5B" + a["11"] = "5C" + a["12"] = "6A" + a["13"] = "6B" + a["14"] = "7" + a["15"] = "8" zone = a[climateZone] - # Intersect grids with building type polygons + # Intersect grids with building type polygons if vlayerBT: import processing - urbantypelayer = self.plugin_dir + '/tempdata/' + 'intersected.shp' - intersectPrefix = 'i' - parin = { 'INPUT' : polygonpath, - 'INPUT_FIELDS' : [], - 'OUTPUT' : urbantypelayer, - 'OVERLAY' : polygonpathBT, - 'OVERLAY_FIELDS' : [], - 'OVERLAY_FIELDS_PREFIX' : intersectPrefix } + urbantypelayer = self.plugin_dir + "/tempdata/" + "intersected.shp" + + intersectPrefix = "i" + parin = { + "INPUT": polygonpath, + "INPUT_FIELDS": [], + "OUTPUT": urbantypelayer, + "OVERLAY": polygonpathBT, + "OVERLAY_FIELDS": [], + "OVERLAY_FIELDS_PREFIX": intersectPrefix, + } # feedback.setProgressText(str(parin)) - processing.run('native:intersection', parin) + processing.run("native:intersection", parin) vlayertype = QgsVectorLayer(urbantypelayer, "polygon", "ogr") - type_field = parin['OVERLAY_FIELDS_PREFIX'] + 'uwgType' - time_field = parin['OVERLAY_FIELDS_PREFIX'] + 'uwgTime' + type_field = parin["OVERLAY_FIELDS_PREFIX"] + "uwgType" + time_field = parin["OVERLAY_FIELDS_PREFIX"] + "uwgTime" - #Start loop of polygon grids + # Start loop of polygon grids ##land cover and morphology index = 0 for feature in vlayer.getFeatures(): @@ -234,8 +305,8 @@ def processAlgorithm(self, parameters, context, feedback): # create a default dict with all input uwgDict = create_uwgdict() - uwgDict['zone'] = zone - uwgDict['charLength'] = feature.geometry().area() ** 0.5 + uwgDict["zone"] = zone + uwgDict["charLength"] = feature.geometry().area() ** 0.5 with open(lcFile) as file: next(file) @@ -248,7 +319,7 @@ def processAlgorithm(self, parameters, context, feedback): LCF_decidious = split[4] LCF_grass = split[5] break - + with open(morphFile) as file: next(file) for line in file: @@ -259,23 +330,80 @@ def processAlgorithm(self, parameters, context, feedback): break # Populate dict from UMEP - uwgDict['bldHeight'] = IMP_heights_mean # average building height (m) - uwgDict['bldDensity'] = LCF_buildings # urban area building plan density (0-1) - uwgDict['verToHor'] = IMP_wai # urban area vertical to horizontal ratio - uwgDict['grasscover'] = LCF_grass # Fraction of the urban ground covered in grass/shrubs only (0-1) - uwgDict['treeCover'] = str(float(LCF_decidious) + float(LCF_evergreen)) # Fraction of the urban ground covered in trees (0-1) + uwgDict["bldHeight"] = ( + IMP_heights_mean # average building height (m) + ) + uwgDict["bldDensity"] = ( + LCF_buildings # urban area building plan density (0-1) + ) + uwgDict["verToHor"] = ( + IMP_wai # urban area vertical to horizontal ratio + ) + uwgDict["grasscover"] = ( + LCF_grass # Fraction of the urban ground covered in grass/shrubs only (0-1) + ) + uwgDict["treeCover"] = str( + float(LCF_decidious) + float(LCF_evergreen) + ) # Fraction of the urban ground covered in trees (0-1) ## urban type fractions if vlayerBT: fracDict = {} totarea = 0.0 - types = ['FullServiceRestaurant','Hospital','LargeHotel','LargeOffice','MedOffice', - 'MidRiseApartment','OutPatient','PrimarySchool','QuickServiceRestaurant', - 'SecondarySchool','SmallHotel','SmallOffice','StandAloneRetail','StripMall', - 'SuperMarket','Warehouse'] - buildtime = ['Pst80','Pst80','Pst80','Pst80','Pst80','Pre80','Pst80','Pst80', - 'Pst80','Pst80','Pst80','Pst80','Pst80','Pst80','Pst80','Pst80'] #this should also come from an unique post for each polygon... - fractions = [.0,.0,.0,.0,.0,.0,.0,.0,.0,.0,.0,.0,.0,.0,.0,.0] + types = [ + "FullServiceRestaurant", + "Hospital", + "LargeHotel", + "LargeOffice", + "MedOffice", + "MidRiseApartment", + "OutPatient", + "PrimarySchool", + "QuickServiceRestaurant", + "SecondarySchool", + "SmallHotel", + "SmallOffice", + "StandAloneRetail", + "StripMall", + "SuperMarket", + "Warehouse", + ] + buildtime = [ + "Pst80", + "Pst80", + "Pst80", + "Pst80", + "Pst80", + "Pre80", + "Pst80", + "Pst80", + "Pst80", + "Pst80", + "Pst80", + "Pst80", + "Pst80", + "Pst80", + "Pst80", + "Pst80", + ] # this should also come from an unique post for each polygon... + fractions = [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ] fracDict = dict(zip(types, fractions)) timeDict = dict(zip(types, buildtime)) @@ -284,32 +412,40 @@ def processAlgorithm(self, parameters, context, feedback): for featureType in vlayertype.getFeatures(): if feat_id == int(featureType.attribute(poly_field[0])): area = featureType.geometry().area() - fracDict[featureType.attribute(type_field)] = fracDict[featureType.attribute(type_field)] + area - timeDict[featureType.attribute(type_field)] = featureType.attribute(time_field) + fracDict[featureType.attribute(type_field)] = ( + fracDict[featureType.attribute(type_field)] + area + ) + timeDict[featureType.attribute(type_field)] = ( + featureType.attribute(time_field) + ) totarea = totarea + area for key in fracDict: if totarea > 0: fracDict[key] = fracDict[key] / totarea else: - fracDict['MidRiseApartment'] = 1.0 + fracDict["MidRiseApartment"] = 1.0 # Populate dict from type polygon layer - for i in range(0, len(uwgDict['bld'][0])): - uwgDict['bld'][1][i] = timeDict[types[i]] - uwgDict['bld'][2][i] = fracDict[types[i]] + for i in range(0, len(uwgDict["bld"][0])): + uwgDict["bld"][1][i] = timeDict[types[i]] + uwgDict["bld"][2][i] = fracDict[types[i]] - uwgDict['rurVegCover'] = rurVegCover # Fraction of the rural ground covered by vegetation + uwgDict["rurVegCover"] = ( + rurVegCover # Fraction of the rural ground covered by vegetation + ) ## generate input files for UWG - _name = prefix + '_' + str(feat_id) - get_uwg_file(uwgDict, outputDir + '/', _name) + _name = prefix + "_" + str(feat_id) + get_uwg_file(uwgDict, outputDir + "/", _name) - feedback.setProgressText("Urban Weather Generator input files succesfully generated") + feedback.setProgressText( + "Urban Weather Generator input files succesfully generated" + ) return {self.OUTPUT_DIR: outputDir} def name(self): - return 'Urban Heat Island: UWG Prepare' + return "Urban Heat Island: UWG Prepare" def displayName(self): return self.tr(self.name()) @@ -318,34 +454,38 @@ def group(self): return self.tr(self.groupId()) def groupId(self): - return 'Pre-Processor' + return "Pre-Processor" def shortHelpString(self): - return self.tr('THIS PLUGIN IS EXPERIMENTAL' - '\n' - 'The Urban Weather Generator plugin can be used to model the urban heat island effect. Possibilities to model mutiple grids or a single location is available.
' - '\n' - 'For more detailed information during execution, open the QGIS Python console (Plugins>Python Console).' - '\n' - 'NOTE: This plugin requires the uwg python library. Instructions on how to install missing python libraries using the pip command can be found here: ' - 'https://umep-docs.readthedocs.io/en/latest/Getting_Started.html")' - '\n' - 'If you are having issues that certain grids fails to be calculated you can try to reduce the simulation time step, preferably to 150 or 100 seconds. This will increase computation time.' - '\n' - '----------------------\n' - 'Full manual is available via the Help-button.') + return self.tr( + "THIS PLUGIN IS EXPERIMENTAL" + "\n" + "The Urban Weather Generator plugin can be used to model the urban heat island effect. Possibilities to model mutiple grids or a single location is available.
" + "\n" + "For more detailed information during execution, open the QGIS Python console (Plugins>Python Console)." + "\n" + "NOTE: This plugin requires the uwg python library. Instructions on how to install missing python libraries using the pip command can be found here: " + 'https://umep-docs.readthedocs.io/en/latest/Getting_Started.html")' + "\n" + "If you are having issues that certain grids fails to be calculated you can try to reduce the simulation time step, preferably to 150 or 100 seconds. This will increase computation time." + "\n" + "----------------------\n" + "Full manual is available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/pre-processor/Urban%20Heat%20Island%20UWG%20Prepare.html" return url def tr(self, string): - return QCoreApplication.translate('Processing', string) + return QCoreApplication.translate("Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/icon_uwg.png") return icon def createInstance(self): - return ProcessingUWGPrepareAlgorithm() \ No newline at end of file + return ProcessingUWGPrepareAlgorithm() diff --git a/preprocessor/wall_heightaspect_algorithm.py b/preprocessor/wall_heightaspect_algorithm.py index 7997922..598e8d2 100644 --- a/preprocessor/wall_heightaspect_algorithm.py +++ b/preprocessor/wall_heightaspect_algorithm.py @@ -22,19 +22,21 @@ ***************************************************************************/ """ -__author__ = 'Fredrik Lindberg' -__date__ = '2020-04-02' -__copyright__ = '(C) 2020 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2020-04-02" +__copyright__ = "(C) 2020 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QVariant -from qgis.core import (QgsProcessingAlgorithm, - QgsProcessingParameterNumber, - QgsProcessingParameterRasterLayer, - QgsProcessingParameterRasterDestination,) +from qgis.core import ( + QgsProcessingAlgorithm, + QgsProcessingParameterNumber, + QgsProcessingParameterRasterLayer, + QgsProcessingParameterRasterDestination, +) from osgeo import gdal from qgis.PyQt.QtWidgets import QAction, QFileDialog, QMessageBox @@ -47,7 +49,6 @@ from pathlib import Path from ..util.misc import saverasternd - # def saverasternd(gdal_data, filename, raster): # rows = gdal_data.RasterYSize # cols = gdal_data.RasterXSize @@ -68,73 +69,105 @@ class ProcessingWallHeightAscpetAlgorithm(QgsProcessingAlgorithm): - INPUT_LIMIT = 'INPUT_LIMIT' - INPUT = 'INPUT' - OUTPUT_HEIGHT = 'OUTPUT_HEIGHT' - OUTPUT_ASPECT = 'OUTPUT_ASPECT' + INPUT_LIMIT = "INPUT_LIMIT" + INPUT = "INPUT" + OUTPUT_HEIGHT = "OUTPUT_HEIGHT" + OUTPUT_ASPECT = "OUTPUT_ASPECT" # ASPECT_BOOL = 'ASPECT_BOOL' - def initAlgorithm(self, config): - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT, - self.tr('Input building and ground DSM'), - None, False)) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT, + self.tr("Input building and ground DSM"), + None, + False, + ) + ) # self.addParameter(QgsProcessingParameterBoolean(self.ASPECT_BOOL, # self.tr("Calculate wall aspect"), - # defaultValue=True)) - self.addParameter(QgsProcessingParameterNumber(self.INPUT_LIMIT, - self.tr("Lower limit for wall height (m)"), - QgsProcessingParameterNumber.Type.Double, - QVariant(3.0), - minValue=0.0)) - self.addParameter(QgsProcessingParameterRasterDestination(self.OUTPUT_HEIGHT, - self.tr("Output Wall Height Raster"), - None, False)) - self.addParameter(QgsProcessingParameterRasterDestination(self.OUTPUT_ASPECT, - self.tr("Output Wall Aspect Raster"), optional=True, createByDefault=False)) + # defaultValue=True)) + self.addParameter( + QgsProcessingParameterNumber( + self.INPUT_LIMIT, + self.tr("Lower limit for wall height (m)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(3.0), + minValue=0.0, + ) + ) + self.addParameter( + QgsProcessingParameterRasterDestination( + self.OUTPUT_HEIGHT, + self.tr("Output Wall Height Raster"), + None, + False, + ) + ) + self.addParameter( + QgsProcessingParameterRasterDestination( + self.OUTPUT_ASPECT, + self.tr("Output Wall Aspect Raster"), + optional=True, + createByDefault=False, + ) + ) def processAlgorithm(self, parameters, context, feedback): - outputFileHeight = self.parameterAsOutputLayer(parameters, self.OUTPUT_HEIGHT, context) - outputFileAspect = self.parameterAsOutputLayer(parameters, self.OUTPUT_ASPECT, context) - dsmin = self.parameterAsRasterLayer(parameters, self.INPUT, context) + outputFileHeight = self.parameterAsOutputLayer( + parameters, self.OUTPUT_HEIGHT, context + ) + outputFileAspect = self.parameterAsOutputLayer( + parameters, self.OUTPUT_ASPECT, context + ) + dsmin = self.parameterAsRasterLayer(parameters, self.INPUT, context) # aspectcalculation = self.parameterAsBool(parameters, self.ASPECT_BOOL, context) - walllimit = self.parameterAsDouble(parameters, self.INPUT_LIMIT, context) + walllimit = self.parameterAsDouble( + parameters, self.INPUT_LIMIT, context + ) - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]) + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ) feedback.setProgressText(str(cmd_folder)) feedback.setProgressText(str(cmd_folder.parent)) - + # feedback.setProgressText(str(parameters["INPUT"])) # this prints to the processing log tab # QgsMessageLog.logMessage("Testing", "umep", level=Qgis.Info) # This prints to a umep tab - + provider = dsmin.dataProvider() filepath_dsm = str(provider.dataSourceUri()) gdal_dsm = gdal.Open(filepath_dsm) dsm = gdal_dsm.ReadAsArray().astype(float) - + feedback.setProgressText("Calculating wall height") - total = 100. / (int(dsm.shape[0] * dsm.shape[1])) + total = 100.0 / (int(dsm.shape[0] * dsm.shape[1])) # walls = wa.findwalls(dsm, walllimit, feedback, total) walls = wa.findwalls_sp(dsm, walllimit, False) wallssave = np.copy(walls) # feedback.setProgressText(outputFileHeight) saverasternd(gdal_dsm, outputFileHeight, wallssave) - + if outputFileAspect: - total = 100. / 180.0 + total = 100.0 / 180.0 # outputFileAspect = self.parameterAsOutputLayer(parameters, self.OUTPUT_ASPECT, context) feedback.setProgressText("Calculating wall aspect") - dirwalls = wa.filter1Goodwin_as_aspect_v3(walls, 1, dsm, feedback, total) + dirwalls = wa.filter1Goodwin_as_aspect_v3( + walls, 1, dsm, feedback, total + ) saverasternd(gdal_dsm, outputFileAspect, dirwalls) else: feedback.setProgressText("Wall aspect not calculated") - - return {self.OUTPUT_HEIGHT: outputFileHeight, self.OUTPUT_ASPECT: outputFileAspect} + + return { + self.OUTPUT_HEIGHT: outputFileHeight, + self.OUTPUT_ASPECT: outputFileAspect, + } def name(self): - return 'Urban Geometry: Wall Height and Aspect' + return "Urban Geometry: Wall Height and Aspect" def displayName(self): return self.tr(self.name()) @@ -143,35 +176,39 @@ def group(self): return self.tr(self.groupId()) def groupId(self): - return 'Pre-Processor' + return "Pre-Processor" def shortHelpString(self): - return self.tr('This algorithm identiies wall pixels and ' - 'their height from ground and building digital surface models (DSM) by using a filter as ' - 'presented by Lindberg et al. (2015a). Optionally, wall aspect can also be estimated using ' - 'a specific linear filter as presented by Goodwin et al. (1999) and further developed by ' - 'Lindberg et al. (2015b) to obtain the wall aspect. Wall aspect is given in degrees where ' - 'a north facing wall pixel has a value of zero. The output of this plugin is used in other ' - 'UMEP plugins such as SEBE (Solar Energy on Building Envelopes) and SOLWEIG (SOlar LongWave ' - 'Environmental Irradiance Geometry model).\n' - '------------------ \n' - 'Goodwin NR, Coops NC, Tooke TR, Christen A, Voogt JA (2009) Characterizing urban surface cover and structure with airborne lidar technology. Can J Remote Sens 35:297–309\n' - 'Lindberg F., Grimmond, C.S.B. and Martilli, A. (2015a) Sunlit fractions on urban facets - Impact of spatial resolution and approach Urban Climate DOI: 10.1016/j.uclim.2014.11.006\n' - 'Lindberg F., Jonsson, P. & Honjo, T. and Wästberg, D. (2015b) Solar energy on building envelopes - 3D modelling in a 2D environment Solar Energy 115 369–378' - '-------------\n' - 'Full manual available via the Help-button.') + return self.tr( + "This algorithm identiies wall pixels and " + "their height from ground and building digital surface models (DSM) by using a filter as " + "presented by Lindberg et al. (2015a). Optionally, wall aspect can also be estimated using " + "a specific linear filter as presented by Goodwin et al. (1999) and further developed by " + "Lindberg et al. (2015b) to obtain the wall aspect. Wall aspect is given in degrees where " + "a north facing wall pixel has a value of zero. The output of this plugin is used in other " + "UMEP plugins such as SEBE (Solar Energy on Building Envelopes) and SOLWEIG (SOlar LongWave " + "Environmental Irradiance Geometry model).\n" + "------------------ \n" + "Goodwin NR, Coops NC, Tooke TR, Christen A, Voogt JA (2009) Characterizing urban surface cover and structure with airborne lidar technology. Can J Remote Sens 35:297–309\n" + "Lindberg F., Grimmond, C.S.B. and Martilli, A. (2015a) Sunlit fractions on urban facets - Impact of spatial resolution and approach Urban Climate DOI: 10.1016/j.uclim.2014.11.006\n" + "Lindberg F., Jonsson, P. & Honjo, T. and Wästberg, D. (2015b) Solar energy on building envelopes - 3D modelling in a 2D environment Solar Energy 115 369–378" + "-------------\n" + "Full manual available via the Help-button." + ) def helpUrl(self): - url = 'https://umep-docs.readthedocs.io/en/latest/pre-processor/Urban%20Geometry%20Wall%20Height%20and%20Aspect.html' + url = "https://umep-docs.readthedocs.io/en/latest/pre-processor/Urban%20Geometry%20Wall%20Height%20and%20Aspect.html" return url def tr(self, string): - return QCoreApplication.translate('Processing', string) + return QCoreApplication.translate("Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/WallsIcon.png") return icon def createInstance(self): - return ProcessingWallHeightAscpetAlgorithm() \ No newline at end of file + return ProcessingWallHeightAscpetAlgorithm() diff --git a/processing_umep.py b/processing_umep.py index db67829..e39c3b8 100644 --- a/processing_umep.py +++ b/processing_umep.py @@ -22,13 +22,13 @@ ***************************************************************************/ """ -__author__ = 'Fredrik Lindberg' -__date__ = '2020-04-02' -__copyright__ = '(C) 2020 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2020-04-02" +__copyright__ = "(C) 2020 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" import os import sys diff --git a/processing_umep_provider.py b/processing_umep_provider.py index f0d3cf5..b4af314 100644 --- a/processing_umep_provider.py +++ b/processing_umep_provider.py @@ -22,48 +22,77 @@ ***************************************************************************/ """ -__author__ = 'Fredrik Lindberg' -__date__ = '2020-04-02' -__copyright__ = '(C) 2020 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2020-04-02" +__copyright__ = "(C) 2020 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.core import QgsProcessingProvider -from .preprocessor.wall_heightaspect_algorithm import ProcessingWallHeightAscpetAlgorithm -from .preprocessor.skyviewfactor_algorithm import ProcessingSkyViewFactorAlgorithm -from .preprocessor.copernicusera5_algorithm import ProcessingCopernicusERA5Algorithm -from .preprocessor.imagemorphparmspoint_algorithm import ProcessingImageMorphParmsPointAlgorithm -from .preprocessor.imagemorphparms_algorithm import ProcessingImageMorphParmsAlgorithm -from .preprocessor.landcoverfractionpoint_algorithm import ProcessingLandCoverFractionPointAlgorithm -from .preprocessor.landcoverfraction_algorithm import ProcessingLandCoverFractionAlgorithm -from .preprocessor.dsm_generator_algorithm import ProcessingDSMGeneratorAlgorithm -from .preprocessor.treegenerator_algorithm import ProcessingTreeGeneratorAlgorithm +from .preprocessor.wall_heightaspect_algorithm import ( + ProcessingWallHeightAscpetAlgorithm, +) +from .preprocessor.skyviewfactor_algorithm import ( + ProcessingSkyViewFactorAlgorithm, +) +from .preprocessor.copernicusera5_algorithm import ( + ProcessingCopernicusERA5Algorithm, +) +from .preprocessor.imagemorphparmspoint_algorithm import ( + ProcessingImageMorphParmsPointAlgorithm, +) +from .preprocessor.imagemorphparms_algorithm import ( + ProcessingImageMorphParmsAlgorithm, +) +from .preprocessor.landcoverfractionpoint_algorithm import ( + ProcessingLandCoverFractionPointAlgorithm, +) +from .preprocessor.landcoverfraction_algorithm import ( + ProcessingLandCoverFractionAlgorithm, +) +from .preprocessor.dsm_generator_algorithm import ( + ProcessingDSMGeneratorAlgorithm, +) +from .preprocessor.treegenerator_algorithm import ( + ProcessingTreeGeneratorAlgorithm, +) from .preprocessor.uwgprepare_algorithm import ProcessingUWGPrepareAlgorithm -from .preprocessor.targetprepare_algorithm import ProcessingTARGETPrepareAlgorithm +from .preprocessor.targetprepare_algorithm import ( + ProcessingTARGETPrepareAlgorithm, +) from .preprocessor.urock_prepare_algorithm import URockPrepareAlgorithm from .processor.suews_algorithm import ProcessingSuewsAlgorithm -from .processor.shadow_generator_algorithm import ProcessingShadowGeneratorAlgorithm +from .processor.shadow_generator_algorithm import ( + ProcessingShadowGeneratorAlgorithm, +) from .processor.sebe_algorithm import ProcessingSEBEAlgorithm from .processor.solweig_algorithm import ProcessingSOLWEIGAlgorithm from .processor.uwg_algorithm import ProcessingUWGProcessorAlgorithm from .processor.urock_processing_algorithm import URockAlgorithm from .processor.target_algorithm import ProcessingTargetProcessorAlgorithm -from .postprocessor.solwieganalyzer_algorithm import ProcessingSolweigAnalyzerAlgorithm -from .postprocessor.suewsanalyzer_algorithm import ProcessingSuewsAnalyzerAlgorithm +from .postprocessor.solwieganalyzer_algorithm import ( + ProcessingSolweigAnalyzerAlgorithm, +) +from .postprocessor.suewsanalyzer_algorithm import ( + ProcessingSuewsAnalyzerAlgorithm, +) from .postprocessor.treeplanter_algorithm import ProcessingTreePlanterAlgorithm from .postprocessor.uwganalyzer_algorithm import ProcessingUWGAnalyzerAlgorithm from .postprocessor.spatialtc_algorithm import ProcessingSpatialTCAlgorithm from .postprocessor.urock_analyser_algorithm import URockAnalyserAlgorithm -from .postprocessor.targetanalyzer_algorithm import ProcessingTARGETAnalyzerAlgorithm +from .postprocessor.targetanalyzer_algorithm import ( + ProcessingTARGETAnalyzerAlgorithm, +) import os.path from qgis.PyQt.QtGui import QIcon + class ProcessingUMEPProvider(QgsProcessingProvider): def __init__(self): @@ -72,9 +101,9 @@ def __init__(self): """ self.plugin_dir = os.path.dirname(__file__) QgsProcessingProvider.__init__(self) - - if not (os.path.isdir(self.plugin_dir + '/temp')): - os.mkdir(self.plugin_dir + '/temp') + + if not (os.path.isdir(self.plugin_dir + "/temp")): + os.mkdir(self.plugin_dir + "/temp") def unload(self): """ @@ -87,7 +116,7 @@ def loadAlgorithms(self): """ Loads all algorithms belonging to this provider. """ - #Preprocessor + # Preprocessor self.addAlgorithm(ProcessingSkyViewFactorAlgorithm()) self.addAlgorithm(ProcessingWallHeightAscpetAlgorithm()) self.addAlgorithm(ProcessingImageMorphParmsPointAlgorithm()) @@ -100,8 +129,8 @@ def loadAlgorithms(self): self.addAlgorithm(ProcessingUWGPrepareAlgorithm()) self.addAlgorithm(ProcessingTARGETPrepareAlgorithm()) self.addAlgorithm(URockPrepareAlgorithm()) - - #Processor + + # Processor self.addAlgorithm(ProcessingSEBEAlgorithm()) self.addAlgorithm(ProcessingShadowGeneratorAlgorithm()) self.addAlgorithm(ProcessingSOLWEIGAlgorithm()) @@ -111,7 +140,7 @@ def loadAlgorithms(self): self.addAlgorithm(ProcessingTargetProcessorAlgorithm()) self.addAlgorithm(URockAlgorithm()) - #Postprocessor + # Postprocessor self.addAlgorithm(ProcessingSolweigAnalyzerAlgorithm()) self.addAlgorithm(ProcessingSuewsAnalyzerAlgorithm()) self.addAlgorithm(ProcessingUWGAnalyzerAlgorithm()) @@ -125,7 +154,7 @@ def id(self): string should be a unique, short, character only string, eg "qgis" or "gdal". This string should not be localised. """ - return 'umep' + return "umep" def name(self): """ @@ -134,14 +163,14 @@ def name(self): This string should be short (e.g. "Lastools") and localised. """ - return 'UMEP' - + return "UMEP" + def icon(self): """ Should return a QIcon which is used for your provider inside the Processing toolbox. """ - icon = QIcon(os.path.dirname(__file__) + '/icons/icon_umep.png') + icon = QIcon(os.path.dirname(__file__) + "/icons/icon_umep.png") return icon def longName(self): @@ -151,4 +180,4 @@ def longName(self): (version 2.2.1)". This string should be localised. The default implementation returns the same string as name(). """ - return 'UMEP for Processing, Version 3.0' + return "UMEP for Processing, Version 3.0" diff --git a/processor/configsolweig.ini b/processor/configsolweig.ini index 45053bf..4889102 100644 --- a/processor/configsolweig.ini +++ b/processor/configsolweig.ini @@ -6,39 +6,41 @@ #--------------------------------------------------------------------------------------------------------- ####### INPUTS ####### # output path -output_dir=C:/temp/test/ +output_dir=C:\Run_UMEP\output\bin # working dir -working_dir=C:/temp/test/ +working_dir=C:/Run_UMEP # parameters json file -para_json_path=C:/Users/xlinfr/Documents/PythonScripts/processing_umep/processor/parametersforsolweig.json +para_json_path=C:/Run_UMEP/UMEP_processing_OHM/processor/parametersforsolweig.json # Input ground and building dsm -filepath_dsm=C:/Users/xlinfr/Documents/PythonScripts/SOLWEIG/SOLWEIGdata/DSM_KRbig.tif +filepath_dsm=C:\Users\eliot\OneDrive\Documents\Docs\GUCG\Data\Data_Usable\GA\DSM_GA.tif # Input vegetation dsm -filepath_cdsm=C:/Users/xlinfr/Documents/PythonScripts/SOLWEIG/SOLWEIGdata/CDSM_KRbig.asc +filepath_cdsm=C:\Users\eliot\OneDrive\Documents\Docs\GUCG\Data\Data_Usable\GA\CDSM_GA.tif # Input trunkzone vegetation dsm filepath_tdsm= # Input Digital Elevation Model -filepath_dem= +filepath_dem=C:\Users\eliot\OneDrive\Documents\Docs\GUCG\Data\Data_Usable\GA\DSM_GA.tif # Input Land cover dataset -filepath_lc=C:/Users/xlinfr/Documents/PythonScripts/SOLWEIG/SOLWEIGdata/landcover.tif +filepath_lc=C:\Users\eliot\OneDrive\Documents\Docs\GUCG\Data\Data_Usable\GA\LC_GA.tif # Input wall height raster -filepath_wh=C:\Users\xlinfr\Desktop\SOLWEIGdata\wallheight.tif +filepath_wh=C:\Users\eliot\OneDrive\Documents\Docs\GUCG\Data\Data_Usable\GA\wall_height_GA.tif # Input wall aspect raster -filepath_wa=C:\Users\xlinfr\Desktop\SOLWEIGdata\wallaspect.tif +filepath_wa=C:\Users\eliot\OneDrive\Documents\Docs\GUCG\Data\Data_Usable\GA\wall_aspect_GA.tif # Skyview factor files -input_svf=C:\Users\xlinfr\Desktop\SOLWEIGdata\svfs.zip +input_svf=C:\Users\eliot\OneDrive\Documents\Docs\GUCG\Data\Data_Usable\GA\svfs.zip # Input file for anisotrophic sky -input_aniso=C:\Users\xlinfr\Desktop\SOLWEIGdata\shadowmats.npz +input_aniso= #Point of Interest file for ground -poi_file= -poi_field= +poi_file=C:\Users\eliot\OneDrive\Documents\Docs\GUCG\Data\Data_Usable\GA\POI_GA.shp +poi_field=id # Input file for wall temperture scheme (Wallenberg et al. 2025) -input_wall = C:\Users\xlinfr\Desktop\SOLWEIGdata\blabla.npz +input_wall= +# Input file for surface temperature data +input_surf= #Point of Interest file for walls woi_file= woi_field= # input meteorolgical file (i.e. forcing file) -input_met=C:\Users\xlinfr\Documents\UMEP\TutorialData\Gothenburg\Goteborg_SWEREF99_1200\gbg19970606_2015a.txt +input_met=C:\Users\eliot\OneDrive\Documents\Docs\GUCG\Data\Data_Usable\GA\MetFiles\MetFile_20060726.txt ## input settings ## # option to execute solweig outside of osgeo/qgis environment @@ -56,8 +58,8 @@ lat=57.7 lon=12.0 # utc time given in meteorological forcing file utc=1 -# scale between horisontal and vertical resolution (1 = 1 meter pixel reolution, 0.5 = 2) -scale=1 +# scale between horizontal and vertical resolution (1 = 1 meter pixel resolution, 0.5 = 2) +scale=0.5 # Set to 1 if an EPW file is used as meteorological forcing data useepwfile=0 # land cover scheme activated (1) (Lindberg et al. 2016 UC) @@ -70,6 +72,8 @@ aniso=0 wallscheme=0 # If building materials is not included in lc, then this is used for all buildings (Wood, Brick or Concrete) walltype=Brick +# use OHM for ground surface temperature modeling +groundmodel=1 # output settings outputtmrt=1 outputkup=1 diff --git a/processor/parametersforsolweig.json b/processor/parametersforsolweig.json index 17a8cbe..e9eb049 100644 --- a/processor/parametersforsolweig.json +++ b/processor/parametersforsolweig.json @@ -1,221 +1,284 @@ { - "Names": { - "Value": { - "0": "Cobble_stone_2014a", - "1": "Dark_asphalt", - "2": "Roofs(buildings)", - "5": "Grass_unmanaged", - "6": "Bare_soil", - "7": "Water", - "99": "Walls", - "100": "Brick_wall", - "101": "Concrete_wall", - "102": "Wood_wall" - }, - "Comment": "Name of each respective land cover class in land cover data." - }, - "Code": { - "Value": { - "Cobble_stone_2014a": 0, - "Dark_asphalt": 1, - "Roofs(buildings)": 2, - "Grass_unmanaged": 5, - "Bare_soil": 6, - "Water": 7, - "Walls": 99, - "Brick_wall": 100, - "Concrete_wall": 101, - "Wood_wall": 102 - }, - "Comment": "Code for each land cover class name." - }, - "Albedo": { - "Effective": { - "Value": { - "Cobble_stone_2014a": 0.2, - "Dark_asphalt": 0.18, - "Roofs(buildings)": 0.18, - "Grass_unmanaged": 0.16, - "Bare_soil": 0.25, - "Water": 0.05, - "Walls": 0.2 - }, - "Comment": "Effective albedos according to Lindberg et al., 2008; 2016." - }, - "Material": { - "Value": { - "Brick_wall": 0.2, - "Concrete_wall": 0.2, - "Wood_wall": 0.2 - }, - "Comment": "Material albedos according to Wallenberg et al., 2025." - } - }, - "Emissivity": { - "Value": { - "Cobble_stone_2014a": 0.95, - "Dark_asphalt": 0.95, - "Roofs(buildings)": 0.95, - "Grass_unmanaged": 0.94, - "Bare_soil": 0.94, - "Water": 0.98, - "Walls": 0.9, - "Brick_wall": 0.9, - "Concrete_wall": 0.9, - "Wood_wall": 0.9 - }, - "Comment": "Emissivity of each land cover class." - }, - "Specific_heat": { - "Value": { - "Cobble_stone_2014a": -9999.0, - "Dark_asphalt": -9999.0, - "Roofs(buildings)": -9999.0, - "Grass_unmanaged": -9999.0, - "Bare_soil": -9999.0, - "Water": -9999.0, - "Walls": -9999.0, - "Brick_wall": 800, - "Concrete_wall": 840, - "Wood_wall": 1880 - }, - "Comment": "Specific heat capacity, in units J kg-1 K-1, used for wall surface temperatures according to Wallenberg et al. 2025." - }, - "Thermal_conductivity": { - "Value": { - "Cobble_stone_2014a": -9999.0, - "Dark_asphalt": -9999.0, - "Roofs(buildings)": -9999.0, - "Grass_unmanaged": -9999.0, - "Bare_soil": -9999.0, - "Water": -9999.0, - "Walls": -9999.0, - "Brick_wall": 0.84, - "Concrete_wall": 1.7, - "Wood_wall": 0.17 - }, - "Comment": "Thermal conductivity of each land cover class, in units W m-1 K-1, used for wall surface temperatures according to Wallenberg et al. 2025." - }, - "Density": { - "Value": { - "Cobble_stone_2014a": -9999.0, - "Dark_asphalt": -9999.0, - "Roofs(buildings)": -9999.0, - "Grass_unmanaged": -9999.0, - "Bare_soil": -9999.0, - "Water": -9999.0, - "Walls": -9999.0, - "Brick_wall": 1700, - "Concrete_wall": 2200, - "Wood_wall": 700 - }, - "Comment": "Density of the material in units kg m-3, used for wall surface temperatures according to Wallenberg et al. 2025." - }, - "Wall_thickness": { - "Value": { - "Brick_wall": 0.1, - "Concrete_wall": 0.2, - "Wood_wall": 0.03 - }, - "Comment": "Wall thickness in units meters, used to calculate characteristic time for wall surface temperatures (Wallenberg et al., 2025)." - }, - "TmaxLST": { - "Value": { - "Cobble_stone_2014a": 15.0, - "Dark_asphalt": 15.0, - "Roofs(buildings)": 15.0, - "Grass_unmanaged": 14.0, - "Bare_soil": 14.0, - "Water": 12.0, - "Walls": 15.0, - "Brick_wall": -999.0, - "Concrete_wall": -999.0, - "Wood_wall": -999.0 - }, - "Comment": "TmaxLST used for ground surface temperatures and wall surface temperatures according to Lindberg et al. 2008; 2016." - }, - "Ts_deg": { - "Value": { - "Cobble_stone_2014a": 0.37, - "Dark_asphalt": 0.58, - "Roofs(buildings)": 0.58, - "Grass_unmanaged": 0.21, - "Bare_soil": 0.33, - "Water": 0.0, - "Walls": 0.37, - "Brick_wall": -999.0, - "Concrete_wall": -999.0, - "Wood_wall": -999.0 - }, - "Comment": "Ts_deg used for ground surface temperatures and wall surface temperatures according to Lindberg et al. 2008; 2016." - }, - "Tstart": { - "Value": { - "Cobble_stone_2014a": -3.41, - "Dark_asphalt": -9.78, - "Roofs(buildings)": -9.78, - "Grass_unmanaged": -3.38, - "Bare_soil": -3.01, - "Water": 0.0, - "Walls": -3.41, - "Brick_wall": -999.0, - "Concrete_wall": -999.0, - "Wood_wall": -999.0 - }, - "Comment": "Tstart used for ground surface temperatures and wall surface temperatures according to Lindberg et al. 2008; 2016." - }, - "Tmrt_params": { - "Value": { - "absK": 0.70, - "absL": 0.95, - "posture": "Standing" - }, - "Comment": "Absorption coefficients for mean radiant temperature (Tmrt) and posture. Posture is either standing or sitting." - }, - "PET_settings": { - "Value": { - "Age": 35, - "Weight": 75.0, - "Height": 180, - "Sex": "Male", - "Activity": 80.0, - "clo": 0.90 - }, - "Comment": "Settings to calculate Physiological Equivalent Temperature (PET). Sex is either Male or Female." - }, - "Wind_Height": { - "Value": { - "magl": 10.0 - }, - "Comment": "Height of wind sensor for PET and UTCI calcualtions." - }, - "Tree_settings":{ - "Value":{ - "Transmissivity": 0.03, - "Trunk_ratio": 0.25, - "First_day_leaf": 97, - "Last_day_leaf": 300 - }, - "Comment": "Settings for trees. Shortwave trasmissivity in %. Trunkratio as a fraction of total height" - }, - "Posture": { - "Standing" : { - "Value": { - "Fside" : 0.22, - "Fup" : 0.06, - "height" : 1.1, - "Fcyl" : 0.28 - }, - "Comment": "Standing posture of human body. Used in Tmrt calulations" - }, - "Sitting" : { - "Value":{ - "Fside" : 0.166666, - "Fup" : 0.166666, - "height" : 0.75, - "Fcyl" : 0.2 - }, - "Comment": "Sitting posture of human body. Used in Tmrt calulations" - } - } -} \ No newline at end of file + "Names": { + "Value": { + "0": "Cobble_stone_2014a", + "1": "Dark_asphalt", + "2": "Roofs(buildings)", + "5": "Grass_unmanaged", + "6": "Bare_soil", + "7": "Water", + "99": "Walls", + "100": "Brick_wall", + "101": "Concrete_wall", + "102": "Wood_wall" + }, + "Comment": "Name of each respective land cover class in land cover data." + }, + "Code": { + "Value": { + "Cobble_stone_2014a": 0, + "Dark_asphalt": 1, + "Roofs(buildings)": 2, + "Grass_unmanaged": 5, + "Bare_soil": 6, + "Water": 7, + "Walls": 99, + "Brick_wall": 100, + "Concrete_wall": 101, + "Wood_wall": 102 + }, + "Comment": "Code for each land cover class name." + }, + "Albedo": { + "Effective": { + "Value": { + "Cobble_stone_2014a": 0.2, + "Dark_asphalt": 0.18, + "Roofs(buildings)": 0.18, + "Grass_unmanaged": 0.16, + "Bare_soil": 0.25, + "Water": 0.05, + "Walls": 0.2 + }, + "Comment": "Effective albedos according to Lindberg et al., 2008; 2016." + }, + "Material": { + "Value": { + "Brick_wall": 0.2, + "Concrete_wall": 0.2, + "Wood_wall": 0.2 + }, + "Comment": "Material albedos according to Wallenberg et al., 2025." + } + }, + "Emissivity": { + "Value": { + "Cobble_stone_2014a": 0.95, + "Dark_asphalt": 0.95, + "Roofs(buildings)": 0.95, + "Grass_unmanaged": 0.94, + "Bare_soil": 0.94, + "Water": 0.98, + "Walls": 0.9, + "Brick_wall": 0.9, + "Concrete_wall": 0.9, + "Wood_wall": 0.9 + }, + "Comment": "Emissivity of each land cover class." + }, + "Specific_heat": { + "Value": { + "Cobble_stone_2014a": -9999.0, + "Dark_asphalt": -9999.0, + "Roofs(buildings)": -9999.0, + "Grass_unmanaged": -9999.0, + "Bare_soil": -9999.0, + "Water": -9999.0, + "Walls": -9999.0, + "Brick_wall": 800, + "Concrete_wall": 840, + "Wood_wall": 1880 + }, + "Comment": "Specific heat capacity, in units J kg-1 K-1, used for wall surface temperatures according to Wallenberg et al. 2025." + }, + "Heat capacity": { + "Value": { + "Cobble_stone_2014a": 2110000.0, + "Dark_asphalt": 1940000.0, + "Roofs(buildings)": 1250000.0, + "Grass_unmanaged": 1350000.0, + "Bare_soil": 2000000.0, + "Water": 4180000.0, + "Walls": 1250000.0, + "Brick_wall": -9999.0, + "Concrete_wall": -9999.0, + "Wood_wall": -9999.0 + }, + "Comment": "Heat capacity of each land cover class, in J m-3 K-1, equal to density times specific heat and used in the ground surface temperature scheme" + }, + "Thermal_conductivity": { + "Value": { + "Cobble_stone_2014a": 2, + "Dark_asphalt": 0.83, + "Roofs(buildings)": 1, + "Grass_unmanaged": 1.5, + "Bare_soil": 1.5, + "Water": 1, + "Walls": 1, + "Brick_wall": 0.84, + "Concrete_wall": 1.7, + "Wood_wall": 0.17 + }, + "Comment": "Thermal conductivity of each land cover class, in units W m-1 K-1, used for wall surface temperatures according to Wallenberg et al. 2025." + }, + "Thermal_diffusivity": { + "Value": { + "Cobble_stone_2014a": 0.00000072, + "Dark_asphalt": 0.00000038, + "Roofs(buildings)": 0.00000005, + "Grass_unmanaged": 0.00000021, + "Bare_soil": 0.0000003, + "Water": 0.0000001, + "Walls": 0.00000005, + "Brick_wall": -9999.0, + "Concrete_wall": -9999.0, + "Wood_wall": -9999.0 + }, + "Comment": "Thermal diffusivity of each land cover class, in , equal to thermal conductivity per density per specific heat and used in the ground surface temperature scheme" + }, + "Density": { + "Value": { + "Cobble_stone_2014a": -9999.0, + "Dark_asphalt": -9999.0, + "Roofs(buildings)": -9999.0, + "Grass_unmanaged": -9999.0, + "Bare_soil": -9999.0, + "Water": -9999.0, + "Walls": -9999.0, + "Brick_wall": 1700, + "Concrete_wall": 2200, + "Wood_wall": 700 + }, + "Comment": "Density of the material in units kg m-3, used for wall surface temperatures according to Wallenberg et al. 2025." + }, + "Wall_thickness": { + "Value": { + "Brick_wall": 0.1, + "Concrete_wall": 0.2, + "Wood_wall": 0.03 + }, + "Comment": "Wall thickness in units meters, used to calculate characteristic time for wall surface temperatures (Wallenberg et al., 2025)." + }, + "TmaxLST": { + "Value": { + "Cobble_stone_2014a": 15.0, + "Dark_asphalt": 15.0, + "Roofs(buildings)": 15.0, + "Grass_unmanaged": 14.0, + "Bare_soil": 14.0, + "Water": 12.0, + "Walls": 15.0, + "Brick_wall": -999.0, + "Concrete_wall": -999.0, + "Wood_wall": -999.0 + }, + "Comment": "TmaxLST used for ground surface temperatures and wall surface temperatures according to Lindberg et al. 2008; 2016." + }, + "Ts_deg": { + "Value": { + "Cobble_stone_2014a": 0.37, + "Dark_asphalt": 0.58, + "Roofs(buildings)": 0.58, + "Grass_unmanaged": 0.21, + "Bare_soil": 0.33, + "Water": 0.0, + "Walls": 0.37, + "Brick_wall": -999.0, + "Concrete_wall": -999.0, + "Wood_wall": -999.0 + }, + "Comment": "Ts_deg used for ground surface temperatures and wall surface temperatures according to Lindberg et al. 2008; 2016." + }, + "Tstart": { + "Value": { + "Cobble_stone_2014a": -3.41, + "Dark_asphalt": -9.78, + "Roofs(buildings)": -9.78, + "Grass_unmanaged": -3.38, + "Bare_soil": -3.01, + "Water": 0.0, + "Walls": -3.41, + "Brick_wall": -999.0, + "Concrete_wall": -999.0, + "Wood_wall": -999.0 + }, + "Comment": "Tstart used for ground surface temperatures and wall surface temperatures according to Lindberg et al. 2008; 2016." + }, + "OHM_coefficients": { + "Values": { + "Cobble_stone_2014a": [0.61, 1.39, 0.28, -23.9], + "Dark_asphalt": [0.5, 1.39, 0.28, -31.45], + "Roofs(buildings)": [0.12, 1.39, 0.24, -4.5], + "Grass_unmanaged": [0.27, 1.39, 0.33, -21.75], + "Bare_soil": [0.3, 1.39, 0.44, -24], + "Water": [0.1, 1.39, 0, -10] + }, + "Comment": "Used in the Objective Hysteresis Model leading to the ground surface temperature the mean of coef a1 (-), its phase (rad), a2 (h-1) and a3 (W m-2) " + }, + "Tg_ini coefficients": { + "Values": { + "Cobble_stone_2014a": [-1.7, 1.5, 1.61], + "Dark_asphalt": [-2.35, 0.9, 1.6], + "Roofs(buildings)": [1, -1.3, 1.55], + "Grass_unmanaged": [-1.4, 1.1, 1.58], + "Bare_soil": [-1.8, 0.75, 1.58], + "Water": [0, 0, 0] + }, + "Comment": "Offset in K, Ratio between amplitude and offset and phase used to model the seasonal pattern of the initial ground surface temperature when compared to the air temperature" + }, + "Tm_ini coefficients": { + "Values": { + "Cobble_stone_2014a": [-6.0, 1.71, 1.43], + "Dark_asphalt": [-4.9, 1.71, -0.22], + "Roofs(buildings)": [0, 0, 0.3], + "Grass_unmanaged": [-2.7, 1.7, -0.68], + "Bare_soil": [-2.9, 1.7, -0.73], + "Water": [0, 0, 0] + }, + "Comment": "Amplitude in K, phase and offset in K used to model the seasonal pattern of the initial ground surface temperature when compared to the air temperature" + }, + "Tmrt_params": { + "Value": { + "absK": 0.7, + "absL": 0.95, + "posture": "Standing" + }, + "Comment": "Absorption coefficients for mean radiant temperature (Tmrt) and posture. Posture is either standing or sitting." + }, + "PET_settings": { + "Value": { + "Age": 35, + "Weight": 75.0, + "Height": 180, + "Sex": "Male", + "Activity": 80.0, + "clo": 0.9 + }, + "Comment": "Settings to calculate Physiological Equivalent Temperature (PET). Sex is either Male or Female." + }, + "Wind_Height": { + "Value": { + "magl": 10.0 + }, + "Comment": "Height of wind sensor for PET and UTCI calculations." + }, + "Tree_settings": { + "Value": { + "Transmissivity": 0.03, + "Trunk_ratio": 0.25, + "First_day_leaf": 97, + "Last_day_leaf": 300 + }, + "Comment": "Settings for trees. Shortwave trasmissivity in %. Trunkratio as a fraction of total height" + }, + "Posture": { + "Standing": { + "Value": { + "Fside": 0.22, + "Fup": 0.06, + "height": 1.1, + "Fcyl": 0.28 + }, + "Comment": "Standing posture of human body. Used in Tmrt calulations" + }, + "Sitting": { + "Value": { + "Fside": 0.166666, + "Fup": 0.166666, + "height": 0.75, + "Fcyl": 0.2 + }, + "Comment": "Sitting posture of human body. Used in Tmrt calulations" + } + } +} diff --git a/processor/sebe_algorithm.py b/processor/sebe_algorithm.py index e901c0c..3412d94 100644 --- a/processor/sebe_algorithm.py +++ b/processor/sebe_algorithm.py @@ -22,27 +22,29 @@ ***************************************************************************/ """ -__author__ = 'Fredrik Lindberg' -__date__ = '2020-04-02' -__copyright__ = '(C) 2020 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2020-04-02" +__copyright__ = "(C) 2020 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QDate, QTime, Qt from qgis.PyQt.QtWidgets import QDateEdit, QTimeEdit from qgis.PyQt.QtCore import QCoreApplication, QVariant -from qgis.core import (QgsProcessingAlgorithm, - QgsProcessingParameterBoolean, - QgsProcessingParameterNumber, - QgsProcessingParameterEnum, - QgsProcessingParameterFolderDestination, - QgsProcessingParameterRasterDestination, - QgsProcessingParameterFileDestination, - QgsProcessingParameterFile, - QgsProcessingException, - QgsProcessingParameterRasterLayer) +from qgis.core import ( + QgsProcessingAlgorithm, + QgsProcessingParameterBoolean, + QgsProcessingParameterNumber, + QgsProcessingParameterEnum, + QgsProcessingParameterFolderDestination, + QgsProcessingParameterRasterDestination, + QgsProcessingParameterFileDestination, + QgsProcessingParameterFile, + QgsProcessingException, + QgsProcessingParameterRasterLayer, +) from processing.gui.wrappers import WidgetWrapper import numpy as np from osgeo import gdal, osr @@ -54,7 +56,9 @@ from ..functions.SEBEfiles import SEBE_2015a_calc_forprocessing as sebe from ..functions.SEBEfiles.sunmapcreator_2015a import sunmapcreator_2015a from ..functions.SEBEfiles import WriteMetaDataSEBE -from ..util.SEBESOLWEIGCommonFiles.Solweig_v2015_metdata_noload import Solweig_2015a_metdata_noload +from ..util.SEBESOLWEIGCommonFiles.Solweig_v2015_metdata_noload import ( + Solweig_2015a_metdata_noload, +) from ..util.misc import get_ders, saveraster, createTSlist @@ -66,87 +70,178 @@ class ProcessingSEBEAlgorithm(QgsProcessingAlgorithm): # Constants used to refer to parameters and outputs. They will be # used when calling the algorithm from another algorithm, or when # calling from the QGIS console. - - INPUT_DSM = 'INPUT_DSM' - INPUT_CDSM = 'INPUT_CDSM' - INPUT_TDSM = 'INPUT_TDSM' - INPUT_HEIGHT = 'INPUT_HEIGHT' - INPUT_ASPECT = 'INPUT_ASPECT' - TRANS_VEG = 'TRANS_VEG' - INPUT_THEIGHT = 'INPUT_THEIGHT' - UTC = 'UTC' - ALBEDO = 'ALBEDO' - ONLYGLOBAL = 'ONLYGLOBAL' - INPUT_MET = 'INPUTMET' - SAVESKYIRR = 'SAVESKYIRR' - IRR_FILE = 'IRR_FILE' - OUTPUT_DIR = 'OUTPUT_DIR' - OUTPUT_ROOF = 'OUTPUT_ROOF' - + + INPUT_DSM = "INPUT_DSM" + INPUT_CDSM = "INPUT_CDSM" + INPUT_TDSM = "INPUT_TDSM" + INPUT_HEIGHT = "INPUT_HEIGHT" + INPUT_ASPECT = "INPUT_ASPECT" + TRANS_VEG = "TRANS_VEG" + INPUT_THEIGHT = "INPUT_THEIGHT" + UTC = "UTC" + ALBEDO = "ALBEDO" + ONLYGLOBAL = "ONLYGLOBAL" + INPUT_MET = "INPUTMET" + SAVESKYIRR = "SAVESKYIRR" + IRR_FILE = "IRR_FILE" + OUTPUT_DIR = "OUTPUT_DIR" + OUTPUT_ROOF = "OUTPUT_ROOF" def initAlgorithm(self, config): - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_DSM, - self.tr('Input building and ground DSM'), None, False)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_CDSM, - self.tr('Vegetation Canopy DSM'), '', True)) - self.addParameter(QgsProcessingParameterNumber(self.TRANS_VEG, - self.tr('Transmissivity of light through vegetation (%):'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(3), True, minValue=0, maxValue=100)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_TDSM, - self.tr('Vegetation Trunk zone DSM'), '', True)) - self.addParameter(QgsProcessingParameterNumber(self.INPUT_THEIGHT, - self.tr("Trunk zone height (percent of Canopy Height)"), - QgsProcessingParameterNumber.Type.Double, - QVariant(25.0), True, minValue=0.1, maxValue=99.9)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_HEIGHT, - self.tr('Wall height raster'), '', False)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_ASPECT, - self.tr('Wall aspect raster'), '', False)) - self.addParameter(QgsProcessingParameterNumber(self.ALBEDO, - self.tr('Albedo:'), QgsProcessingParameterNumber.Type.Double, - QVariant(0.15), False, minValue=0, maxValue=1)) - self.addParameter(QgsProcessingParameterFile(self.INPUT_MET, - self.tr('Input meteorological file'), extension = 'txt')) - self.addParameter(QgsProcessingParameterBoolean(self.ONLYGLOBAL, - self.tr("Estimate diffuse and direct shortwave radiation from global radiation"), defaultValue=False)) - self.sorted_utclist, _ = createTSlist() #response to #104 + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_DSM, + self.tr("Input building and ground DSM"), + None, + False, + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_CDSM, self.tr("Vegetation Canopy DSM"), "", True + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.TRANS_VEG, + self.tr("Transmissivity of light through vegetation (%):"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(3), + True, + minValue=0, + maxValue=100, + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_TDSM, self.tr("Vegetation Trunk zone DSM"), "", True + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.INPUT_THEIGHT, + self.tr("Trunk zone height (percent of Canopy Height)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(25.0), + True, + minValue=0.1, + maxValue=99.9, + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_HEIGHT, self.tr("Wall height raster"), "", False + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_ASPECT, self.tr("Wall aspect raster"), "", False + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.ALBEDO, + self.tr("Albedo:"), + QgsProcessingParameterNumber.Type.Double, + QVariant(0.15), + False, + minValue=0, + maxValue=1, + ) + ) + self.addParameter( + QgsProcessingParameterFile( + self.INPUT_MET, + self.tr("Input meteorological file"), + extension="txt", + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.ONLYGLOBAL, + self.tr( + "Estimate diffuse and direct shortwave radiation from global radiation" + ), + defaultValue=False, + ) + ) + self.sorted_utclist, _ = createTSlist() # response to #104 lista = [] for i in self.sorted_utclist: - lista.append((str(i['utc_offset_str']), str(i['utc_offset']))) - self.addParameter(QgsProcessingParameterEnum(self.UTC, - self.tr('Coordinated Universal Time (UTC) '), - options=[i[0] for i in lista], - defaultValue=14)) - self.addParameter(QgsProcessingParameterBoolean(self.SAVESKYIRR, - self.tr("Save sky irradiance distribution"), defaultValue=False)) - self.addParameter(QgsProcessingParameterFileDestination(self.IRR_FILE, - self.tr('Sky irradiance distribution'), self.tr('txt files (*.txt)'))) - self.addParameter(QgsProcessingParameterFolderDestination(self.OUTPUT_DIR, - 'Output folder')) - self.addParameter(QgsProcessingParameterRasterDestination(self.OUTPUT_ROOF, - self.tr("Roof irradiance raster (kWh)"), optional=True, - createByDefault=False)) + lista.append((str(i["utc_offset_str"]), str(i["utc_offset"]))) + self.addParameter( + QgsProcessingParameterEnum( + self.UTC, + self.tr("Coordinated Universal Time (UTC) "), + options=[i[0] for i in lista], + defaultValue=14, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.SAVESKYIRR, + self.tr("Save sky irradiance distribution"), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterFileDestination( + self.IRR_FILE, + self.tr("Sky irradiance distribution"), + self.tr("txt files (*.txt)"), + ) + ) + self.addParameter( + QgsProcessingParameterFolderDestination( + self.OUTPUT_DIR, "Output folder" + ) + ) + self.addParameter( + QgsProcessingParameterRasterDestination( + self.OUTPUT_ROOF, + self.tr("Roof irradiance raster (kWh)"), + optional=True, + createByDefault=False, + ) + ) def processAlgorithm(self, parameters, context, feedback): # InputParameters - outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) - dsmlayer = self.parameterAsRasterLayer(parameters, self.INPUT_DSM, context) - transVeg = self.parameterAsDouble(parameters, self.TRANS_VEG, context) - vegdsm = self.parameterAsRasterLayer(parameters, self.INPUT_CDSM, context) - vegdsm2 = self.parameterAsRasterLayer(parameters, self.INPUT_TDSM, context) - whlayer = self.parameterAsRasterLayer(parameters, self.INPUT_HEIGHT, context) - walayer = self.parameterAsRasterLayer(parameters, self.INPUT_ASPECT, context) - trunkr = self.parameterAsDouble(parameters, self.INPUT_THEIGHT, context) + outputDir = self.parameterAsString( + parameters, self.OUTPUT_DIR, context + ) + dsmlayer = self.parameterAsRasterLayer( + parameters, self.INPUT_DSM, context + ) + transVeg = self.parameterAsDouble(parameters, self.TRANS_VEG, context) + vegdsm = self.parameterAsRasterLayer( + parameters, self.INPUT_CDSM, context + ) + vegdsm2 = self.parameterAsRasterLayer( + parameters, self.INPUT_TDSM, context + ) + whlayer = self.parameterAsRasterLayer( + parameters, self.INPUT_HEIGHT, context + ) + walayer = self.parameterAsRasterLayer( + parameters, self.INPUT_ASPECT, context + ) + trunkr = self.parameterAsDouble( + parameters, self.INPUT_THEIGHT, context + ) onlyglobal = self.parameterAsBool(parameters, self.ONLYGLOBAL, context) utcpos = self.parameterAsString(parameters, self.UTC, context) albedo = self.parameterAsDouble(parameters, self.ALBEDO, context) inputMet = self.parameterAsString(parameters, self.INPUT_MET, context) saveskyirr = self.parameterAsBool(parameters, self.SAVESKYIRR, context) - irrFile = self.parameterAsFileOutput(parameters, self.IRR_FILE, context) - outputRoof = self.parameterAsOutputLayer(parameters, self.OUTPUT_ROOF, context) - - if parameters['OUTPUT_DIR'] == 'TEMPORARY_OUTPUT': + irrFile = self.parameterAsFileOutput( + parameters, self.IRR_FILE, context + ) + outputRoof = self.parameterAsOutputLayer( + parameters, self.OUTPUT_ROOF, context + ) + + if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): os.mkdir(outputDir) @@ -159,13 +254,13 @@ def processAlgorithm(self, parameters, context, feedback): # response to issue #85 nd = self.gdal_dsm.GetRasterBand(1).GetNoDataValue() - self.dsm[self.dsm == nd] = 0. + self.dsm[self.dsm == nd] = 0.0 if self.dsm.min() < 0: self.dsm = self.dsm + np.abs(self.dsm.min()) # response to issue #104 self.sorted_utclist - utc = self.sorted_utclist[int(utcpos)]['utc_offset'] + utc = self.sorted_utclist[int(utcpos)]["utc_offset"] # Get latlon from grid coordinate system old_cs = osr.SpatialReference() @@ -192,19 +287,23 @@ def processAlgorithm(self, parameters, context, feedback): height = self.gdal_dsm.RasterYSize geotransform = self.gdal_dsm.GetGeoTransform() minx = geotransform[0] - miny = geotransform[3] + width*geotransform[4] + height*geotransform[5] + miny = ( + geotransform[3] + + width * geotransform[4] + + height * geotransform[5] + ) lonlat = transform.TransformPoint(minx, miny) gdalver = float(gdal.__version__[0]) - if gdalver == 3.: - lon = lonlat[1] #changed to gdal 3 - lat = lonlat[0] #changed to gdal 3 + if gdalver == 3.0: + lon = lonlat[1] # changed to gdal 3 + lat = lonlat[0] # changed to gdal 3 else: - lon = lonlat[0] #changed to gdal 2 - lat = lonlat[1] #changed to gdal 2 + lon = lonlat[0] # changed to gdal 2 + lat = lonlat[1] # changed to gdal 2 self.scale = 1 / geotransform[1] - feedback.setProgressText('Longitude derived from DSM: ' + str(lon)) - feedback.setProgressText('Latitude derived from DSM: ' + str(lat)) + feedback.setProgressText("Longitude derived from DSM: " + str(lon)) + feedback.setProgressText("Latitude derived from DSM: " + str(lat)) trunkfile = 0 trunkratio = 0 @@ -212,8 +311,8 @@ def processAlgorithm(self, parameters, context, feedback): if vegdsm: usevegdem = 1 - feedback.setProgressText('Vegetation scheme activated') - # vegdsm = self.parameterAsRasterLayer(parameters, self.INPUT_CDSM, context) + feedback.setProgressText("Vegetation scheme activated") + # vegdsm = self.parameterAsRasterLayer(parameters, self.INPUT_CDSM, context) # if vegdsm is None: # raise QgsProcessingException("Error: No valid vegetation DSM selected") @@ -228,10 +327,12 @@ def processAlgorithm(self, parameters, context, feedback): vegsizey = vegdsm.shape[1] if not (vegsizex == sizex) & (vegsizey == sizey): - raise QgsProcessingException("Error in Vegetation Canopy DSM: All rasters must be of same extent and resolution") + raise QgsProcessingException( + "Error in Vegetation Canopy DSM: All rasters must be of same extent and resolution" + ) if vegdsm2: - # vegdsm2 = self.parameterAsRasterLayer(parameters, self.INPUT_TDSM, context) + # vegdsm2 = self.parameterAsRasterLayer(parameters, self.INPUT_TDSM, context) # if vegdsm2 is None: # raise QgsProcessingException("Error: No valid Trunk zone DSM selected") @@ -252,7 +353,9 @@ def processAlgorithm(self, parameters, context, feedback): vegsizey = vegdsm2.shape[1] if not (vegsizex == sizex) & (vegsizey == sizey): # & - raise QgsProcessingException("Error in Trunk Zone DSM: All rasters must be of same extent and resolution") + raise QgsProcessingException( + "Error in Trunk Zone DSM: All rasters must be of same extent and resolution" + ) else: vegdsm = 0 vegdsm2 = 0 @@ -270,9 +373,13 @@ def processAlgorithm(self, parameters, context, feedback): vhsizex = wheight.shape[0] vhsizey = wheight.shape[1] if not (vhsizex == sizex) & (vhsizey == sizey): - raise QgsProcessingException("Error in Wall height raster: All rasters must be of same extent and resolution") + raise QgsProcessingException( + "Error in Wall height raster: All rasters must be of same extent and resolution" + ) - wallmaxheight = self.gdal_wh.GetRasterBand(1).GetStatistics(True,True)[1] + wallmaxheight = self.gdal_wh.GetRasterBand(1).GetStatistics( + True, True + )[1] # wall aspectlayer # if walayer is None: @@ -284,45 +391,70 @@ def processAlgorithm(self, parameters, context, feedback): vasizex = waspect.shape[0] vasizey = waspect.shape[1] if not (vasizex == sizex) & (vasizey == sizey): - raise QgsProcessingException("Error in Wall aspect raster: All rasters must be of same extent and resolution") + raise QgsProcessingException( + "Error in Wall aspect raster: All rasters must be of same extent and resolution" + ) voxelheight = geotransform[1] # float # Metdata headernum = 1 - delim = ' ' + delim = " " try: - self.metdata = np.loadtxt(inputMet, skiprows=headernum, delimiter=delim) + self.metdata = np.loadtxt( + inputMet, skiprows=headernum, delimiter=delim + ) except: - QgsProcessingException("Error: Make sure format of meteorological file is correct. You can" - "prepare your data by using 'Prepare Existing Data' in " - "the Pre-processor") - - testwhere = np.where((self.metdata[:, 14] < 0.0) | (self.metdata[:, 14] > 1300.0)) + QgsProcessingException( + "Error: Make sure format of meteorological file is correct. You can" + "prepare your data by using 'Prepare Existing Data' in " + "the Pre-processor" + ) + + testwhere = np.where( + (self.metdata[:, 14] < 0.0) | (self.metdata[:, 14] > 1300.0) + ) if testwhere[0].__len__() > 0: - QgsProcessingException("Error: Kdown - beyond what is expected at line: " + str(testwhere[0] + 1)) + QgsProcessingException( + "Error: Kdown - beyond what is expected at line: " + + str(testwhere[0] + 1) + ) if self.metdata.shape[1] == 24: feedback.setProgressText("Meteorological data succefully loaded") else: - QgsProcessingException("Error: Wrong number of columns in meteorological data. You can " - "prepare your data by using 'Prepare Existing Data' in " - "the Pre-processor") + QgsProcessingException( + "Error: Wrong number of columns in meteorological data. You can " + "prepare your data by using 'Prepare Existing Data' in " + "the Pre-processor" + ) alt = np.median(self.dsm) if alt < 0: alt = 3 - feedback.setProgressText("Calculating sun positions for each time step") - location = {'longitude': lon, 'latitude': lat, 'altitude': alt} - YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax = \ + feedback.setProgressText( + "Calculating sun positions for each time step" + ) + location = {"longitude": lon, "latitude": lat, "altitude": alt} + YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax = ( Solweig_2015a_metdata_noload(self.metdata, location, utc) + ) feedback.setProgressText("Distributing irradiance on sky vault") - output = {'energymonth': 0, 'energyyear': 1, 'suitmap': 0} - radmatI, radmatD, radmatR = sunmapcreator_2015a(self.metdata, altitude, azimuth, - onlyglobal, output, jday, albedo, location, zen) + output = {"energymonth": 0, "energyyear": 1, "suitmap": 0} + radmatI, radmatD, radmatR = sunmapcreator_2015a( + self.metdata, + altitude, + azimuth, + onlyglobal, + output, + jday, + albedo, + location, + zen, + ) if saveskyirr: metout = np.zeros((145, 4)) @@ -330,47 +462,100 @@ def processAlgorithm(self, parameters, context, feedback): metout[:, 1] = radmatI[:, 1] metout[:, 2] = radmatI[:, 2] metout[:, 3] = radmatD[:, 2] - header = '%altitude azimuth radI radD' - numformat = '%6.2f %6.2f %6.2f %6.2f' - np.savetxt(irrFile, metout, fmt=numformat, header=header, comments='') + header = "%altitude azimuth radI radD" + numformat = "%6.2f %6.2f %6.2f %6.2f" + np.savetxt( + irrFile, metout, fmt=numformat, header=header, comments="" + ) building_slope, building_aspect = get_ders(self.dsm, self.scale) - WriteMetaDataSEBE.writeRunInfo(outputDir, filepath_dsm, self.gdal_dsm, usevegdem, - filePath_cdsm, trunkfile, filePath_tdsm, lat, lon, utc, - inputMet, albedo, onlyglobal, trunkratio, psi, sizex, sizey) + WriteMetaDataSEBE.writeRunInfo( + outputDir, + filepath_dsm, + self.gdal_dsm, + usevegdem, + filePath_cdsm, + trunkfile, + filePath_tdsm, + lat, + lon, + utc, + inputMet, + albedo, + onlyglobal, + trunkratio, + psi, + sizex, + sizey, + ) # Main function feedback.setProgressText("Executing main model") - seberesult = sebe.SEBE_2015a_calc(self.dsm, self.scale, building_slope, - building_aspect, voxelheight, sizey, sizex, vegdsm, vegdsm2, wheight, - waspect, albedo, psi, radmatI, radmatD, radmatR, usevegdem, feedback, wallmaxheight) + seberesult = sebe.SEBE_2015a_calc( + self.dsm, + self.scale, + building_slope, + building_aspect, + voxelheight, + sizey, + sizex, + vegdsm, + vegdsm2, + wheight, + waspect, + albedo, + psi, + radmatI, + radmatD, + radmatR, + usevegdem, + feedback, + wallmaxheight, + ) Energyyearroof = seberesult["Energyyearroof"] Energyyearwall = seberesult["Energyyearwall"] vegdata = seberesult["vegdata"] - feedback.setProgressText("SEBE: Model calculation finished. Saving to disk") + feedback.setProgressText( + "SEBE: Model calculation finished. Saving to disk" + ) - if outputRoof: saveraster(self.gdal_dsm, outputRoof, Energyyearroof) - saveraster(self.gdal_dsm, outputDir + '/dsm.tif', self.dsm) - filenameroof = outputDir + '/Energyyearroof.tif' + saveraster(self.gdal_dsm, outputDir + "/dsm.tif", self.dsm) + filenameroof = outputDir + "/Energyyearroof.tif" saveraster(self.gdal_dsm, filenameroof, Energyyearroof) - filenamewall = outputDir + '/Energyyearwall.txt' - header = '%row col irradiance' - numformat = '%4d %4d ' + '%6.2f ' * (Energyyearwall.shape[1] - 2) - np.savetxt(filenamewall, Energyyearwall, fmt=numformat, header=header, comments='') + filenamewall = outputDir + "/Energyyearwall.txt" + header = "%row col irradiance" + numformat = "%4d %4d " + "%6.2f " * (Energyyearwall.shape[1] - 2) + np.savetxt( + filenamewall, + Energyyearwall, + fmt=numformat, + header=header, + comments="", + ) if usevegdem == 1: - filenamewall = outputDir + '/Vegetationdata.txt' - header = '%row col height' - numformat = '%4d %4d %6.2f' - np.savetxt(filenamewall, vegdata, fmt=numformat, header=header, comments='') + filenamewall = outputDir + "/Vegetationdata.txt" + header = "%row col height" + numformat = "%4d %4d %6.2f" + np.savetxt( + filenamewall, + vegdata, + fmt=numformat, + header=header, + comments="", + ) + + return { + self.OUTPUT_DIR: outputDir, + self.IRR_FILE: irrFile, + self.OUTPUT_ROOF: outputRoof, + } - return {self.OUTPUT_DIR: outputDir, self.IRR_FILE: irrFile, self.OUTPUT_ROOF: outputRoof} - def name(self): """ Returns the algorithm name, used for identifying the algorithm. This @@ -379,7 +564,7 @@ def name(self): lowercase alphanumeric characters only and no spaces or other formatting characters. """ - return 'Solar Radiation: Solar Energy of Builing Envelopes (SEBE)' + return "Solar Radiation: Solar Energy of Builing Envelopes (SEBE)" def displayName(self): """ @@ -403,21 +588,26 @@ def groupId(self): contain lowercase alphanumeric characters only and no spaces or other formatting characters. """ - return 'Processor' + return "Processor" def shortHelpString(self): - return self.tr('The SEBE plugin (Solar Energy on Building Envelopes) can be used to calculate pixel wise potential solar energy using ground and building digital surface models (DSM). SEBE is also able to estimate irradiance on building walls. Optionally, vegetation DSMs could also be used.
' - '--------------\n' - 'Full manual available via the Help-button.') + return self.tr( + "The SEBE plugin (Solar Energy on Building Envelopes) can be used to calculate pixel wise potential solar energy using ground and building digital surface models (DSM). SEBE is also able to estimate irradiance on building walls. Optionally, vegetation DSMs could also be used.
" + "--------------\n" + "Full manual available via the Help-button." + ) + def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/processor/Solar%20Radiation%20Solar%20Energy%20on%20Building%20Envelopes%20(SEBE).html" return url def tr(self, string): - return QCoreApplication.translate('Processing', string) + return QCoreApplication.translate("Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/sebeicon.png") return icon diff --git a/processor/shadow_generator_algorithm.py b/processor/shadow_generator_algorithm.py index b80c40c..8f1d1bb 100644 --- a/processor/shadow_generator_algorithm.py +++ b/processor/shadow_generator_algorithm.py @@ -22,24 +22,26 @@ ***************************************************************************/ """ -__author__ = 'Fredrik Lindberg' -__date__ = '2020-04-02' -__copyright__ = '(C) 2020 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2020-04-02" +__copyright__ = "(C) 2020 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QDate, QTime, Qt, QVariant -from qgis.core import (QgsProcessingAlgorithm, - QgsProcessingParameterEnum, - QgsProcessingParameterBoolean, - QgsProcessingParameterNumber, - QgsProcessingParameterFolderDestination, - QgsProcessingParameterRasterDestination, - QgsProcessingException, - QgsProcessingParameterDateTime, - QgsProcessingParameterRasterLayer) +from qgis.core import ( + QgsProcessingAlgorithm, + QgsProcessingParameterEnum, + QgsProcessingParameterBoolean, + QgsProcessingParameterNumber, + QgsProcessingParameterFolderDestination, + QgsProcessingParameterRasterDestination, + QgsProcessingException, + QgsProcessingParameterDateTime, + QgsProcessingParameterRasterLayer, +) from processing.gui.wrappers import WidgetWrapper from qgis.PyQt.QtWidgets import QDateEdit, QTimeEdit @@ -63,142 +65,198 @@ class ProcessingShadowGeneratorAlgorithm(QgsProcessingAlgorithm): # Constants used to refer to parameters and outputs. They will be # used when calling the algorithm from another algorithm, or when # calling from the QGIS console. - - INPUT_DSM = 'INPUT_DSM' - INPUT_CDSM = 'INPUT_CDSM' - INPUT_TDSM = 'INPUT_TDSM' - INPUT_HEIGHT = 'INPUT_HEIGHT' - INPUT_ASPECT = 'INPUT_ASPECT' - TRANS_VEG = 'TRANS_VEG' - INPUT_THEIGHT = 'INPUT_THEIGHT' - ONE_SHADOW = 'ONE_SHADOW' - ITERTIME = 'ITERTIME' - DATEINI = 'DATEINI' - TIMEINI = 'TIMEINI' - UTC = 'UTC' - DST = 'DST' - OUTPUT_DIR = 'OUTPUT_DIR' - OUTPUT_FILE = 'OUTPUT_FILE' + + INPUT_DSM = "INPUT_DSM" + INPUT_CDSM = "INPUT_CDSM" + INPUT_TDSM = "INPUT_TDSM" + INPUT_HEIGHT = "INPUT_HEIGHT" + INPUT_ASPECT = "INPUT_ASPECT" + TRANS_VEG = "TRANS_VEG" + INPUT_THEIGHT = "INPUT_THEIGHT" + ONE_SHADOW = "ONE_SHADOW" + ITERTIME = "ITERTIME" + DATEINI = "DATEINI" + TIMEINI = "TIMEINI" + UTC = "UTC" + DST = "DST" + OUTPUT_DIR = "OUTPUT_DIR" + OUTPUT_FILE = "OUTPUT_FILE" def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterRasterLayer( self.INPUT_DSM, - self.tr('Input building and ground DSM'), + self.tr("Input building and ground DSM"), None, - False)) + False, + ) + ) self.addParameter( QgsProcessingParameterRasterLayer( - self.INPUT_CDSM, - self.tr('Vegetation Canopy DSM'), - '', - True)) + self.INPUT_CDSM, self.tr("Vegetation Canopy DSM"), "", True + ) + ) self.addParameter( QgsProcessingParameterNumber( self.TRANS_VEG, - self.tr('Transmissivity of light through vegetation (%):'), + self.tr("Transmissivity of light through vegetation (%):"), QgsProcessingParameterNumber.Type.Integer, - QVariant(3), - True, - minValue=0, - maxValue=100)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_TDSM, - self.tr('Vegetation Trunk zone DSM'), '', True)) + QVariant(3), + True, + minValue=0, + maxValue=100, + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_TDSM, self.tr("Vegetation Trunk zone DSM"), "", True + ) + ) self.addParameter( QgsProcessingParameterNumber( self.INPUT_THEIGHT, self.tr("Trunk zone height (percent of Canopy Height)"), QgsProcessingParameterNumber.Type.Double, QVariant(25.0), - True, - minValue=0.1, - maxValue=99.9)) + True, + minValue=0.1, + maxValue=99.9, + ) + ) self.addParameter( QgsProcessingParameterRasterLayer( self.INPUT_HEIGHT, - self.tr('Wall height raster (required if facade shadow should be claculated)'), - '', - True)) + self.tr( + "Wall height raster (required if facade shadow should be claculated)" + ), + "", + True, + ) + ) self.addParameter( QgsProcessingParameterRasterLayer( self.INPUT_ASPECT, - self.tr('Wall aspect raster (required if facade shadow should be claculated)'), - '', - True)) - self.sorted_utclist, _ = createTSlist() #Inculde all UTC times + self.tr( + "Wall aspect raster (required if facade shadow should be claculated)" + ), + "", + True, + ) + ) + self.sorted_utclist, _ = createTSlist() # Inculde all UTC times lista = [] for i in self.sorted_utclist: - lista.append((str(i['utc_offset_str']), str(i['utc_offset']))) - self.addParameter(QgsProcessingParameterEnum(self.UTC, - self.tr('Coordinated Universal Time (UTC) '), - options=[i[0] for i in lista], - defaultValue=15)) - + lista.append((str(i["utc_offset_str"]), str(i["utc_offset"]))) + self.addParameter( + QgsProcessingParameterEnum( + self.UTC, + self.tr("Coordinated Universal Time (UTC) "), + options=[i[0] for i in lista], + defaultValue=15, + ) + ) + # self.addParameter( # QgsProcessingParameterNumber( # self.UTC, # self.tr('Coordinated Universal Time (UTC) '), # QgsProcessingParameterNumber.Integer, # QVariant(0), - # True, - # minValue=-12, - # maxValue=12)) + # True, + # minValue=-12, + # maxValue=12)) self.addParameter( QgsProcessingParameterBoolean( self.DST, - self.tr("Add Daylight savings time"), - defaultValue=False)) - self.addParameter(QgsProcessingParameterDateTime(self.DATEINI, - self.tr('Date'), - QgsProcessingParameterDateTime.Type.Date)) + self.tr("Add Daylight savings time"), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterDateTime( + self.DATEINI, + self.tr("Date"), + QgsProcessingParameterDateTime.Type.Date, + ) + ) self.addParameter( QgsProcessingParameterNumber( self.ITERTIME, - self.tr('Time interval between casting of each shadow (minutes)'), + self.tr( + "Time interval between casting of each shadow (minutes)" + ), QgsProcessingParameterNumber.Type.Integer, QVariant(30), - True, + True, minValue=0.1, - maxValue=360)) + maxValue=360, + ) + ) self.addParameter( QgsProcessingParameterBoolean( self.ONE_SHADOW, - self.tr("Cast only one shadow"), - defaultValue=False)) - self.addParameter(QgsProcessingParameterDateTime(self.TIMEINI, - self.tr('Time for single shadow'), - QgsProcessingParameterDateTime.Type.Time)) + self.tr("Cast only one shadow"), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterDateTime( + self.TIMEINI, + self.tr("Time for single shadow"), + QgsProcessingParameterDateTime.Type.Time, + ) + ) self.addParameter( QgsProcessingParameterFolderDestination( - self.OUTPUT_DIR, - 'Output folder')) + self.OUTPUT_DIR, "Output folder" + ) + ) self.addParameter( QgsProcessingParameterRasterDestination( self.OUTPUT_FILE, - self.tr("Aggregated (or single) shadow raster"), + self.tr("Aggregated (or single) shadow raster"), optional=True, - createByDefault=False)) - + createByDefault=False, + ) + ) def processAlgorithm(self, parameters, context, feedback): # InputParameters - outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) - outputFile = self.parameterAsOutputLayer(parameters, self.OUTPUT_FILE, context) - dsmlayer = self.parameterAsRasterLayer(parameters, self.INPUT_DSM, context) + outputDir = self.parameterAsString( + parameters, self.OUTPUT_DIR, context + ) + outputFile = self.parameterAsOutputLayer( + parameters, self.OUTPUT_FILE, context + ) + dsmlayer = self.parameterAsRasterLayer( + parameters, self.INPUT_DSM, context + ) transVeg = self.parameterAsDouble(parameters, self.TRANS_VEG, context) - vegdsm = self.parameterAsRasterLayer(parameters, self.INPUT_CDSM, context) - vegdsm2 = self.parameterAsRasterLayer(parameters, self.INPUT_TDSM, context) - whlayer = self.parameterAsRasterLayer(parameters, self.INPUT_HEIGHT, context) - walayer = self.parameterAsRasterLayer(parameters, self.INPUT_ASPECT, context) - trunkr = self.parameterAsDouble(parameters, self.INPUT_THEIGHT, context) - utcpos = self.parameterAsString(parameters, self.UTC, context) + vegdsm = self.parameterAsRasterLayer( + parameters, self.INPUT_CDSM, context + ) + vegdsm2 = self.parameterAsRasterLayer( + parameters, self.INPUT_TDSM, context + ) + whlayer = self.parameterAsRasterLayer( + parameters, self.INPUT_HEIGHT, context + ) + walayer = self.parameterAsRasterLayer( + parameters, self.INPUT_ASPECT, context + ) + trunkr = self.parameterAsDouble( + parameters, self.INPUT_THEIGHT, context + ) + utcpos = self.parameterAsString(parameters, self.UTC, context) dst = self.parameterAsBool(parameters, self.DST, context) myDate = self.parameterAsString(parameters, self.DATEINI, context) - oneShadow = self.parameterAsDouble(parameters, self.ONE_SHADOW, context) + oneShadow = self.parameterAsDouble( + parameters, self.ONE_SHADOW, context + ) myTime = self.parameterAsString(parameters, self.TIMEINI, context) iterShadow = self.parameterAsDouble(parameters, self.ITERTIME, context) - if parameters['OUTPUT_DIR'] == 'TEMPORARY_OUTPUT': + if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not os.path.isdir(outputDir): os.mkdir(outputDir) @@ -214,12 +272,12 @@ def processAlgorithm(self, parameters, context, feedback): # response to issue #85 nd = gdal_dsm.GetRasterBand(1).GetNoDataValue() - dsm[dsm == nd] = 0. + dsm[dsm == nd] = 0.0 if dsm.min() < 0: dsm = dsm + np.abs(dsm.min()) self.sorted_utclist - utc = self.sorted_utclist[int(utcpos)]['utc_offset'] + utc = self.sorted_utclist[int(utcpos)]["utc_offset"] sizex = dsm.shape[0] sizey = dsm.shape[1] @@ -249,27 +307,27 @@ def processAlgorithm(self, parameters, context, feedback): height = gdal_dsm.RasterYSize gt = gdal_dsm.GetGeoTransform() minx = gt[0] - miny = gt[3] + width*gt[4] + height*gt[5] + miny = gt[3] + width * gt[4] + height * gt[5] lonlat = transform.TransformPoint(minx, miny) geotransform = gdal_dsm.GetGeoTransform() scale = 1 / geotransform[1] gdalver = float(gdal.__version__[0]) - if gdalver >= 3.: - lon = lonlat[1] #changed to gdal 3 - lat = lonlat[0] #changed to gdal 3 + if gdalver >= 3.0: + lon = lonlat[1] # changed to gdal 3 + lat = lonlat[0] # changed to gdal 3 else: - lon = lonlat[0] #changed to gdal 2 - lat = lonlat[1] #changed to gdal 2 + lon = lonlat[0] # changed to gdal 2 + lat = lonlat[1] # changed to gdal 2 - feedback.setProgressText('Longitude derived from DSM: ' + str(lon)) - feedback.setProgressText('Latitude derived from DSM: ' + str(lat)) + feedback.setProgressText("Longitude derived from DSM: " + str(lon)) + feedback.setProgressText("Latitude derived from DSM: " + str(lat)) trans = transVeg / 100.0 if vegdsm: usevegdem = 1 - feedback.setProgressText('Vegetation scheme activated') + feedback.setProgressText("Vegetation scheme activated") # load raster gdal.AllRegister() @@ -282,7 +340,9 @@ def processAlgorithm(self, parameters, context, feedback): vegsizey = vegdsm.shape[1] if not (vegsizex == sizex) & (vegsizey == sizey): - raise QgsProcessingException("Error in Vegetation Canopy DSM: All rasters must be of same extent and resolution") + raise QgsProcessingException( + "Error in Vegetation Canopy DSM: All rasters must be of same extent and resolution" + ) if vegdsm2: gdal.AllRegister() @@ -298,14 +358,16 @@ def processAlgorithm(self, parameters, context, feedback): vegsizey = vegdsm2.shape[1] if not (vegsizex == sizex) & (vegsizey == sizey): # & - raise QgsProcessingException("Error in Trunk Zone DSM: All rasters must be of same extent and resolution") + raise QgsProcessingException( + "Error in Trunk Zone DSM: All rasters must be of same extent and resolution" + ) else: vegdsm = 0 vegdsm2 = 0 usevegdem = 0 if whlayer and walayer: - feedback.setProgressText('Facade shadow scheme activated') + feedback.setProgressText("Facade shadow scheme activated") wallsh = 1 provider = whlayer.dataProvider() filepath_wh = str(provider.dataSourceUri()) @@ -314,7 +376,9 @@ def processAlgorithm(self, parameters, context, feedback): vhsizex = wheight.shape[0] vhsizey = wheight.shape[1] if not (vhsizex == sizex) & (vhsizey == sizey): # & - raise QgsProcessingException("Error in Wall height raster: All rasters must be of same extent and resolution") + raise QgsProcessingException( + "Error in Wall height raster: All rasters must be of same extent and resolution" + ) provider = walayer.dataProvider() filepath_wa = str(provider.dataSourceUri()) @@ -323,23 +387,25 @@ def processAlgorithm(self, parameters, context, feedback): vasizex = waspect.shape[0] vasizey = waspect.shape[1] if not (vasizex == sizex) & (vasizey == sizey): - raise QgsProcessingException("Error in Wall aspect raster: All rasters must be of same extent and resolution") + raise QgsProcessingException( + "Error in Wall aspect raster: All rasters must be of same extent and resolution" + ) else: wallsh = 0 wheight = 0 waspect = 0 - if outputDir is 'None': + if outputDir is "None": raise QgsProcessingException("Error: No selected folder") else: - startDate = datetime.datetime.strptime(myDate, '%Y-%m-%d') + startDate = datetime.datetime.strptime(myDate, "%Y-%m-%d") year = startDate.year month = startDate.month day = startDate.day # UTC = utc #self.dlg.spinBoxUTC.value() - if oneShadow: #self.dlg.shadowCheckBox.isChecked(): + if oneShadow: # self.dlg.shadowCheckBox.isChecked(): onetime = 1 - onetimetime = datetime.datetime.strptime(myTime, '%H:%M:%S.%f') + onetimetime = datetime.datetime.strptime(myTime, "%H:%M:%S.%f") hour = onetimetime.hour minu = onetimetime.minute sec = onetimetime.second @@ -351,20 +417,42 @@ def processAlgorithm(self, parameters, context, feedback): tv = [year, month, day, hour, minu, sec] - timeInterval = iterShadow # self.dlg.intervalTimeEdit.time() - shadowresult = dsh.dailyshading(dsm, vegdsm, vegdsm2, scale, lon, lat, sizex, sizey, tv, utc, usevegdem, - timeInterval, onetime, feedback, outputDir, gdal_dsm, trans, - dst, wallsh, wheight, waspect) - + timeInterval = iterShadow # self.dlg.intervalTimeEdit.time() + shadowresult = dsh.dailyshading( + dsm, + vegdsm, + vegdsm2, + scale, + lon, + lat, + sizex, + sizey, + tv, + utc, + usevegdem, + timeInterval, + onetime, + feedback, + outputDir, + gdal_dsm, + trans, + dst, + wallsh, + wheight, + waspect, + ) + shfinal = shadowresult["shfinal"] if outputFile: dsh.saveraster(gdal_dsm, outputFile, shfinal) - feedback.setProgressText("ShadowGenerator: Shadow grid(s) successfully generated") + feedback.setProgressText( + "ShadowGenerator: Shadow grid(s) successfully generated" + ) return {self.OUTPUT_DIR: outputDir, self.OUTPUT_FILE: outputFile} - + def name(self): """ Returns the algorithm name, used for identifying the algorithm. This @@ -373,7 +461,7 @@ def name(self): lowercase alphanumeric characters only and no spaces or other formatting characters. """ - return 'Solar Radiation: Shadow Generator' + return "Solar Radiation: Shadow Generator" def displayName(self): """ @@ -397,31 +485,34 @@ def groupId(self): contain lowercase alphanumeric characters only and no spaces or other formatting characters. """ - return 'Processor' + return "Processor" def shortHelpString(self): - return self.tr('The Shadow generator plugin can be used to generate pixel wise shadow analysis using ground and ' - 'building digital surface models (DSM). Optionally, vegetation DSMs could also be used. ' - 'The methodology that is used to generate shadows originates from Ratti and Richens (1990) ' - 'and is further developed and described in Lindberg and Grimmond (2011).
' - '\n' - '------------------
' - 'Lindberg, F., Grimmond, C.S.B., 2011a. The influence of vegetation and building morphology on shadow patterns and mean radiant temperatures in urban areas: model development and evaluation. Theoret. Appl. Climatol. 105, 311–323
' - '\n' - 'Ratti CF, Richens P (1999) Urban texture analysis with image processing techniques. In: Proceedings of the CAADFutures99, Atalanta, GA' - '\n' - 'Full manual available via the Help-button.') - + return self.tr( + "The Shadow generator plugin can be used to generate pixel wise shadow analysis using ground and " + "building digital surface models (DSM). Optionally, vegetation DSMs could also be used. " + "The methodology that is used to generate shadows originates from Ratti and Richens (1990) " + "and is further developed and described in Lindberg and Grimmond (2011).
" + "\n" + "------------------
" + "Lindberg, F., Grimmond, C.S.B., 2011a. The influence of vegetation and building morphology on shadow patterns and mean radiant temperatures in urban areas: model development and evaluation. Theoret. Appl. Climatol. 105, 311–323
" + "\n" + "Ratti CF, Richens P (1999) Urban texture analysis with image processing techniques. In: Proceedings of the CAADFutures99, Atalanta, GA" + "\n" + "Full manual available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/processor/Solar%20Radiation%20Daily%20Shadow%20Pattern.html" return url def tr(self, string): - return QCoreApplication.translate('Processing', string) + return QCoreApplication.translate("Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/ShadowIcon.png") return icon @@ -433,8 +524,9 @@ class DateWidget(WidgetWrapper): """ QDateEdit widget with calendar pop up """ + def createWidget(self): - self._combo = QDateEdit() #QDateTimeEdit() + self._combo = QDateEdit() # QDateTimeEdit() self._combo.setCalendarPopup(True) today = QDate.currentDate() @@ -446,12 +538,14 @@ def value(self): date_chosen = self._combo.dateTime() return date_chosen.toString(Qt.DateFormat.ISODate) + class TimeWidget(WidgetWrapper): """ QTimeEdit widget with calendar pop up """ + def createWidget(self): - self._combo = QTimeEdit() #QDateTimeEdit() + self._combo = QTimeEdit() # QDateTimeEdit() self._combo.setCalendarPopup(True) today = QTime.currentTime() diff --git a/processor/solweig_algorithm.py b/processor/solweig_algorithm.py index 4b63ec1..f1ece4f 100644 --- a/processor/solweig_algorithm.py +++ b/processor/solweig_algorithm.py @@ -22,27 +22,29 @@ ***************************************************************************/ """ -__author__ = 'Fredrik Lindberg' -__date__ = '2020-04-02' -__copyright__ = '(C) 2020 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2020-04-02" +__copyright__ = "(C) 2020 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QVariant -from qgis.core import (QgsProcessing, - QgsProcessingAlgorithm, - QgsProcessingParameterBoolean, - QgsProcessingParameterNumber, - QgsProcessingParameterFolderDestination, - QgsProcessingParameterFile, - QgsProcessingException, - QgsProcessingParameterDefinition, - QgsProcessingParameterEnum, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterField, - QgsProcessingParameterRasterLayer) +from qgis.core import ( + QgsProcessing, + QgsProcessingAlgorithm, + QgsProcessingParameterBoolean, + QgsProcessingParameterNumber, + QgsProcessingParameterFolderDestination, + QgsProcessingParameterFile, + QgsProcessingException, + QgsProcessingParameterDefinition, + QgsProcessingParameterEnum, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterField, + QgsProcessingParameterRasterLayer, +) import numpy as np from osgeo import gdal @@ -53,7 +55,10 @@ from pathlib import Path from ..util.misc import xy2latlon_fromraster import zipfile -from ..util.umep_solweig_export_component import read_solweig_config, write_solweig_config +from ..util.umep_solweig_export_component import ( + read_solweig_config, + write_solweig_config, +) from ..functions.SOLWEIGpython import Solweig_run as sr import json @@ -66,264 +71,627 @@ class ProcessingSOLWEIGAlgorithm(QgsProcessingAlgorithm): # Constants used to refer to parameters and outputs. They will be # used when calling the algorithm from another algorithm, or when # calling from the QGIS console. - - #Spatial data - INPUT_DSM = 'INPUT_DSM' - INPUT_SVF = 'INPUT_SVF' - INPUT_CDSM = 'INPUT_CDSM' - INPUT_TDSM = 'INPUT_TDSM' - INPUT_HEIGHT = 'INPUT_HEIGHT' - INPUT_ASPECT = 'INPUT_ASPECT' - TRANS_VEG = 'TRANS_VEG' - LEAF_START = 'LEAF_START' - LEAF_END = 'LEAF_END' - CONIFER_TREES = 'CONIFER_TREES' - INPUT_THEIGHT = 'INPUT_THEIGHT' - INPUT_LC = 'INPUT_LC' - USE_LC_BUILD = 'USE_LC_BUILD' - INPUT_DEM = 'INPUT_DEM' - SAVE_BUILD = 'SAVE_BUILD' - INPUT_ANISO = 'INPUT_ANISO' - INPUT_WALLSCHEME = 'INPUT_WALLSCHEME' - WALLTEMP_NETCDF = 'WALLTEMP_NETCDF' - - #Enivornmental parameters - ALBEDO_WALLS = 'ALBEDO_WALLS' - ALBEDO_GROUND = 'ALBEDO_GROUND' - EMIS_WALLS = 'EMIS_WALLS' - EMIS_GROUND = 'EMIS_GROUND' - WALL_TYPE = 'WALL_TYPE' - - #Tmrt parameters - ABS_S = 'ABS_S' - ABS_L = 'ABS_L' - POSTURE = 'POSTURE' - - #Meteorology - INPUT_MET = 'INPUTMET' - ONLYGLOBAL = 'ONLYGLOBAL' - UTC = 'UTC' - - #PET parameters - AGE = 'AGE' - ACTIVITY = 'ACTIVITY' - CLO = 'CLO' - WEIGHT = 'WEIGHT' - HEIGHT = 'HEIGHT' - SEX = 'SEX' - SENSOR_HEIGHT = 'SENSOR_HEIGHT' - - #Optional settings - WOI_FILE = 'WOI_FILE' - WOI_FIELD = 'WOI_FIELD' - # POI = 'POI' - POI_FILE = 'POI_FILE' - POI_FIELD = 'POI_FIELD' - CYL = 'CYL' - - #Output - OUTPUT_DIR = 'OUTPUT_DIR' - OUTPUT_TMRT = 'OUTPUT_TMRT' - OUTPUT_LUP = 'OUTPUT_LUP' - OUTPUT_KUP = 'OUTPUT_KUP' - OUTPUT_KDOWN = 'OUTPUT_KDOWN' - OUTPUT_LDOWN = 'OUTPUT_LDOWN' - OUTPUT_SH = 'OUTPUT_SH' - OUTPUT_TREEPLANTER = 'OUTPUT_TREEPLANTER' + # Spatial data + INPUT_DSM = "INPUT_DSM" + INPUT_SVF = "INPUT_SVF" + INPUT_CDSM = "INPUT_CDSM" + INPUT_TDSM = "INPUT_TDSM" + INPUT_HEIGHT = "INPUT_HEIGHT" + INPUT_ASPECT = "INPUT_ASPECT" + TRANS_VEG = "TRANS_VEG" + LEAF_START = "LEAF_START" + LEAF_END = "LEAF_END" + CONIFER_TREES = "CONIFER_TREES" + INPUT_THEIGHT = "INPUT_THEIGHT" + INPUT_LC = "INPUT_LC" + USE_LC_BUILD = "USE_LC_BUILD" + INPUT_DEM = "INPUT_DEM" + SAVE_BUILD = "SAVE_BUILD" + INPUT_ANISO = "INPUT_ANISO" + INPUT_WALLSCHEME = "INPUT_WALLSCHEME" + WALLTEMP_NETCDF = "WALLTEMP_NETCDF" + + # Enivornmental parameters + ALBEDO_WALLS = "ALBEDO_WALLS" + ALBEDO_GROUND = "ALBEDO_GROUND" + EMIS_WALLS = "EMIS_WALLS" + EMIS_GROUND = "EMIS_GROUND" + WALL_TYPE = "WALL_TYPE" + + # Tmrt parameters + ABS_S = "ABS_S" + ABS_L = "ABS_L" + POSTURE = "POSTURE" + + # Meteorology + INPUT_MET = "INPUTMET" + ONLYGLOBAL = "ONLYGLOBAL" + UTC = "UTC" + + # PET parameters + AGE = "AGE" + ACTIVITY = "ACTIVITY" + CLO = "CLO" + WEIGHT = "WEIGHT" + HEIGHT = "HEIGHT" + SEX = "SEX" + SENSOR_HEIGHT = "SENSOR_HEIGHT" + + # Optional settings + WOI_FILE = "WOI_FILE" + WOI_FIELD = "WOI_FIELD" + # POI = 'POI' + POI_FILE = "POI_FILE" + POI_FIELD = "POI_FIELD" + CYL = "CYL" + + # solweig + groundmodel = "groundmodel" + + # Output + OUTPUT_DIR = "OUTPUT_DIR" + OUTPUT_TMRT = "OUTPUT_TMRT" + OUTPUT_LUP = "OUTPUT_LUP" + OUTPUT_KUP = "OUTPUT_KUP" + OUTPUT_KDOWN = "OUTPUT_KDOWN" + OUTPUT_LDOWN = "OUTPUT_LDOWN" + OUTPUT_SH = "OUTPUT_SH" + OUTPUT_TREEPLANTER = "OUTPUT_TREEPLANTER" def initAlgorithm(self, config): - #spatial - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_DSM, - self.tr('Building and ground Digital Surface Model (DSM)'), None, optional=False)) - self.addParameter(QgsProcessingParameterFile(self.INPUT_SVF, - self.tr('Sky View Factor grids (.zip)'), extension='zip')) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_HEIGHT, - self.tr('Wall height raster'), '', optional=False)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_ASPECT, - self.tr('Wall aspect raster'), '', optional=False)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_CDSM, - self.tr('Vegetation Canopy DSM'), '', optional=True)) - self.addParameter(QgsProcessingParameterNumber(self.TRANS_VEG, - self.tr('Transmissivity of light through vegetation (%):'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(3), True, minValue=0, maxValue=100)) - - self.addParameter(QgsProcessingParameterNumber(self.LEAF_START, - self.tr('First day of year with leaves on trees (if deciduous)'), QgsProcessingParameterNumber.Type.Integer, - QVariant(97), False, minValue=0, maxValue=366)) - - self.addParameter(QgsProcessingParameterNumber(self.LEAF_END, - self.tr('Last day of year with leaves on trees (if deciduous)'), QgsProcessingParameterNumber.Type.Integer, - QVariant(300), False, minValue=0, maxValue=366)) - - self.addParameter(QgsProcessingParameterBoolean(self.CONIFER_TREES, - self.tr("Coniferous trees (deciduous default)"), defaultValue=False)) - - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_TDSM, - self.tr('Vegetation Trunk-zone DSM'), '', optional=True)) - self.addParameter(QgsProcessingParameterNumber(self.INPUT_THEIGHT, - self.tr("Trunk zone height (percent of Canopy Height). Used if no Vegetation Trunk-zone DSM is loaded"), - QgsProcessingParameterNumber.Type.Double, - QVariant(25.0), optional=True, minValue=0.1, maxValue=99.9)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_LC, - self.tr('UMEP land cover grid'), '', optional=True)) - self.addParameter(QgsProcessingParameterBoolean(self.USE_LC_BUILD, - self.tr("Use land cover grid to derive building grid"), defaultValue=False, optional=True)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_DEM, - self.tr('Digital Elevation Model (DEM)'), '', optional=True)) - self.addParameter(QgsProcessingParameterBoolean(self.SAVE_BUILD, - self.tr("Save generated building grid"), defaultValue=False, optional=True)) - self.addParameter(QgsProcessingParameterFile(self.INPUT_ANISO, - self.tr('Shadow maps used for anisotropic model for sky diffuse and longwave radiation (.npz)'), extension='npz', optional=True)) - self.addParameter(QgsProcessingParameterFile(self.INPUT_WALLSCHEME, - self.tr('Voxel data for wall temperature parameterization (.npz)'), extension='npz', optional=True)) - self.addParameter(QgsProcessingParameterBoolean(self.WALLTEMP_NETCDF, - self.tr("Save wall temperatures as NetCDF"), defaultValue=False, optional=True)) - - #Environmental parameters - self.wallType = ((self.tr('Brick'), '0'), - (self.tr('Concrete'), '1'), - (self.tr('Wood'), '2')) - self.addParameter(QgsProcessingParameterEnum(self.WALL_TYPE, - self.tr('Wall type (only with wall scheme)'), - options=[i[0] for i in self.wallType], - defaultValue=0)) - self.addParameter(QgsProcessingParameterNumber(self.ALBEDO_WALLS, - self.tr('Albedo (walls)'), QgsProcessingParameterNumber.Type.Double, - QVariant(0.20), False, minValue=0, maxValue=1)) - self.addParameter(QgsProcessingParameterNumber(self.ALBEDO_GROUND, - self.tr('Albedo (ground)'), QgsProcessingParameterNumber.Type.Double, - QVariant(0.15), False, minValue=0, maxValue=1)) - self.addParameter(QgsProcessingParameterNumber(self.EMIS_WALLS, - self.tr('Emissivity (walls)'), QgsProcessingParameterNumber.Type.Double, - QVariant(0.90), False, minValue=0, maxValue=1)) - self.addParameter(QgsProcessingParameterNumber(self.EMIS_GROUND, - self.tr('Emissivity (ground)'), QgsProcessingParameterNumber.Type.Double, - QVariant(0.95), False, minValue=0, maxValue=1)) - - #Tmrt parameters - self.addParameter(QgsProcessingParameterNumber(self.ABS_S, - self.tr('Absorption of shortwave radiation of human body'), QgsProcessingParameterNumber.Type.Double, - QVariant(0.70), False, minValue=0, maxValue=1)) - self.addParameter(QgsProcessingParameterNumber(self.ABS_L, - self.tr('Absorption of longwave radiation of human body'), QgsProcessingParameterNumber.Type.Double, - QVariant(0.95), False, minValue=0, maxValue=1)) - self.addParameter(QgsProcessingParameterEnum( - self.POSTURE, self.tr('Posture of human body'), ['Standing', 'Sitting'], defaultValue=0)) - self.addParameter(QgsProcessingParameterBoolean(self.CYL, - self.tr("Consider human as cylinder instead of box"), defaultValue=True)) - - #Meteorology - self.addParameter(QgsProcessingParameterFile(self.INPUT_MET, - self.tr('Input meteorological file (.txt)'), extension='txt')) - self.addParameter(QgsProcessingParameterBoolean(self.ONLYGLOBAL, - self.tr("Estimate diffuse and direct shortwave radiation from global radiation"), defaultValue=False)) - self.addParameter(QgsProcessingParameterNumber(self.UTC, - self.tr('Coordinated Universal Time (UTC) '), - QgsProcessingParameterNumber.Type.Integer, - QVariant(0), False, minValue=-12, maxValue=12)) + # spatial + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_DSM, + self.tr("Building and ground Digital Surface Model (DSM)"), + None, + optional=False, + ) + ) + self.addParameter( + QgsProcessingParameterFile( + self.INPUT_SVF, + self.tr("Sky View Factor grids (.zip)"), + extension="zip", + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_HEIGHT, + self.tr("Wall height raster"), + "", + optional=False, + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_ASPECT, + self.tr("Wall aspect raster"), + "", + optional=False, + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_CDSM, + self.tr("Vegetation Canopy DSM"), + "", + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.TRANS_VEG, + self.tr("Transmissivity of light through vegetation (%):"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(3), + True, + minValue=0, + maxValue=100, + ) + ) + + self.addParameter( + QgsProcessingParameterNumber( + self.LEAF_START, + self.tr( + "First day of year with leaves on trees (if deciduous)" + ), + QgsProcessingParameterNumber.Type.Integer, + QVariant(97), + False, + minValue=0, + maxValue=366, + ) + ) + + self.addParameter( + QgsProcessingParameterNumber( + self.LEAF_END, + self.tr( + "Last day of year with leaves on trees (if deciduous)" + ), + QgsProcessingParameterNumber.Type.Integer, + QVariant(300), + False, + minValue=0, + maxValue=366, + ) + ) + + self.addParameter( + QgsProcessingParameterBoolean( + self.CONIFER_TREES, + self.tr("Coniferous trees (deciduous default)"), + defaultValue=False, + ) + ) + + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_TDSM, + self.tr("Vegetation Trunk-zone DSM"), + "", + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.INPUT_THEIGHT, + self.tr( + "Trunk zone height (percent of Canopy Height). Used if no Vegetation Trunk-zone DSM is loaded" + ), + QgsProcessingParameterNumber.Type.Double, + QVariant(25.0), + optional=True, + minValue=0.1, + maxValue=99.9, + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_LC, + self.tr("UMEP land cover grid"), + "", + optional=False, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.USE_LC_BUILD, + self.tr("Use land cover grid to derive building grid"), + defaultValue=False, + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_DEM, + self.tr("Digital Elevation Model (DEM)"), + "", + optional=False, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.SAVE_BUILD, + self.tr("Save generated building grid"), + defaultValue=False, + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterFile( + self.INPUT_ANISO, + self.tr( + "Shadow maps used for anisotropic model for sky diffuse and longwave radiation (.npz)" + ), + extension="npz", + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterFile( + self.INPUT_WALLSCHEME, + self.tr( + "Voxel data for wall temperature parameterization (.npz)" + ), + extension="npz", + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.WALLTEMP_NETCDF, + self.tr("Save wall temperatures as NetCDF"), + defaultValue=False, + optional=True, + ) + ) + + # Environmental parameters + self.wallType = ( + (self.tr("Brick"), "0"), + (self.tr("Concrete"), "1"), + (self.tr("Wood"), "2"), + ) + self.addParameter( + QgsProcessingParameterEnum( + self.WALL_TYPE, + self.tr("Wall type (only with wall scheme)"), + options=[i[0] for i in self.wallType], + defaultValue=0, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.ALBEDO_WALLS, + self.tr("Albedo (walls)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(0.20), + False, + minValue=0, + maxValue=1, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.ALBEDO_GROUND, + self.tr("Albedo (ground)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(0.15), + False, + minValue=0, + maxValue=1, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.EMIS_WALLS, + self.tr("Emissivity (walls)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(0.90), + False, + minValue=0, + maxValue=1, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.EMIS_GROUND, + self.tr("Emissivity (ground)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(0.95), + False, + minValue=0, + maxValue=1, + ) + ) + + # Tmrt parameters + self.addParameter( + QgsProcessingParameterNumber( + self.ABS_S, + self.tr("Absorption of shortwave radiation of human body"), + QgsProcessingParameterNumber.Type.Double, + QVariant(0.70), + False, + minValue=0, + maxValue=1, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.ABS_L, + self.tr("Absorption of longwave radiation of human body"), + QgsProcessingParameterNumber.Type.Double, + QVariant(0.95), + False, + minValue=0, + maxValue=1, + ) + ) + self.addParameter( + QgsProcessingParameterEnum( + self.POSTURE, + self.tr("Posture of human body"), + ["Standing", "Sitting"], + defaultValue=0, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.CYL, + self.tr("Consider human as cylinder instead of box"), + defaultValue=True, + ) + ) + + # Meteorology + self.addParameter( + QgsProcessingParameterFile( + self.INPUT_MET, + self.tr("Input meteorological file (.txt)"), + extension="txt", + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.ONLYGLOBAL, + self.tr( + "Estimate diffuse and direct shortwave radiation from global radiation" + ), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.UTC, + self.tr("Coordinated Universal Time (UTC) "), + QgsProcessingParameterNumber.Type.Integer, + QVariant(0), + False, + minValue=-12, + maxValue=12, + ) + ) # ADVANCED PARAMETERS - woifile = QgsProcessingParameterFeatureSource(self.WOI_FILE, - self.tr('Vector point file including Wall of Interest(s) for output with wall surface temperatures'), [QgsProcessing.SourceType.TypeVectorPoint], optional=True) - woifile.setFlags(woifile.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + woifile = QgsProcessingParameterFeatureSource( + self.WOI_FILE, + self.tr( + "Vector point file including Wall of Interest(s) for output with wall surface temperatures" + ), + [QgsProcessing.SourceType.TypeVectorPoint], + optional=True, + ) + woifile.setFlags( + woifile.flags() + | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(woifile) - woi_field = QgsProcessingParameterField(self.WOI_FIELD, - self.tr('Wall ID field'),'', self.WOI_FILE, QgsProcessingParameterField.DataType.Numeric, optional=True) - woi_field.setFlags(woi_field.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) - self.addParameter(woi_field) - - #POIs for thermal comfort estimations - poifile = QgsProcessingParameterFeatureSource(self.POI_FILE, - self.tr('Vector point file including Point of Interest(s) for thermal comfort calculations (PET and UTCI)'), [QgsProcessing.SourceType.TypeVectorPoint], optional=True) - poifile.setFlags(poifile.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + woi_field = QgsProcessingParameterField( + self.WOI_FIELD, + self.tr("Wall ID field"), + "", + self.WOI_FILE, + QgsProcessingParameterField.DataType.Numeric, + optional=True, + ) + woi_field.setFlags( + woi_field.flags() + | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) + self.addParameter(woi_field) + + # POIs for thermal comfort estimations + poifile = QgsProcessingParameterFeatureSource( + self.POI_FILE, + self.tr( + "Vector point file including Point of Interest(s) for thermal comfort calculations (PET and UTCI)" + ), + [QgsProcessing.SourceType.TypeVectorPoint], + optional=True, + ) + poifile.setFlags( + poifile.flags() + | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(poifile) - poi_field = QgsProcessingParameterField(self.POI_FIELD, - self.tr('ID field'),'', self.POI_FILE, QgsProcessingParameterField.DataType.Numeric, optional=True) - poi_field.setFlags(poi_field.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + poi_field = QgsProcessingParameterField( + self.POI_FIELD, + self.tr("ID field"), + "", + self.POI_FILE, + QgsProcessingParameterField.DataType.Numeric, + optional=True, + ) + poi_field.setFlags( + poi_field.flags() + | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(poi_field) - #PET parameters - age = QgsProcessingParameterNumber(self.AGE, self.tr('Age (yy)'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(35), optional=True, minValue=0, maxValue=120) - age.setFlags(age.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + # PET parameters + age = QgsProcessingParameterNumber( + self.AGE, + self.tr("Age (yy)"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(35), + optional=True, + minValue=0, + maxValue=120, + ) + age.setFlags( + age.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(age) - act = QgsProcessingParameterNumber(self.ACTIVITY, self.tr('Activity (W)'), - QgsProcessingParameterNumber.Type.Double, - QVariant(80), optional=True, minValue=0, maxValue=1000) - act.setFlags(act.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + act = QgsProcessingParameterNumber( + self.ACTIVITY, + self.tr("Activity (W)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(80), + optional=True, + minValue=0, + maxValue=1000, + ) + act.setFlags( + act.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(act) - clo = QgsProcessingParameterNumber(self.CLO, self.tr('Clothing (clo)'), - QgsProcessingParameterNumber.Type.Double, - QVariant(0.9), optional=True, minValue=0, maxValue=10) - clo.setFlags(clo.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + clo = QgsProcessingParameterNumber( + self.CLO, + self.tr("Clothing (clo)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(0.9), + optional=True, + minValue=0, + maxValue=10, + ) + clo.setFlags( + clo.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(clo) - wei = QgsProcessingParameterNumber(self.WEIGHT, self.tr('Weight (kg)'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(75), optional=True, minValue=0, maxValue=500) - wei.setFlags(wei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + wei = QgsProcessingParameterNumber( + self.WEIGHT, + self.tr("Weight (kg)"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(75), + optional=True, + minValue=0, + maxValue=500, + ) + wei.setFlags( + wei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(wei) - hei = QgsProcessingParameterNumber(self.HEIGHT, self.tr('Height (cm)'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(180), optional=True, minValue=0, maxValue=250) - hei.setFlags(hei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + hei = QgsProcessingParameterNumber( + self.HEIGHT, + self.tr("Height (cm)"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(180), + optional=True, + minValue=0, + maxValue=250, + ) + hei.setFlags( + hei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(hei) sex = QgsProcessingParameterEnum( - self.SEX, self.tr('Sex'), ['Male', 'Female'], optional=True, defaultValue=0) - sex.setFlags(sex.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + self.SEX, + self.tr("Sex"), + ["Male", "Female"], + optional=True, + defaultValue=0, + ) + sex.setFlags( + sex.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(sex) - shei = QgsProcessingParameterNumber(self.SENSOR_HEIGHT, self.tr('Height of wind sensor (m agl)'), - QgsProcessingParameterNumber.Type.Double, - QVariant(10), optional=True, minValue=0, maxValue=250) - shei.setFlags(shei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + shei = QgsProcessingParameterNumber( + self.SENSOR_HEIGHT, + self.tr("Height of wind sensor (m agl)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(10), + optional=True, + minValue=0, + maxValue=250, + ) + shei.setFlags( + shei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(shei) - #OUTPUT - self.addParameter(QgsProcessingParameterBoolean(self.OUTPUT_TMRT, - self.tr("Save Mean Radiant Temperature raster(s)"), defaultValue=True)) - self.addParameter(QgsProcessingParameterBoolean(self.OUTPUT_KDOWN, - self.tr("Save Incoming shortwave radiation raster(s)"), defaultValue=False)) - self.addParameter(QgsProcessingParameterBoolean(self.OUTPUT_KUP, - self.tr("Save Outgoing shortwave radiation raster(s)"), defaultValue=False)) - self.addParameter(QgsProcessingParameterBoolean(self.OUTPUT_LDOWN, - self.tr("Save Incoming longwave radiation raster(s)"), defaultValue=False)) - self.addParameter(QgsProcessingParameterBoolean(self.OUTPUT_LUP, - self.tr("Save Outgoing longwave radiation raster(s)"), defaultValue=False)) - self.addParameter(QgsProcessingParameterBoolean(self.OUTPUT_SH, - self.tr("Save shadow raster(s)"), defaultValue=False)) - self.addParameter(QgsProcessingParameterBoolean(self.OUTPUT_TREEPLANTER, - self.tr("Save necessary raster(s) for the TreePlanter and Spatial TC tools"), defaultValue=False)) - self.addParameter(QgsProcessingParameterFolderDestination(self.OUTPUT_DIR, - 'Output folder')) + # OUTPUT + self.addParameter( + QgsProcessingParameterBoolean( + self.OUTPUT_TMRT, + self.tr("Save Mean Radiant Temperature raster(s)"), + defaultValue=True, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.OUTPUT_KDOWN, + self.tr("Save Incoming shortwave radiation raster(s)"), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.OUTPUT_KUP, + self.tr("Save Outgoing shortwave radiation raster(s)"), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.OUTPUT_LDOWN, + self.tr("Save Incoming longwave radiation raster(s)"), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.OUTPUT_LUP, + self.tr("Save Outgoing longwave radiation raster(s)"), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.OUTPUT_SH, + self.tr("Save shadow raster(s)"), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.OUTPUT_TREEPLANTER, + self.tr( + "Save necessary raster(s) for the TreePlanter and Spatial TC tools" + ), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterFolderDestination( + self.OUTPUT_DIR, "Output folder" + ) + ) self.plugin_dir = os.path.dirname(__file__) - self.temp_dir = os.path.dirname(self.plugin_dir) + '/temp' + self.temp_dir = os.path.dirname(self.plugin_dir) + "/temp" def processAlgorithm(self, parameters, context, feedback): - np.seterr(divide='ignore', invalid='ignore') - + np.seterr(divide="ignore", invalid="ignore") + # InputParameters - dsmlayer = self.parameterAsRasterLayer(parameters, self.INPUT_DSM, context) - transVeg = self.parameterAsDouble(parameters, self.TRANS_VEG, context) / 100. - firstdayleaf = self.parameterAsInt(parameters, self.LEAF_START, context) + dsmlayer = self.parameterAsRasterLayer( + parameters, self.INPUT_DSM, context + ) + transVeg = ( + self.parameterAsDouble(parameters, self.TRANS_VEG, context) / 100.0 + ) + firstdayleaf = self.parameterAsInt( + parameters, self.LEAF_START, context + ) lastdayleaf = self.parameterAsInt(parameters, self.LEAF_END, context) - conifer_bool = self.parameterAsBool(parameters, self.CONIFER_TREES, context) - vegdsm = self.parameterAsRasterLayer(parameters, self.INPUT_CDSM, context) - vegdsm2 = self.parameterAsRasterLayer(parameters, self.INPUT_TDSM, context) - lcgrid = self.parameterAsRasterLayer(parameters, self.INPUT_LC, context) - useLcBuild = self.parameterAsBool(parameters, self.USE_LC_BUILD, context) + conifer_bool = self.parameterAsBool( + parameters, self.CONIFER_TREES, context + ) + vegdsm = self.parameterAsRasterLayer( + parameters, self.INPUT_CDSM, context + ) + vegdsm2 = self.parameterAsRasterLayer( + parameters, self.INPUT_TDSM, context + ) + lcgrid = self.parameterAsRasterLayer( + parameters, self.INPUT_LC, context + ) + useLcBuild = self.parameterAsBool( + parameters, self.USE_LC_BUILD, context + ) dem = None inputSVF = self.parameterAsString(parameters, self.INPUT_SVF, context) - whlayer = self.parameterAsRasterLayer(parameters, self.INPUT_HEIGHT, context) - walayer = self.parameterAsRasterLayer(parameters, self.INPUT_ASPECT, context) - trunkr = self.parameterAsDouble(parameters, self.INPUT_THEIGHT, context) + whlayer = self.parameterAsRasterLayer( + parameters, self.INPUT_HEIGHT, context + ) + walayer = self.parameterAsRasterLayer( + parameters, self.INPUT_ASPECT, context + ) + trunkr = self.parameterAsDouble( + parameters, self.INPUT_THEIGHT, context + ) onlyglobal = self.parameterAsBool(parameters, self.ONLYGLOBAL, context) utc = self.parameterAsDouble(parameters, self.UTC, context) inputMet = self.parameterAsString(parameters, self.INPUT_MET, context) # usePOI = self.parameterAsBool(parameters, self.POI, context) - poilyr = self.parameterAsVectorLayer(parameters, self.POI_FILE, context) + poilyr = self.parameterAsVectorLayer( + parameters, self.POI_FILE, context + ) poi_field = None mbody = None ht = None @@ -334,14 +702,22 @@ def processAlgorithm(self, parameters, context, feedback): sensorheight = None saveBuild = self.parameterAsBool(parameters, self.SAVE_BUILD, context) demforbuild = 0 - folderPathPerez = self.parameterAsString(parameters, self.INPUT_ANISO, context) - folderWallScheme = self.parameterAsString(parameters, self.INPUT_WALLSCHEME, context) - wallNetCDF = self.parameterAsBool(parameters, self.WALLTEMP_NETCDF, context) + folderPathPerez = self.parameterAsString( + parameters, self.INPUT_ANISO, context + ) + folderWallScheme = self.parameterAsString( + parameters, self.INPUT_WALLSCHEME, context + ) + wallNetCDF = self.parameterAsBool( + parameters, self.WALLTEMP_NETCDF, context + ) poisxy = None poiname = None # Wall of interest - woilyr = self.parameterAsVectorLayer(parameters, self.WOI_FILE, context) + woilyr = self.parameterAsVectorLayer( + parameters, self.WOI_FILE, context + ) woi_field = None woisxy = None woiname = None @@ -350,7 +726,7 @@ def processAlgorithm(self, parameters, context, feedback): absK = self.parameterAsDouble(parameters, self.ABS_S, context) absL = self.parameterAsDouble(parameters, self.ABS_L, context) pos = self.parameterAsInt(parameters, self.POSTURE, context) - + cyl = self.parameterAsBool(parameters, self.CYL, context) if pos == 0: @@ -364,24 +740,41 @@ def processAlgorithm(self, parameters, context, feedback): height = 0.75 Fcyl = 0.2 - albedo_b = self.parameterAsDouble(parameters, self.ALBEDO_WALLS, context) - albedo_g = self.parameterAsDouble(parameters, self.ALBEDO_GROUND, context) + albedo_b = self.parameterAsDouble( + parameters, self.ALBEDO_WALLS, context + ) + albedo_g = self.parameterAsDouble( + parameters, self.ALBEDO_GROUND, context + ) ewall = self.parameterAsDouble(parameters, self.EMIS_WALLS, context) eground = self.parameterAsDouble(parameters, self.EMIS_GROUND, context) - elvis = 0 # option removed 20200907 in processing UMEP. Should be removed completely + elvis = 0 # option removed 20200907 in processing UMEP. Should be removed completely # thermal_effusivity = self.parameterAsDouble(parameters, self.EFFUS_WALL, context) - wall_type = str(100 + int(self.parameterAsString(parameters, self.WALL_TYPE, context))) - - outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) - outputTmrt = self.parameterAsBool(parameters, self.OUTPUT_TMRT, context) + wall_type = str( + 100 + + int(self.parameterAsString(parameters, self.WALL_TYPE, context)) + ) + + outputDir = self.parameterAsString( + parameters, self.OUTPUT_DIR, context + ) + outputTmrt = self.parameterAsBool( + parameters, self.OUTPUT_TMRT, context + ) outputSh = self.parameterAsBool(parameters, self.OUTPUT_SH, context) outputKup = self.parameterAsBool(parameters, self.OUTPUT_KUP, context) - outputKdown = self.parameterAsBool(parameters, self.OUTPUT_KDOWN, context) + outputKdown = self.parameterAsBool( + parameters, self.OUTPUT_KDOWN, context + ) outputLup = self.parameterAsBool(parameters, self.OUTPUT_LUP, context) - outputLdown = self.parameterAsBool(parameters, self.OUTPUT_LDOWN, context) - outputTreeplanter = self.parameterAsBool(parameters, self.OUTPUT_TREEPLANTER, context) + outputLdown = self.parameterAsBool( + parameters, self.OUTPUT_LDOWN, context + ) + outputTreeplanter = self.parameterAsBool( + parameters, self.OUTPUT_TREEPLANTER, context + ) outputKdiff = False - #outputSstr = False + # outputSstr = False # If "Save necessary rasters for TreePlanter tool" is ticked, save the following raster for TreePlanter or Spatial TC if outputTreeplanter: @@ -393,72 +786,92 @@ def processAlgorithm(self, parameters, context, feedback): outputSh = True saveBuild = True outputKdiff = True - #outputSstr = True + # outputSstr = True - if parameters['OUTPUT_DIR'] == 'TEMPORARY_OUTPUT': + if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): os.mkdir(outputDir) # Load parameters and config settings for SOLWEIG - with open(self.plugin_dir + '/parametersforsolweig.json', "r") as jsn: + with open(self.plugin_dir + "/parametersforsolweig.json", "r") as jsn: solweig_parameters = json.load(jsn) - configDict = read_solweig_config(self.plugin_dir + '/configsolweig.ini') + configDict = read_solweig_config( + self.plugin_dir + "/configsolweig.ini" + ) # Add GUI Tmrt settings to SOLWEIG parameter file - solweig_parameters['Tmrt_params']['Value']['absK'] = absK - solweig_parameters['Tmrt_params']['Value']['absL'] = absL + solweig_parameters["Tmrt_params"]["Value"]["absK"] = absK + solweig_parameters["Tmrt_params"]["Value"]["absL"] = absL if pos == 0: - solweig_parameters['Tmrt_params']['Value']['posture'] = 'Standing' + solweig_parameters["Tmrt_params"]["Value"]["posture"] = "Standing" else: - solweig_parameters['Tmrt_params']['Value']['posture'] = 'Sitting' + solweig_parameters["Tmrt_params"]["Value"]["posture"] = "Sitting" - solweig_parameters['Albedo']['Effective']['Value']['Cobble_stone_2014a'] = albedo_g - solweig_parameters['Albedo']['Effective']['Value']['Walls'] = albedo_b - solweig_parameters['Emissivity']['Value']['Cobble_stone_2014a'] = eground - solweig_parameters['Emissivity']['Value']['Walls'] = ewall + solweig_parameters["Albedo"]["Effective"]["Value"][ + "Cobble_stone_2014a" + ] = albedo_g + solweig_parameters["Albedo"]["Effective"]["Value"]["Walls"] = albedo_b + solweig_parameters["Emissivity"]["Value"][ + "Cobble_stone_2014a" + ] = eground + solweig_parameters["Emissivity"]["Value"]["Walls"] = ewall # Load dsm layer provider = dsmlayer.dataProvider() filepath_dsm = str(provider.dataSourceUri()) gdal_dsm = gdal.Open(filepath_dsm) dsm = gdal_dsm.ReadAsArray().astype(float) - rows = dsmlayer.height() # new way to get x and y pixels + rows = dsmlayer.height() # new way to get x and y pixels cols = dsmlayer.width() # rows = dsm.shape[0] # cols = dsm.shape[1] - + # response to issue #85 nd = gdal_dsm.GetRasterBand(1).GetNoDataValue() if dsm.min() < 0: dsmraise = np.abs(dsm.min()) # dsm = dsm + dsmraise #moved to Solweig_run - feedback.setProgressText('Digital Surface Model (DSM) included negative values. DSM raised with ' + str(dsmraise) + 'm.') + feedback.setProgressText( + "Digital Surface Model (DSM) included negative values. DSM raised with " + + str(dsmraise) + + "m." + ) else: dsmraise = 0 # Get latlon from grid coordinate system dsm_wkt = dsmlayer.crs().toWkt() - lat, lon, scale, minx, miny = xy2latlon_fromraster(dsm_wkt, gdal_dsm) # new funciton in misc - - feedback.setProgressText('Longitude derived from DSM: ' + str(lon)) - feedback.setProgressText('Latitude derived from DSM: ' + str(lat)) + lat, lon, scale, minx, miny = xy2latlon_fromraster( + dsm_wkt, gdal_dsm + ) # new funciton in misc + + feedback.setProgressText("Longitude derived from DSM: " + str(lon)) + feedback.setProgressText("Latitude derived from DSM: " + str(lat)) # if useVegdem: if vegdsm is not None: usevegdem = 1 - feedback.setProgressText('Vegetation scheme activated') + feedback.setProgressText("Vegetation scheme activated") # check cdsm raster filePath_cdsm = str(vegdsm.dataProvider().dataSourceUri()) vegrows = vegdsm.height() vegcols = vegdsm.width() - solweig_parameters["Tree_settings"]["Value"]["Transmissivity"] = transVeg - solweig_parameters["Tree_settings"]["Value"]["First_day_leaf"] = firstdayleaf - solweig_parameters["Tree_settings"]["Value"]["Last_day_leaf"] = lastdayleaf + solweig_parameters["Tree_settings"]["Value"][ + "Transmissivity" + ] = transVeg + solweig_parameters["Tree_settings"]["Value"][ + "First_day_leaf" + ] = firstdayleaf + solweig_parameters["Tree_settings"]["Value"][ + "Last_day_leaf" + ] = lastdayleaf if not (vegrows == rows) & (vegcols == cols): - raise QgsProcessingException("Error in Vegetation Canopy DSM: All rasters must be of same extent and resolution") + raise QgsProcessingException( + "Error in Vegetation Canopy DSM: All rasters must be of same extent and resolution" + ) if vegdsm2 is not None: filePath_tdsm = str(vegdsm2.dataProvider().dataSourceUri()) @@ -466,19 +879,23 @@ def processAlgorithm(self, parameters, context, feedback): vegcols = vegdsm2.width() if not (vegrows == rows) & (vegcols == cols): # & - raise QgsProcessingException("Error in Trunk Zone DSM: All rasters must be of same extent and resolution") + raise QgsProcessingException( + "Error in Trunk Zone DSM: All rasters must be of same extent and resolution" + ) else: - solweig_parameters["Tree_settings"]["Value"]["Trunk_ratio"] = trunkr / 100. - filePath_tdsm = '' + solweig_parameters["Tree_settings"]["Value"]["Trunk_ratio"] = ( + trunkr / 100.0 + ) + filePath_tdsm = "" else: usevegdem = 0 - filePath_cdsm = '' - filePath_tdsm = '' + filePath_cdsm = "" + filePath_tdsm = "" # Land cover if lcgrid is not None: landcover = 1 - feedback.setProgressText('Land cover scheme activated') + feedback.setProgressText("Land cover scheme activated") # load raster gdal.AllRegister() @@ -491,25 +908,35 @@ def processAlgorithm(self, parameters, context, feedback): lccols = lcgrid.shape[1] if not (lcrows == rows) & (lccols == cols): - raise QgsProcessingException("Error in land cover grid: All grids must be of same extent and resolution") + raise QgsProcessingException( + "Error in land cover grid: All grids must be of same extent and resolution" + ) - baddataConifer = (lcgrid == 3) - baddataDecid = (lcgrid == 4) + baddataConifer = lcgrid == 3 + baddataDecid = lcgrid == 4 if baddataConifer.any(): - raise QgsProcessingException("Error in land cover grid: Land cover grid includes Confier land cover class. Ground cover information (underneath canopy) is required.") + raise QgsProcessingException( + "Error in land cover grid: Land cover grid includes Confier land cover class. Ground cover information (underneath canopy) is required." + ) if baddataDecid.any(): - raise QgsProcessingException("Error in land cover grid: Land cover grid includes Decidiuous land cover class. Ground cover information (underneath canopy) is required.") + raise QgsProcessingException( + "Error in land cover grid: Land cover grid includes Decidiuous land cover class. Ground cover information (underneath canopy) is required." + ) if np.isnan(lcgrid).any(): - raise QgsProcessingException("Error in land cover grid: Land cover grid includes NaN values. Use the QGIS Fill NoData cells tool to remove NaN values.") + raise QgsProcessingException( + "Error in land cover grid: Land cover grid includes NaN values. Use the QGIS Fill NoData cells tool to remove NaN values." + ) else: - filepath_lc = '' + filepath_lc = "" landcover = 0 lcgrid = 0 # DEM # if not useLcBuild: demforbuild = 1 - dem = self.parameterAsRasterLayer(parameters, self.INPUT_DEM, context) + dem = self.parameterAsRasterLayer( + parameters, self.INPUT_DEM, context + ) if dem is None: raise QgsProcessingException("Error: No valid DEM selected") @@ -525,221 +952,302 @@ def processAlgorithm(self, parameters, context, feedback): demcols = dem.shape[1] if not (demrows == rows) & (demcols == cols): - raise QgsProcessingException( "Error in DEM: All grids must be of same extent and resolution") + raise QgsProcessingException( + "Error in DEM: All grids must be of same extent and resolution" + ) # response to issue and #230 nd = dataSet.GetRasterBand(1).GetNoDataValue() - dem[dem == nd] = 0. + dem[dem == nd] = 0.0 if dem.min() < 0: demraise = np.abs(dem.min()) dem = dem + demraise - feedback.setProgressText('Digital Evevation Model (DEM) included negative values. DEM raised with ' + str(demraise) + 'm.') + feedback.setProgressText( + "Digital Evevation Model (DEM) included negative values. DEM raised with " + + str(demraise) + + "m." + ) else: demraise = 0 if (dsmraise != demraise) and (dsmraise - demraise > 0.5): - feedback.setProgressText('WARNiNG! DEM and DSM was raised unequally (difference > 0.5 m). Check your input data!') + feedback.setProgressText( + "WARNiNG! DEM and DSM was raised unequally (difference > 0.5 m). Check your input data!" + ) else: demforbuild = 0 - filepath_dem = '' + filepath_dem = "" - #SVFs - zip = zipfile.ZipFile(inputSVF, 'r') - zip.extract('svf.tif',self.temp_dir) + # SVFs + zip = zipfile.ZipFile(inputSVF, "r") + zip.extract("svf.tif", self.temp_dir) zip.close() try: dataSet = gdal.Open(self.temp_dir + "/svf.tif") svf = dataSet.ReadAsArray().astype(float) except: - raise QgsProcessingException("SVF import error: The zipfile including the SVFs seems corrupt. Retry calcualting the SVFs in the Pre-processor or choose another file.") + raise QgsProcessingException( + "SVF import error: The zipfile including the SVFs seems corrupt. Retry calcualting the SVFs in the Pre-processor or choose another file." + ) if not (svf.shape[0] == rows) & (svf.shape[1] == cols): # & - raise QgsProcessingException("Error in svf rasters: All grids must be of same extent and resolution") + raise QgsProcessingException( + "Error in svf rasters: All grids must be of same extent and resolution" + ) - feedback.setProgressText('Loading Sky View Factor rasters') + feedback.setProgressText("Loading Sky View Factor rasters") # wall height layer if whlayer is None: - raise QgsProcessingException("Error: No valid wall height raster layer is selected") + raise QgsProcessingException( + "Error: No valid wall height raster layer is selected" + ) filepath_wh = str(whlayer.dataProvider().dataSourceUri()) if not (whlayer.height() == rows) & (whlayer.width() == cols): - raise QgsProcessingException("Error in Wall height raster: All rasters must be of same extent and resolution") + raise QgsProcessingException( + "Error in Wall height raster: All rasters must be of same extent and resolution" + ) # wall aspectlayer if walayer is None: - raise QgsProcessingException("Error: No valid wall aspect raster layer is selected") + raise QgsProcessingException( + "Error: No valid wall aspect raster layer is selected" + ) filepath_wa = str(walayer.dataProvider().dataSourceUri()) if not (walayer.height() == rows) & (walayer.width() == cols): - raise QgsProcessingException("Error in Wall aspect raster: All rasters must be of same extent and resolution") + raise QgsProcessingException( + "Error in Wall aspect raster: All rasters must be of same extent and resolution" + ) # Metdata headernum = 1 - delim = ' ' + delim = " " try: - self.metdata = np.loadtxt(inputMet,skiprows=headernum, delimiter=delim) + self.metdata = np.loadtxt( + inputMet, skiprows=headernum, delimiter=delim + ) except: - raise QgsProcessingException("Error: Make sure format of meteorological file is correct. You can" - "prepare your data by using 'Prepare Existing Data' in " - "the Pre-processor") - - testwhere = np.where((self.metdata[:, 14] < 0.0) | (self.metdata[:, 14] > 1300.0)) + raise QgsProcessingException( + "Error: Make sure format of meteorological file is correct. You can" + "prepare your data by using 'Prepare Existing Data' in " + "the Pre-processor" + ) + + testwhere = np.where( + (self.metdata[:, 14] < 0.0) | (self.metdata[:, 14] > 1300.0) + ) if testwhere[0].__len__() > 0: - raise QgsProcessingException("Error: Kdown - beyond what is expected at line: " + str(testwhere[0] + 1)) + raise QgsProcessingException( + "Error: Kdown - beyond what is expected at line: " + + str(testwhere[0] + 1) + ) if self.metdata.shape[1] == 24: feedback.setProgressText("Meteorological data successfully loaded") else: - raise QgsProcessingException("Error: Wrong number of columns in meteorological data. You can " - "prepare your data by using 'Prepare Existing Data' in " - "the Pre-processor") + raise QgsProcessingException( + "Error: Wrong number of columns in meteorological data. You can " + "prepare your data by using 'Prepare Existing Data' in " + "the Pre-processor" + ) # Check if diffuse and direct radiation exist if onlyglobal == 0: if np.min(self.metdata[:, 21]) == -999: - raise QgsProcessingException("Diffuse radiation include NoData values", - 'Tick in the box "Estimate diffuse and direct shortwave..." or aqcuire ' - 'observed values from external data sources.') + raise QgsProcessingException( + "Diffuse radiation include NoData values", + 'Tick in the box "Estimate diffuse and direct shortwave..." or aqcuire ' + "observed values from external data sources.", + ) if np.min(self.metdata[:, 22]) == -999: - raise QgsProcessingException("Direct radiation include NoData values", - 'Tick in the box "Estimate diffuse and direct shortwave..." or aqcuire ' - 'observed values from external data sources.') + raise QgsProcessingException( + "Direct radiation include NoData values", + 'Tick in the box "Estimate diffuse and direct shortwave..." or aqcuire ' + "observed values from external data sources.", + ) # POIs check - if poilyr is not None: # usePOI: - + if poilyr is not None: # usePOI: + poi_file = str(poilyr.dataProvider().dataSourceUri()) - poi_field = self.parameterAsString(parameters, self.POI_FIELD, context) + poi_field = self.parameterAsString( + parameters, self.POI_FIELD, context + ) # Other PET variables - solweig_parameters['PET_settings']['Value']['Age'] = self.parameterAsDouble(parameters, self.AGE, context) - solweig_parameters['PET_settings']['Value']['Weight'] = self.parameterAsDouble(parameters, self.WEIGHT, context) - solweig_parameters['PET_settings']['Value']['Height'] = self.parameterAsDouble(parameters, self.HEIGHT, context) / 100. - solweig_parameters['PET_settings']['Value']['clo'] = self.parameterAsDouble(parameters, self.CLO, context) - solweig_parameters['PET_settings']['Value']['Activity'] = self.parameterAsDouble(parameters, self.WEIGHT, context) + solweig_parameters["PET_settings"]["Value"]["Age"] = ( + self.parameterAsDouble(parameters, self.AGE, context) + ) + solweig_parameters["PET_settings"]["Value"]["Weight"] = ( + self.parameterAsDouble(parameters, self.WEIGHT, context) + ) + solweig_parameters["PET_settings"]["Value"]["Height"] = ( + self.parameterAsDouble(parameters, self.HEIGHT, context) + / 100.0 + ) + solweig_parameters["PET_settings"]["Value"]["clo"] = ( + self.parameterAsDouble(parameters, self.CLO, context) + ) + solweig_parameters["PET_settings"]["Value"]["Activity"] = ( + self.parameterAsDouble(parameters, self.WEIGHT, context) + ) if (self.parameterAsInt(parameters, self.SEX, context) + 1) == 1: - solweig_parameters['PET_settings']['Value']['Sex'] = 'Male' + solweig_parameters["PET_settings"]["Value"]["Sex"] = "Male" else: - solweig_parameters['PET_settings']['Value']['Sex'] = 'Female' + solweig_parameters["PET_settings"]["Value"]["Sex"] = "Female" - solweig_parameters['Wind_Height']['Value']['magl'] = self.parameterAsDouble(parameters, self.SENSOR_HEIGHT, context) + solweig_parameters["Wind_Height"]["Value"]["magl"] = ( + self.parameterAsDouble(parameters, self.SENSOR_HEIGHT, context) + ) - feedback.setProgressText("Point of interest (POI) vector data identified") + feedback.setProgressText( + "Point of interest (POI) vector data identified" + ) else: - poi_file = '' - poi_field = '' + poi_file = "" + poi_field = "" # Import shadow matrices (Anisotropic sky) - if folderPathPerez: #UseAniso + if folderPathPerez: # UseAniso anisotropic_sky = 1 - feedback.setProgressText("Anisotropic sky for diffuse shortwave radiation (Perez et al., 1993) and longwave radiation (Martin & Berdahl, 1984)") + feedback.setProgressText( + "Anisotropic sky for diffuse shortwave radiation (Perez et al., 1993) and longwave radiation (Martin & Berdahl, 1984)" + ) else: feedback.setProgressText("Isotropic sky") anisotropic_sky = 0 # % Ts parameterisation maps - if landcover == 1.: + if landcover == 1.0: if folderWallScheme: unique_landcover = np.unique(lcgrid) unique_landcover = unique_landcover[unique_landcover < 100] - if np.max(unique_landcover) > 7 or np.min(unique_landcover) < 1: - feedback.pushWarning("The land cover grid includes integer values higher (or lower) than standard UMEP-formatted. " \ - "Land cover grid (should be integer between 1 and 7). If other LC-classes should be included they also need to be included in landcoverclasses_2016a.txt") + if ( + np.max(unique_landcover) > 7 + or np.min(unique_landcover) < 1 + ): + feedback.pushWarning( + "The land cover grid includes integer values higher (or lower) than standard UMEP-formatted. " + "Land cover grid (should be integer between 1 and 7). If other LC-classes should be included they also need to be included in landcoverclasses_2016a.txt" + ) else: if np.max(lcgrid) > 7 or np.min(lcgrid) < 1: - feedback.pushWarning("The land cover grid includes integer values higher (or lower) than standard UMEP-formatted. " \ - "Land cover grid (should be integer between 1 and 7). If other LC-classes should be included they also need to be included in landcoverclasses_2016a.txt") + feedback.pushWarning( + "The land cover grid includes integer values higher (or lower) than standard UMEP-formatted. " + "Land cover grid (should be integer between 1 and 7). If other LC-classes should be included they also need to be included in landcoverclasses_2016a.txt" + ) if np.where(lcgrid) == 3 or np.where(lcgrid) == 4: - raise QgsProcessingException("The land cover grid includes values (decidouos and/or conifer) not appropriate for SOLWEIG-formatted land cover grid (should not include 3 or 4).") + raise QgsProcessingException( + "The land cover grid includes values (decidouos and/or conifer) not appropriate for SOLWEIG-formatted land cover grid (should not include 3 or 4)." + ) # Import data for wall temperature parameterization if folderWallScheme: wallScheme = 1 - feedback.setProgressText("Wall surface temperature scheme activated") + feedback.setProgressText( + "Wall surface temperature scheme activated" + ) # Use wall of interest if woilyr is not None: woi_file = str(woilyr.dataProvider().dataSourceUri()) - woi_field = self.parameterAsString(parameters, self.WOI_FIELD, context) + woi_field = self.parameterAsString( + parameters, self.WOI_FIELD, context + ) else: - woi_file = '' - woi_field = '' + woi_file = "" + woi_field = "" else: wallScheme = 0 - woi_file = '' - woi_field = '' + woi_file = "" + woi_field = "" feedback.setProgressText("Writing settings to solweigconfig.ini") # Code to dump setting into configfile configDict = { - 'output_dir': outputDir, - 'working_dir': self.temp_dir, - 'para_json_path': outputDir + '/solweig_parameters.json', - 'filepath_dsm': filepath_dsm, - 'filepath_cdsm': filePath_cdsm, #vegdsm.dataProvider().dataSourceUri() if vegdsm.dataProvider().dataSourceUri() is not None else '', # if vegdsm is None: str(vegdsm.dataProvider().dataSourceUri()), - 'filepath_tdsm': filePath_tdsm, #str(vegdsm.dataProvider().dataSourceUri()) if vegdsm is None else '', #str(vegdsm2.dataProvider().dataSourceUri()), - 'filepath_dem': filepath_dem, - 'filepath_lc': filepath_lc, # 'C:/Users/xlinfr/Documents/PythonScripts/SOLWEIG/SOLWEIGdata/landcover.tif', - 'filepath_wh': filepath_wh, #'C:\\Users\\xlinfr\\Desktop\\SOLWEIGdata\\wallheight.tif', - 'filepath_wa': filepath_wa, #'C:\\Users\\xlinfr\\Desktop\\SOLWEIGdata\\wallaspect.tif', - 'input_svf': inputSVF, - 'input_aniso': folderPathPerez, # 'C:\\Users\\xlinfr\\Desktop\\SOLWEIGdata\\shadowmats.npz', - 'poi_file': poi_file, - 'poi_field': poi_field, - 'input_wall': folderWallScheme, - 'woi_file': woi_file, - 'woi_field': woi_field, - 'input_met': inputMet, - 'standalone': '0', # used in standalone - 'onlyglobal': int(onlyglobal), - 'usevegdem': int(usevegdem), - 'conifer_bool': int(conifer_bool), - 'cyl': int(cyl), - 'posture': solweig_parameters['Tmrt_params']['Value']['posture'], - 'lat': lat, - 'lon': lon, - 'utc': int(utc), - 'scale': scale, - 'useepwfile': '0', # used in standalone - 'landcover': int(landcover), - 'demforbuild': int(demforbuild), - 'aniso': int(anisotropic_sky), - 'wallscheme': wallScheme, - 'walltype': wall_type, #'Brick_wall', #:TODO - 'outputtmrt': int(outputTmrt), - 'outputkup': int(outputKup), - 'outputkdown': int(outputKdown), - 'outputlup': int(outputLup), - 'outputldown': int(outputLdown), - 'outputsh': int(outputSh), - 'savebuild': int(saveBuild), - 'outputkdiff': int(outputKdiff), - 'outputtreeplanter': int(outputTreeplanter), - 'wallnetcdf': int(wallNetCDF), - 'date1': '2018,5,1,0', # used in standalone - 'date2': '2018,8,1,18' # used in standalone - } + "output_dir": outputDir, + "working_dir": self.temp_dir, + "para_json_path": outputDir + "/solweig_parameters.json", + "filepath_dsm": filepath_dsm, + "filepath_cdsm": ( + filePath_cdsm + ), # vegdsm.dataProvider().dataSourceUri() if vegdsm.dataProvider().dataSourceUri() is not None else '', # if vegdsm is None: str(vegdsm.dataProvider().dataSourceUri()), + "filepath_tdsm": ( + filePath_tdsm + ), # str(vegdsm.dataProvider().dataSourceUri()) if vegdsm is None else '', #str(vegdsm2.dataProvider().dataSourceUri()), + "filepath_dem": filepath_dem, + "filepath_lc": ( + filepath_lc + ), # 'C:/Users/xlinfr/Documents/PythonScripts/SOLWEIG/SOLWEIGdata/landcover.tif', + "filepath_wh": ( + filepath_wh + ), #'C:\\Users\\xlinfr\\Desktop\\SOLWEIGdata\\wallheight.tif', + "filepath_wa": ( + filepath_wa + ), #'C:\\Users\\xlinfr\\Desktop\\SOLWEIGdata\\wallaspect.tif', + "input_svf": inputSVF, + "input_aniso": ( + folderPathPerez + ), # 'C:\\Users\\xlinfr\\Desktop\\SOLWEIGdata\\shadowmats.npz', + "poi_file": poi_file, + "poi_field": poi_field, + "input_wall": folderWallScheme, + "woi_file": woi_file, + "woi_field": woi_field, + "input_met": inputMet, + "standalone": "0", # used in standalone + "onlyglobal": int(onlyglobal), + "usevegdem": int(usevegdem), + "conifer_bool": int(conifer_bool), + "cyl": int(cyl), + "posture": solweig_parameters["Tmrt_params"]["Value"]["posture"], + "lat": lat, + "lon": lon, + "utc": int(utc), + "scale": scale, + "useepwfile": "0", # used in standalone + "landcover": int(landcover), + "demforbuild": int(demforbuild), + "aniso": int(anisotropic_sky), + "wallscheme": wallScheme, + "walltype": wall_type, #'Brick_wall', #:TODO + "groundmodel": 1, + "input_surf": "", + "outputtmrt": int(outputTmrt), + "outputkup": int(outputKup), + "outputkdown": int(outputKdown), + "outputlup": int(outputLup), + "outputldown": int(outputLdown), + "outputsh": int(outputSh), + "savebuild": int(saveBuild), + "outputkdiff": int(outputKdiff), + "outputtreeplanter": int(outputTreeplanter), + "wallnetcdf": int(wallNetCDF), + "date1": "2018,5,1,0", # used in standalone + "date2": "2018,8,1,18", # used in standalone + } # Save configfile - write_solweig_config(configDict, outputDir + '/configsolweig.ini') + write_solweig_config(configDict, outputDir + "/configsolweig.ini") # Save solweig_parameters in output folder - with open(outputDir + '/solweig_parameters.json', 'w') as f: + with open(outputDir + "/solweig_parameters.json", "w") as f: json.dump(solweig_parameters, f, indent=2) # Main function feedback.setProgressText("Executing main model") - sr.solweig_run(outputDir + '/configsolweig.ini', feedback) + sr.solweig_run(outputDir + "/configsolweig.ini", feedback) feedback.setProgressText("SOLWEIG: Model calculation finished.") return {self.OUTPUT_DIR: outputDir} - + def name(self): """ Returns the algorithm name, used for identifying the algorithm. This @@ -748,14 +1256,14 @@ def name(self): lowercase alphanumeric characters only and no spaces or other formatting characters. """ - return 'Outdoor Thermal Comfort: SOLWEIG' + return "Outdoor Thermal Comfort: SOLWEIG" def displayName(self): """ Returns the translated algorithm name, which should be used for any user-visible display of the algorithm name. """ - return self.tr('Outdoor Thermal Comfort: SOLWEIG v2025a') + return self.tr("Outdoor Thermal Comfort: SOLWEIG v2026a") def group(self): """ @@ -772,40 +1280,43 @@ def groupId(self): contain lowercase alphanumeric characters only and no spaces or other formatting characters. """ - return 'Processor' + return "Processor" def shortHelpString(self): - return self.tr('SOLWEIG (v2025a) is a model which can be used to estimate spatial variations of 3D radiation fluxes and ' - 'mean radiant temperature (Tmrt) in complex urban settings. The SOLWEIG model originally followed the ' - 'approach commonly adopted to observe Tmrt, with shortwave and longwave radiation fluxes from ' - 'six directions being individually calculated to derive Tmrt. The model can also consider a person as a ' - 'standing cylinder. The model requires a limited number ' - 'of inputs, such as global shortwave radiation, air temperature, relative ' - 'humidity, urban geometry and geographical information (latitude, longitude and elevation). ' - 'Additional vegetation and ground cover information can also be used to imporove the estimation of Tmrt.\n' - '\n' - 'Tools to generate sky view factors, wall height and aspect etc. is available in the pre-processing section in UMEP\n' - '\n' - - 'NOTE:\n' - '- Anisotrophic sky models for long- and diffuse shortwave are automatically activated when the *.npz file ' - 'for shadow maps are included.\n' - '- Wall scheme model is automatically activated when the .npz-file for wall voxels are included. This will ' - 'slow down the model consideraby as SOLWEIG become a near 3D-model.\n' - '\n' - '------------\n' - '\n' - 'Full manual available via the Help-button.') + return self.tr( + "SOLWEIG (v2025a) is a model which can be used to estimate spatial variations of 3D radiation fluxes and " + "mean radiant temperature (Tmrt) in complex urban settings. The SOLWEIG model originally followed the " + "approach commonly adopted to observe Tmrt, with shortwave and longwave radiation fluxes from " + "six directions being individually calculated to derive Tmrt. The model can also consider a person as a " + "standing cylinder. The model requires a limited number " + "of inputs, such as global shortwave radiation, air temperature, relative " + "humidity, urban geometry and geographical information (latitude, longitude and elevation). " + "Additional vegetation and ground cover information can also be used to imporove the estimation of Tmrt.\n" + "\n" + "Tools to generate sky view factors, wall height and aspect etc. is available in the pre-processing section in UMEP\n" + "\n" + "NOTE:\n" + "- Anisotrophic sky models for long- and diffuse shortwave are automatically activated when the *.npz file " + "for shadow maps are included.\n" + "- Wall scheme model is automatically activated when the .npz-file for wall voxels are included. This will " + "slow down the model consideraby as SOLWEIG become a near 3D-model.\n" + "\n" + "------------\n" + "\n" + "Full manual available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/processor/Outdoor%20Thermal%20Comfort%20SOLWEIG.html" return url def tr(self, string): - return QCoreApplication.translate('Processing', string) + return QCoreApplication.translate("Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/icon_solweig.png") return icon diff --git a/processor/solweig_algorithm_old.py b/processor/solweig_algorithm_old.py index 5168b15..cb5047d 100644 --- a/processor/solweig_algorithm_old.py +++ b/processor/solweig_algorithm_old.py @@ -22,28 +22,30 @@ ***************************************************************************/ """ -__author__ = 'Fredrik Lindberg' -__date__ = '2020-04-02' -__copyright__ = '(C) 2020 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2020-04-02" +__copyright__ = "(C) 2020 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QVariant, QDate, QTime, Qt from qgis.PyQt.QtWidgets import QDateEdit, QTimeEdit -from qgis.core import (QgsProcessing, - QgsProcessingAlgorithm, - QgsProcessingParameterBoolean, - QgsProcessingParameterNumber, - QgsProcessingParameterFolderDestination, - QgsProcessingParameterFile, - QgsProcessingException, - QgsProcessingParameterDefinition, - QgsProcessingParameterEnum, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterField, - QgsProcessingParameterRasterLayer) +from qgis.core import ( + QgsProcessing, + QgsProcessingAlgorithm, + QgsProcessingParameterBoolean, + QgsProcessingParameterNumber, + QgsProcessingParameterFolderDestination, + QgsProcessingParameterFile, + QgsProcessingException, + QgsProcessingParameterDefinition, + QgsProcessingParameterEnum, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterField, + QgsProcessingParameterRasterLayer, +) from processing.gui.wrappers import WidgetWrapper import numpy as np import pandas as pd @@ -56,9 +58,15 @@ from ..util.misc import get_ders, saveraster import zipfile from osgeo.gdalconst import * -from ..util.SEBESOLWEIGCommonFiles.Solweig_v2015_metdata_noload import Solweig_2015a_metdata_noload -from ..util.SEBESOLWEIGCommonFiles import Solweig_v2015_metdata_noload as metload -from ..util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import clearnessindex_2013b +from ..util.SEBESOLWEIGCommonFiles.Solweig_v2015_metdata_noload import ( + Solweig_2015a_metdata_noload, +) +from ..util.SEBESOLWEIGCommonFiles import ( + Solweig_v2015_metdata_noload as metload, +) +from ..util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import ( + clearnessindex_2013b, +) from ..functions.SOLWEIGpython.Tgmaps_v1 import Tgmaps_v1 from ..functions.SOLWEIGpython import Solweig_2022a_calc_forprocessing as so from ..functions.SOLWEIGpython import WriteMetadataSOLWEIG @@ -81,6 +89,7 @@ # import datetime + class ProcessingSOLWEIGAlgorithm(QgsProcessingAlgorithm): """ This algorithm is a processing version of SOLWEIG @@ -89,271 +98,631 @@ class ProcessingSOLWEIGAlgorithm(QgsProcessingAlgorithm): # Constants used to refer to parameters and outputs. They will be # used when calling the algorithm from another algorithm, or when # calling from the QGIS console. - - #Spatial data - INPUT_DSM = 'INPUT_DSM' - INPUT_SVF = 'INPUT_SVF' - INPUT_CDSM = 'INPUT_CDSM' - INPUT_TDSM = 'INPUT_TDSM' - INPUT_HEIGHT = 'INPUT_HEIGHT' - INPUT_ASPECT = 'INPUT_ASPECT' - TRANS_VEG = 'TRANS_VEG' - LEAF_START = 'LEAF_START' - LEAF_END = 'LEAF_END' - CONIFER_TREES = 'CONIFER_TREES' - INPUT_THEIGHT = 'INPUT_THEIGHT' - INPUT_LC = 'INPUT_LC' - USE_LC_BUILD = 'USE_LC_BUILD' - INPUT_DEM = 'INPUT_DEM' - SAVE_BUILD = 'SAVE_BUILD' - INPUT_ANISO = 'INPUT_ANISO' - INPUT_WALLSCHEME = 'INPUT_WALLSCHEME' - WALLTEMP_NETCDF = 'WALLTEMP_NETCDF' - - #Enivornmental parameters - ALBEDO_WALLS = 'ALBEDO_WALLS' - ALBEDO_GROUND = 'ALBEDO_GROUND' - EMIS_WALLS = 'EMIS_WALLS' - EMIS_GROUND = 'EMIS_GROUND' - WALL_TYPE = 'WALL_TYPE' - - #Tmrt parameters - ABS_S = 'ABS_S' - ABS_L = 'ABS_L' - POSTURE = 'POSTURE' - - #Meteorology - INPUT_MET = 'INPUTMET' - ONLYGLOBAL = 'ONLYGLOBAL' - UTC = 'UTC' - - #PET parameters - AGE = 'AGE' - ACTIVITY = 'ACTIVITY' - CLO = 'CLO' - WEIGHT = 'WEIGHT' - HEIGHT = 'HEIGHT' - SEX = 'SEX' - SENSOR_HEIGHT = 'SENSOR_HEIGHT' - - #Optional settings - WOI_FILE = 'WOI_FILE' - WOI_FIELD = 'WOI_FIELD' - # POI = 'POI' - POI_FILE = 'POI_FILE' - POI_FIELD = 'POI_FIELD' - CYL = 'CYL' - - #Output - OUTPUT_DIR = 'OUTPUT_DIR' - OUTPUT_TMRT = 'OUTPUT_TMRT' - OUTPUT_LUP = 'OUTPUT_LUP' - OUTPUT_KUP = 'OUTPUT_KUP' - OUTPUT_KDOWN = 'OUTPUT_KDOWN' - OUTPUT_LDOWN = 'OUTPUT_LDOWN' - OUTPUT_SH = 'OUTPUT_SH' - OUTPUT_TREEPLANTER = 'OUTPUT_TREEPLANTER' + # Spatial data + INPUT_DSM = "INPUT_DSM" + INPUT_SVF = "INPUT_SVF" + INPUT_CDSM = "INPUT_CDSM" + INPUT_TDSM = "INPUT_TDSM" + INPUT_HEIGHT = "INPUT_HEIGHT" + INPUT_ASPECT = "INPUT_ASPECT" + TRANS_VEG = "TRANS_VEG" + LEAF_START = "LEAF_START" + LEAF_END = "LEAF_END" + CONIFER_TREES = "CONIFER_TREES" + INPUT_THEIGHT = "INPUT_THEIGHT" + INPUT_LC = "INPUT_LC" + USE_LC_BUILD = "USE_LC_BUILD" + INPUT_DEM = "INPUT_DEM" + SAVE_BUILD = "SAVE_BUILD" + INPUT_ANISO = "INPUT_ANISO" + INPUT_WALLSCHEME = "INPUT_WALLSCHEME" + WALLTEMP_NETCDF = "WALLTEMP_NETCDF" + + # Enivornmental parameters + ALBEDO_WALLS = "ALBEDO_WALLS" + ALBEDO_GROUND = "ALBEDO_GROUND" + EMIS_WALLS = "EMIS_WALLS" + EMIS_GROUND = "EMIS_GROUND" + WALL_TYPE = "WALL_TYPE" + + # Tmrt parameters + ABS_S = "ABS_S" + ABS_L = "ABS_L" + POSTURE = "POSTURE" + + # Meteorology + INPUT_MET = "INPUTMET" + ONLYGLOBAL = "ONLYGLOBAL" + UTC = "UTC" + + # PET parameters + AGE = "AGE" + ACTIVITY = "ACTIVITY" + CLO = "CLO" + WEIGHT = "WEIGHT" + HEIGHT = "HEIGHT" + SEX = "SEX" + SENSOR_HEIGHT = "SENSOR_HEIGHT" + + # Optional settings + WOI_FILE = "WOI_FILE" + WOI_FIELD = "WOI_FIELD" + # POI = 'POI' + POI_FILE = "POI_FILE" + POI_FIELD = "POI_FIELD" + CYL = "CYL" + + # Output + OUTPUT_DIR = "OUTPUT_DIR" + OUTPUT_TMRT = "OUTPUT_TMRT" + OUTPUT_LUP = "OUTPUT_LUP" + OUTPUT_KUP = "OUTPUT_KUP" + OUTPUT_KDOWN = "OUTPUT_KDOWN" + OUTPUT_LDOWN = "OUTPUT_LDOWN" + OUTPUT_SH = "OUTPUT_SH" + OUTPUT_TREEPLANTER = "OUTPUT_TREEPLANTER" def initAlgorithm(self, config): - #spatial - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_DSM, - self.tr('Building and ground Digital Surface Model (DSM)'), None, optional=False)) - self.addParameter(QgsProcessingParameterFile(self.INPUT_SVF, - self.tr('Sky View Factor grids (.zip)'), extension='zip')) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_HEIGHT, - self.tr('Wall height raster'), '', optional=False)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_ASPECT, - self.tr('Wall aspect raster'), '', optional=False)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_CDSM, - self.tr('Vegetation Canopy DSM'), '', optional=True)) - self.addParameter(QgsProcessingParameterNumber(self.TRANS_VEG, - self.tr('Transmissivity of light through vegetation (%):'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(3), True, minValue=0, maxValue=100)) - - self.addParameter(QgsProcessingParameterNumber(self.LEAF_START, - self.tr('First day of year with leaves on trees (if deciduous)'), QgsProcessingParameterNumber.Type.Integer, - QVariant(97), False, minValue=0, maxValue=366)) - - self.addParameter(QgsProcessingParameterNumber(self.LEAF_END, - self.tr('Last day of year with leaves on trees (if deciduous)'), QgsProcessingParameterNumber.Type.Integer, - QVariant(300), False, minValue=0, maxValue=366)) - - self.addParameter(QgsProcessingParameterBoolean(self.CONIFER_TREES, - self.tr("Coniferous trees (deciduous default)"), defaultValue=False)) - - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_TDSM, - self.tr('Vegetation Trunk-zone DSM'), '', optional=True)) - self.addParameter(QgsProcessingParameterNumber(self.INPUT_THEIGHT, - self.tr("Trunk zone height (percent of Canopy Height). Used if no Vegetation Trunk-zone DSM is loaded"), - QgsProcessingParameterNumber.Type.Double, - QVariant(25.0), optional=True, minValue=0.1, maxValue=99.9)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_LC, - self.tr('UMEP land cover grid'), '', optional=True)) - self.addParameter(QgsProcessingParameterBoolean(self.USE_LC_BUILD, - self.tr("Use land cover grid to derive building grid"), defaultValue=False, optional=True)) - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_DEM, - self.tr('Digital Elevation Model (DEM)'), '', optional=True)) - self.addParameter(QgsProcessingParameterBoolean(self.SAVE_BUILD, - self.tr("Save generated building grid"), defaultValue=False, optional=True)) - self.addParameter(QgsProcessingParameterFile(self.INPUT_ANISO, - self.tr('Shadow maps used for anisotropic model for sky diffuse and longwave radiation (.npz)'), extension='npz', optional=True)) - self.addParameter(QgsProcessingParameterFile(self.INPUT_WALLSCHEME, - self.tr('Voxel data for wall temperature parameterization (.npz)'), extension='npz', optional=True)) - self.addParameter(QgsProcessingParameterBoolean(self.WALLTEMP_NETCDF, - self.tr("Save wall temperatures as NetCDF"), defaultValue=False, optional=True)) - - #Environmental parameters + # spatial + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_DSM, + self.tr("Building and ground Digital Surface Model (DSM)"), + None, + optional=False, + ) + ) + self.addParameter( + QgsProcessingParameterFile( + self.INPUT_SVF, + self.tr("Sky View Factor grids (.zip)"), + extension="zip", + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_HEIGHT, + self.tr("Wall height raster"), + "", + optional=False, + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_ASPECT, + self.tr("Wall aspect raster"), + "", + optional=False, + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_CDSM, + self.tr("Vegetation Canopy DSM"), + "", + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.TRANS_VEG, + self.tr("Transmissivity of light through vegetation (%):"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(3), + True, + minValue=0, + maxValue=100, + ) + ) + + self.addParameter( + QgsProcessingParameterNumber( + self.LEAF_START, + self.tr( + "First day of year with leaves on trees (if deciduous)" + ), + QgsProcessingParameterNumber.Type.Integer, + QVariant(97), + False, + minValue=0, + maxValue=366, + ) + ) + + self.addParameter( + QgsProcessingParameterNumber( + self.LEAF_END, + self.tr( + "Last day of year with leaves on trees (if deciduous)" + ), + QgsProcessingParameterNumber.Type.Integer, + QVariant(300), + False, + minValue=0, + maxValue=366, + ) + ) + + self.addParameter( + QgsProcessingParameterBoolean( + self.CONIFER_TREES, + self.tr("Coniferous trees (deciduous default)"), + defaultValue=False, + ) + ) + + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_TDSM, + self.tr("Vegetation Trunk-zone DSM"), + "", + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.INPUT_THEIGHT, + self.tr( + "Trunk zone height (percent of Canopy Height). Used if no Vegetation Trunk-zone DSM is loaded" + ), + QgsProcessingParameterNumber.Type.Double, + QVariant(25.0), + optional=True, + minValue=0.1, + maxValue=99.9, + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_LC, + self.tr("UMEP land cover grid"), + "", + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.USE_LC_BUILD, + self.tr("Use land cover grid to derive building grid"), + defaultValue=False, + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterRasterLayer( + self.INPUT_DEM, + self.tr("Digital Elevation Model (DEM)"), + "", + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.SAVE_BUILD, + self.tr("Save generated building grid"), + defaultValue=False, + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterFile( + self.INPUT_ANISO, + self.tr( + "Shadow maps used for anisotropic model for sky diffuse and longwave radiation (.npz)" + ), + extension="npz", + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterFile( + self.INPUT_WALLSCHEME, + self.tr( + "Voxel data for wall temperature parameterization (.npz)" + ), + extension="npz", + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.WALLTEMP_NETCDF, + self.tr("Save wall temperatures as NetCDF"), + defaultValue=False, + optional=True, + ) + ) + + # Environmental parameters # self.addParameter(QgsProcessingParameterNumber(self.EFFUS_WALL, # self.tr('Wall type (only with wall scheme)'), QgsProcessingParameterNumber.Integer, # QVariant(1065), False, minValue=0, maxValue=20000)) - self.wallType = ((self.tr('Brick'), '0'), - (self.tr('Concrete'), '1'), - (self.tr('Wood'), '2')) - self.addParameter(QgsProcessingParameterEnum(self.WALL_TYPE, - self.tr('Wall type (only with wall scheme)'), - options=[i[0] for i in self.wallType], - defaultValue=0)) - self.addParameter(QgsProcessingParameterNumber(self.ALBEDO_WALLS, - self.tr('Albedo (walls)'), QgsProcessingParameterNumber.Type.Double, - QVariant(0.20), False, minValue=0, maxValue=1)) - self.addParameter(QgsProcessingParameterNumber(self.ALBEDO_GROUND, - self.tr('Albedo (ground)'), QgsProcessingParameterNumber.Type.Double, - QVariant(0.15), False, minValue=0, maxValue=1)) - self.addParameter(QgsProcessingParameterNumber(self.EMIS_WALLS, - self.tr('Emissivity (walls)'), QgsProcessingParameterNumber.Type.Double, - QVariant(0.90), False, minValue=0, maxValue=1)) - self.addParameter(QgsProcessingParameterNumber(self.EMIS_GROUND, - self.tr('Emissivity (ground)'), QgsProcessingParameterNumber.Type.Double, - QVariant(0.95), False, minValue=0, maxValue=1)) - - #Tmrt parameters - self.addParameter(QgsProcessingParameterNumber(self.ABS_S, - self.tr('Absorption of shortwave radiation of human body'), QgsProcessingParameterNumber.Type.Double, - QVariant(0.70), False, minValue=0, maxValue=1)) - self.addParameter(QgsProcessingParameterNumber(self.ABS_L, - self.tr('Absorption of longwave radiation of human body'), QgsProcessingParameterNumber.Type.Double, - QVariant(0.95), False, minValue=0, maxValue=1)) - self.addParameter(QgsProcessingParameterEnum( - self.POSTURE, self.tr('Posture of human body'), ['Standing', 'Sitting'], defaultValue=0)) - self.addParameter(QgsProcessingParameterBoolean(self.CYL, - self.tr("Consider human as cylinder instead of box"), defaultValue=True)) - - #Meteorology - self.addParameter(QgsProcessingParameterFile(self.INPUT_MET, - self.tr('Input meteorological file (.txt)'), extension='txt')) - self.addParameter(QgsProcessingParameterBoolean(self.ONLYGLOBAL, - self.tr("Estimate diffuse and direct shortwave radiation from global radiation"), defaultValue=False)) - self.addParameter(QgsProcessingParameterNumber(self.UTC, - self.tr('Coordinated Universal Time (UTC) '), - QgsProcessingParameterNumber.Type.Integer, - QVariant(0), False, minValue=-12, maxValue=12)) + self.wallType = ( + (self.tr("Brick"), "0"), + (self.tr("Concrete"), "1"), + (self.tr("Wood"), "2"), + ) + self.addParameter( + QgsProcessingParameterEnum( + self.WALL_TYPE, + self.tr("Wall type (only with wall scheme)"), + options=[i[0] for i in self.wallType], + defaultValue=0, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.ALBEDO_WALLS, + self.tr("Albedo (walls)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(0.20), + False, + minValue=0, + maxValue=1, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.ALBEDO_GROUND, + self.tr("Albedo (ground)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(0.15), + False, + minValue=0, + maxValue=1, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.EMIS_WALLS, + self.tr("Emissivity (walls)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(0.90), + False, + minValue=0, + maxValue=1, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.EMIS_GROUND, + self.tr("Emissivity (ground)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(0.95), + False, + minValue=0, + maxValue=1, + ) + ) + + # Tmrt parameters + self.addParameter( + QgsProcessingParameterNumber( + self.ABS_S, + self.tr("Absorption of shortwave radiation of human body"), + QgsProcessingParameterNumber.Type.Double, + QVariant(0.70), + False, + minValue=0, + maxValue=1, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.ABS_L, + self.tr("Absorption of longwave radiation of human body"), + QgsProcessingParameterNumber.Type.Double, + QVariant(0.95), + False, + minValue=0, + maxValue=1, + ) + ) + self.addParameter( + QgsProcessingParameterEnum( + self.POSTURE, + self.tr("Posture of human body"), + ["Standing", "Sitting"], + defaultValue=0, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.CYL, + self.tr("Consider human as cylinder instead of box"), + defaultValue=True, + ) + ) + + # Meteorology + self.addParameter( + QgsProcessingParameterFile( + self.INPUT_MET, + self.tr("Input meteorological file (.txt)"), + extension="txt", + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.ONLYGLOBAL, + self.tr( + "Estimate diffuse and direct shortwave radiation from global radiation" + ), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.UTC, + self.tr("Coordinated Universal Time (UTC) "), + QgsProcessingParameterNumber.Type.Integer, + QVariant(0), + False, + minValue=-12, + maxValue=12, + ) + ) # ADVANCED PARAMETERS - woifile = QgsProcessingParameterFeatureSource(self.WOI_FILE, - self.tr('Vector point file including Wall of Interest(s) for output with wall surface temperatures'), [QgsProcessing.SourceType.TypeVectorPoint], optional=True) - woifile.setFlags(woifile.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + woifile = QgsProcessingParameterFeatureSource( + self.WOI_FILE, + self.tr( + "Vector point file including Wall of Interest(s) for output with wall surface temperatures" + ), + [QgsProcessing.SourceType.TypeVectorPoint], + optional=True, + ) + woifile.setFlags( + woifile.flags() + | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(woifile) - woi_field = QgsProcessingParameterField(self.WOI_FIELD, - self.tr('Wall ID field'),'', self.WOI_FILE, QgsProcessingParameterField.DataType.Numeric, optional=True) - woi_field.setFlags(woi_field.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) - self.addParameter(woi_field) - - #POIs for thermal comfort estimations + woi_field = QgsProcessingParameterField( + self.WOI_FIELD, + self.tr("Wall ID field"), + "", + self.WOI_FILE, + QgsProcessingParameterField.DataType.Numeric, + optional=True, + ) + woi_field.setFlags( + woi_field.flags() + | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) + self.addParameter(woi_field) + + # POIs for thermal comfort estimations # poi = QgsProcessingParameterBoolean(self.POI, # self.tr("Include Point of Interest(s) for thermal comfort calculations (PET and UTCI)"), defaultValue=False) # poi.setFlags(poi.flags() | QgsProcessingParameterDefinition.FlagAdvanced) # self.addParameter(poi) - poifile = QgsProcessingParameterFeatureSource(self.POI_FILE, - self.tr('Vector point file including Point of Interest(s) for thermal comfort calculations (PET and UTCI)'), [QgsProcessing.SourceType.TypeVectorPoint], optional=True) - poifile.setFlags(poifile.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + poifile = QgsProcessingParameterFeatureSource( + self.POI_FILE, + self.tr( + "Vector point file including Point of Interest(s) for thermal comfort calculations (PET and UTCI)" + ), + [QgsProcessing.SourceType.TypeVectorPoint], + optional=True, + ) + poifile.setFlags( + poifile.flags() + | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(poifile) - poi_field = QgsProcessingParameterField(self.POI_FIELD, - self.tr('ID field'),'', self.POI_FILE, QgsProcessingParameterField.DataType.Numeric, optional=True) - poi_field.setFlags(poi_field.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + poi_field = QgsProcessingParameterField( + self.POI_FIELD, + self.tr("ID field"), + "", + self.POI_FILE, + QgsProcessingParameterField.DataType.Numeric, + optional=True, + ) + poi_field.setFlags( + poi_field.flags() + | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(poi_field) - #PET parameters - age = QgsProcessingParameterNumber(self.AGE, self.tr('Age (yy)'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(35), optional=True, minValue=0, maxValue=120) - age.setFlags(age.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + # PET parameters + age = QgsProcessingParameterNumber( + self.AGE, + self.tr("Age (yy)"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(35), + optional=True, + minValue=0, + maxValue=120, + ) + age.setFlags( + age.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(age) - act = QgsProcessingParameterNumber(self.ACTIVITY, self.tr('Activity (W)'), - QgsProcessingParameterNumber.Type.Double, - QVariant(80), optional=True, minValue=0, maxValue=1000) - act.setFlags(act.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + act = QgsProcessingParameterNumber( + self.ACTIVITY, + self.tr("Activity (W)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(80), + optional=True, + minValue=0, + maxValue=1000, + ) + act.setFlags( + act.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(act) - clo = QgsProcessingParameterNumber(self.CLO, self.tr('Clothing (clo)'), - QgsProcessingParameterNumber.Type.Double, - QVariant(0.9), optional=True, minValue=0, maxValue=10) - clo.setFlags(clo.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + clo = QgsProcessingParameterNumber( + self.CLO, + self.tr("Clothing (clo)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(0.9), + optional=True, + minValue=0, + maxValue=10, + ) + clo.setFlags( + clo.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(clo) - wei = QgsProcessingParameterNumber(self.WEIGHT, self.tr('Weight (kg)'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(75), optional=True, minValue=0, maxValue=500) - wei.setFlags(wei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + wei = QgsProcessingParameterNumber( + self.WEIGHT, + self.tr("Weight (kg)"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(75), + optional=True, + minValue=0, + maxValue=500, + ) + wei.setFlags( + wei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(wei) - hei = QgsProcessingParameterNumber(self.HEIGHT, self.tr('Height (cm)'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(180), optional=True, minValue=0, maxValue=250) - hei.setFlags(hei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + hei = QgsProcessingParameterNumber( + self.HEIGHT, + self.tr("Height (cm)"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(180), + optional=True, + minValue=0, + maxValue=250, + ) + hei.setFlags( + hei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(hei) sex = QgsProcessingParameterEnum( - self.SEX, self.tr('Sex'), ['Male', 'Female'], optional=True, defaultValue=0) - sex.setFlags(sex.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + self.SEX, + self.tr("Sex"), + ["Male", "Female"], + optional=True, + defaultValue=0, + ) + sex.setFlags( + sex.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(sex) - shei = QgsProcessingParameterNumber(self.SENSOR_HEIGHT, self.tr('Height of wind sensor (m agl)'), - QgsProcessingParameterNumber.Type.Double, - QVariant(10), optional=True, minValue=0, maxValue=250) - shei.setFlags(shei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + shei = QgsProcessingParameterNumber( + self.SENSOR_HEIGHT, + self.tr("Height of wind sensor (m agl)"), + QgsProcessingParameterNumber.Type.Double, + QVariant(10), + optional=True, + minValue=0, + maxValue=250, + ) + shei.setFlags( + shei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(shei) - #OUTPUT - self.addParameter(QgsProcessingParameterBoolean(self.OUTPUT_TMRT, - self.tr("Save Mean Radiant Temperature raster(s)"), defaultValue=True)) - self.addParameter(QgsProcessingParameterBoolean(self.OUTPUT_KDOWN, - self.tr("Save Incoming shortwave radiation raster(s)"), defaultValue=False)) - self.addParameter(QgsProcessingParameterBoolean(self.OUTPUT_KUP, - self.tr("Save Outgoing shortwave radiation raster(s)"), defaultValue=False)) - self.addParameter(QgsProcessingParameterBoolean(self.OUTPUT_LDOWN, - self.tr("Save Incoming longwave radiation raster(s)"), defaultValue=False)) - self.addParameter(QgsProcessingParameterBoolean(self.OUTPUT_LUP, - self.tr("Save Outgoing longwave radiation raster(s)"), defaultValue=False)) - self.addParameter(QgsProcessingParameterBoolean(self.OUTPUT_SH, - self.tr("Save shadow raster(s)"), defaultValue=False)) - self.addParameter(QgsProcessingParameterBoolean(self.OUTPUT_TREEPLANTER, - self.tr("Save necessary raster(s) for the TreePlanter and Spatial TC tools"), defaultValue=False)) - self.addParameter(QgsProcessingParameterFolderDestination(self.OUTPUT_DIR, - 'Output folder')) + # OUTPUT + self.addParameter( + QgsProcessingParameterBoolean( + self.OUTPUT_TMRT, + self.tr("Save Mean Radiant Temperature raster(s)"), + defaultValue=True, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.OUTPUT_KDOWN, + self.tr("Save Incoming shortwave radiation raster(s)"), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.OUTPUT_KUP, + self.tr("Save Outgoing shortwave radiation raster(s)"), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.OUTPUT_LDOWN, + self.tr("Save Incoming longwave radiation raster(s)"), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.OUTPUT_LUP, + self.tr("Save Outgoing longwave radiation raster(s)"), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.OUTPUT_SH, + self.tr("Save shadow raster(s)"), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.OUTPUT_TREEPLANTER, + self.tr( + "Save necessary raster(s) for the TreePlanter and Spatial TC tools" + ), + defaultValue=False, + ) + ) + self.addParameter( + QgsProcessingParameterFolderDestination( + self.OUTPUT_DIR, "Output folder" + ) + ) self.plugin_dir = os.path.dirname(__file__) - self.temp_dir = os.path.dirname(self.plugin_dir) + '/temp' + self.temp_dir = os.path.dirname(self.plugin_dir) + "/temp" def processAlgorithm(self, parameters, context, feedback): - np.seterr(divide='ignore', invalid='ignore') - + np.seterr(divide="ignore", invalid="ignore") + # InputParameters - dsmlayer = self.parameterAsRasterLayer(parameters, self.INPUT_DSM, context) - transVeg = self.parameterAsDouble(parameters, self.TRANS_VEG, context) / 100. - firstdayleaf = self.parameterAsInt(parameters, self.LEAF_START, context) + dsmlayer = self.parameterAsRasterLayer( + parameters, self.INPUT_DSM, context + ) + transVeg = ( + self.parameterAsDouble(parameters, self.TRANS_VEG, context) / 100.0 + ) + firstdayleaf = self.parameterAsInt( + parameters, self.LEAF_START, context + ) lastdayleaf = self.parameterAsInt(parameters, self.LEAF_END, context) - conifer_bool = self.parameterAsBool(parameters, self.CONIFER_TREES, context) - vegdsm = self.parameterAsRasterLayer(parameters, self.INPUT_CDSM, context) - vegdsm2 = self.parameterAsRasterLayer(parameters, self.INPUT_TDSM, context) - lcgrid = self.parameterAsRasterLayer(parameters, self.INPUT_LC, context) - useLcBuild = self.parameterAsBool(parameters, self.USE_LC_BUILD, context) + conifer_bool = self.parameterAsBool( + parameters, self.CONIFER_TREES, context + ) + vegdsm = self.parameterAsRasterLayer( + parameters, self.INPUT_CDSM, context + ) + vegdsm2 = self.parameterAsRasterLayer( + parameters, self.INPUT_TDSM, context + ) + lcgrid = self.parameterAsRasterLayer( + parameters, self.INPUT_LC, context + ) + useLcBuild = self.parameterAsBool( + parameters, self.USE_LC_BUILD, context + ) dem = None inputSVF = self.parameterAsString(parameters, self.INPUT_SVF, context) - whlayer = self.parameterAsRasterLayer(parameters, self.INPUT_HEIGHT, context) - walayer = self.parameterAsRasterLayer(parameters, self.INPUT_ASPECT, context) - trunkr = self.parameterAsDouble(parameters, self.INPUT_THEIGHT, context) + whlayer = self.parameterAsRasterLayer( + parameters, self.INPUT_HEIGHT, context + ) + walayer = self.parameterAsRasterLayer( + parameters, self.INPUT_ASPECT, context + ) + trunkr = self.parameterAsDouble( + parameters, self.INPUT_THEIGHT, context + ) onlyglobal = self.parameterAsBool(parameters, self.ONLYGLOBAL, context) utc = self.parameterAsDouble(parameters, self.UTC, context) inputMet = self.parameterAsString(parameters, self.INPUT_MET, context) # usePOI = self.parameterAsBool(parameters, self.POI, context) - poilyr = self.parameterAsVectorLayer(parameters, self.POI_FILE, context) + poilyr = self.parameterAsVectorLayer( + parameters, self.POI_FILE, context + ) poi_field = None mbody = None ht = None @@ -364,14 +733,22 @@ def processAlgorithm(self, parameters, context, feedback): sensorheight = None saveBuild = self.parameterAsBool(parameters, self.SAVE_BUILD, context) demforbuild = 0 - folderPathPerez = self.parameterAsString(parameters, self.INPUT_ANISO, context) - folderWallScheme = self.parameterAsString(parameters, self.INPUT_WALLSCHEME, context) - wallNetCDF = self.parameterAsBool(parameters, self.WALLTEMP_NETCDF, context) + folderPathPerez = self.parameterAsString( + parameters, self.INPUT_ANISO, context + ) + folderWallScheme = self.parameterAsString( + parameters, self.INPUT_WALLSCHEME, context + ) + wallNetCDF = self.parameterAsBool( + parameters, self.WALLTEMP_NETCDF, context + ) poisxy = None poiname = None # Wall of interest - woilyr = self.parameterAsVectorLayer(parameters, self.WOI_FILE, context) + woilyr = self.parameterAsVectorLayer( + parameters, self.WOI_FILE, context + ) woi_field = None woisxy = None woiname = None @@ -380,7 +757,7 @@ def processAlgorithm(self, parameters, context, feedback): absK = self.parameterAsDouble(parameters, self.ABS_S, context) absL = self.parameterAsDouble(parameters, self.ABS_L, context) pos = self.parameterAsInt(parameters, self.POSTURE, context) - + if self.parameterAsBool(parameters, self.CYL, context): cyl = 1 else: @@ -397,24 +774,38 @@ def processAlgorithm(self, parameters, context, feedback): height = 0.75 Fcyl = 0.2 - albedo_b = self.parameterAsDouble(parameters, self.ALBEDO_WALLS, context) - albedo_g = self.parameterAsDouble(parameters, self.ALBEDO_GROUND, context) + albedo_b = self.parameterAsDouble( + parameters, self.ALBEDO_WALLS, context + ) + albedo_g = self.parameterAsDouble( + parameters, self.ALBEDO_GROUND, context + ) ewall = self.parameterAsDouble(parameters, self.EMIS_WALLS, context) eground = self.parameterAsDouble(parameters, self.EMIS_GROUND, context) - elvis = 0 # option removed 20200907 in processing UMEP + elvis = 0 # option removed 20200907 in processing UMEP # thermal_effusivity = self.parameterAsDouble(parameters, self.EFFUS_WALL, context) wall_type = None - outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) - outputTmrt = self.parameterAsBool(parameters, self.OUTPUT_TMRT, context) + outputDir = self.parameterAsString( + parameters, self.OUTPUT_DIR, context + ) + outputTmrt = self.parameterAsBool( + parameters, self.OUTPUT_TMRT, context + ) outputSh = self.parameterAsBool(parameters, self.OUTPUT_SH, context) outputKup = self.parameterAsBool(parameters, self.OUTPUT_KUP, context) - outputKdown = self.parameterAsBool(parameters, self.OUTPUT_KDOWN, context) + outputKdown = self.parameterAsBool( + parameters, self.OUTPUT_KDOWN, context + ) outputLup = self.parameterAsBool(parameters, self.OUTPUT_LUP, context) - outputLdown = self.parameterAsBool(parameters, self.OUTPUT_LDOWN, context) - outputTreeplanter = self.parameterAsBool(parameters, self.OUTPUT_TREEPLANTER, context) + outputLdown = self.parameterAsBool( + parameters, self.OUTPUT_LDOWN, context + ) + outputTreeplanter = self.parameterAsBool( + parameters, self.OUTPUT_TREEPLANTER, context + ) outputKdiff = False - #outputSstr = False + # outputSstr = False # If "Save necessary rasters for TreePlanter tool" is ticked, save the following raster for TreePlanter or Spatial TC if outputTreeplanter: @@ -426,29 +817,33 @@ def processAlgorithm(self, parameters, context, feedback): outputSh = True saveBuild = True outputKdiff = True - #outputSstr = True + # outputSstr = True - if parameters['OUTPUT_DIR'] == 'TEMPORARY_OUTPUT': + if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): os.mkdir(outputDir) # Load parameters for SOLWEIG for surface temperatures, etc - data_path = self.plugin_dir + '/parametersforsolweig.json' + data_path = self.plugin_dir + "/parametersforsolweig.json" with open(data_path, "r") as jsn: solweig_parameters = json.load(jsn) # Add GUI Tmrt settings to SOLWEIG parameter file - solweig_parameters['Tmrt_params']['Value']['absK'] = absK - solweig_parameters['Tmrt_params']['Value']['absL'] = absL + solweig_parameters["Tmrt_params"]["Value"]["absK"] = absK + solweig_parameters["Tmrt_params"]["Value"]["absL"] = absL if pos == 0: - solweig_parameters['Tmrt_params']['Value']['posture'] = 'Standing' + solweig_parameters["Tmrt_params"]["Value"]["posture"] = "Standing" else: - solweig_parameters['Tmrt_params']['Value']['posture'] = 'Sitting' + solweig_parameters["Tmrt_params"]["Value"]["posture"] = "Sitting" - solweig_parameters['Albedo']['Effective']['Value']['Cobble_stone_2014a'] = albedo_g - solweig_parameters['Albedo']['Effective']['Value']['Walls'] = albedo_b - solweig_parameters['Emissivity']['Value']['Cobble_stone_2014a'] = eground - solweig_parameters['Emissivity']['Value']['Walls'] = ewall + solweig_parameters["Albedo"]["Effective"]["Value"][ + "Cobble_stone_2014a" + ] = albedo_g + solweig_parameters["Albedo"]["Effective"]["Value"]["Walls"] = albedo_b + solweig_parameters["Emissivity"]["Value"][ + "Cobble_stone_2014a" + ] = eground + solweig_parameters["Emissivity"]["Value"]["Walls"] = ewall # Code from old plugin provider = dsmlayer.dataProvider() @@ -462,12 +857,16 @@ def processAlgorithm(self, parameters, context, feedback): # response to issue #85 nd = gdal_dsm.GetRasterBand(1).GetNoDataValue() - dsm[dsm == nd] = 0. + dsm[dsm == nd] = 0.0 # dsmcopy = np.copy(dsm) if dsm.min() < 0: dsmraise = np.abs(dsm.min()) dsm = dsm + dsmraise - feedback.setProgressText('Digital Surface Model (DSM) included negative values. DSM raised with ' + str(dsmraise) + 'm.') + feedback.setProgressText( + "Digital Surface Model (DSM) included negative values. DSM raised with " + + str(dsmraise) + + "m." + ) else: dsmraise = 0 @@ -496,16 +895,20 @@ def processAlgorithm(self, parameters, context, feedback): heightx = gdal_dsm.RasterYSize geotransform = gdal_dsm.GetGeoTransform() minx = geotransform[0] - miny = geotransform[3] + widthx * geotransform[4] + heightx * geotransform[5] + miny = ( + geotransform[3] + + widthx * geotransform[4] + + heightx * geotransform[5] + ) lonlat = transform.TransformPoint(minx, miny) gdalver = float(gdal.__version__[0]) - if gdalver == 3.: - lon = lonlat[1] #changed to gdal 3 - lat = lonlat[0] #changed to gdal 3 + if gdalver == 3.0: + lon = lonlat[1] # changed to gdal 3 + lat = lonlat[0] # changed to gdal 3 else: - lon = lonlat[0] #changed to gdal 2 - lat = lonlat[1] #changed to gdal 2 + lon = lonlat[0] # changed to gdal 2 + lat = lonlat[1] # changed to gdal 2 scale = 1 / geotransform[1] pixel_resolution = geotransform[1] @@ -513,8 +916,8 @@ def processAlgorithm(self, parameters, context, feedback): alt = np.median(dsm) if alt < 0: alt = 3 - feedback.setProgressText('Longitude derived from DSM: ' + str(lon)) - feedback.setProgressText('Latitude derived from DSM: ' + str(lat)) + feedback.setProgressText("Longitude derived from DSM: " + str(lon)) + feedback.setProgressText("Latitude derived from DSM: " + str(lat)) trunkfile = 0 trunkratio = 0 @@ -523,7 +926,7 @@ def processAlgorithm(self, parameters, context, feedback): # if useVegdem: if vegdsm is not None: usevegdem = 1 - feedback.setProgressText('Vegetation scheme activated') + feedback.setProgressText("Vegetation scheme activated") # load raster gdal.AllRegister() @@ -536,7 +939,9 @@ def processAlgorithm(self, parameters, context, feedback): vegsizey = vegdsm.shape[1] if not (vegsizex == sizex) & (vegsizey == sizey): - raise QgsProcessingException("Error in Vegetation Canopy DSM: All rasters must be of same extent and resolution") + raise QgsProcessingException( + "Error in Vegetation Canopy DSM: All rasters must be of same extent and resolution" + ) if vegdsm2 is not None: gdal.AllRegister() @@ -554,7 +959,9 @@ def processAlgorithm(self, parameters, context, feedback): vegsizey = vegdsm2.shape[1] if not (vegsizex == sizex) & (vegsizey == sizey): # & - raise QgsProcessingException("Error in Trunk Zone DSM: All rasters must be of same extent and resolution") + raise QgsProcessingException( + "Error in Trunk Zone DSM: All rasters must be of same extent and resolution" + ) else: vegdsm = np.zeros([rows, cols]) vegdsm2 = np.zeros([rows, cols]) @@ -565,7 +972,7 @@ def processAlgorithm(self, parameters, context, feedback): # Land cover if lcgrid is not None: landcover = 1 - feedback.setProgressText('Land cover scheme activated') + feedback.setProgressText("Land cover scheme activated") # load raster gdal.AllRegister() @@ -578,16 +985,24 @@ def processAlgorithm(self, parameters, context, feedback): lcsizey = lcgrid.shape[1] if not (lcsizex == sizex) & (lcsizey == sizey): - raise QgsProcessingException("Error in land cover grid: All grids must be of same extent and resolution") + raise QgsProcessingException( + "Error in land cover grid: All grids must be of same extent and resolution" + ) - baddataConifer = (lcgrid == 3) - baddataDecid = (lcgrid == 4) + baddataConifer = lcgrid == 3 + baddataDecid = lcgrid == 4 if baddataConifer.any(): - raise QgsProcessingException("Error in land cover grid: Land cover grid includes Confier land cover class. Ground cover information (underneath canopy) is required.") + raise QgsProcessingException( + "Error in land cover grid: Land cover grid includes Confier land cover class. Ground cover information (underneath canopy) is required." + ) if baddataDecid.any(): - raise QgsProcessingException("Error in land cover grid: Land cover grid includes Decidiuous land cover class. Ground cover information (underneath canopy) is required.") + raise QgsProcessingException( + "Error in land cover grid: Land cover grid includes Decidiuous land cover class. Ground cover information (underneath canopy) is required." + ) if np.isnan(lcgrid).any(): - raise QgsProcessingException("Error in land cover grid: Land cover grid includes NaN values. Use the QGIS Fill NoData cells tool to remove NaN values.") + raise QgsProcessingException( + "Error in land cover grid: Land cover grid includes NaN values. Use the QGIS Fill NoData cells tool to remove NaN values." + ) else: filePath_lc = None landcover = 0 @@ -596,7 +1011,9 @@ def processAlgorithm(self, parameters, context, feedback): # DEM # if not useLcBuild: demforbuild = 1 - dem = self.parameterAsRasterLayer(parameters, self.INPUT_DEM, context) + dem = self.parameterAsRasterLayer( + parameters, self.INPUT_DEM, context + ) if dem is None: raise QgsProcessingException("Error: No valid DEM selected") @@ -612,27 +1029,35 @@ def processAlgorithm(self, parameters, context, feedback): demsizey = dem.shape[1] if not (demsizex == sizex) & (demsizey == sizey): - raise QgsProcessingException( "Error in DEM: All grids must be of same extent and resolution") + raise QgsProcessingException( + "Error in DEM: All grids must be of same extent and resolution" + ) # response to issue and #230 nd = dataSet.GetRasterBand(1).GetNoDataValue() - dem[dem == nd] = 0. + dem[dem == nd] = 0.0 if dem.min() < 0: demraise = np.abs(dem.min()) dem = dem + demraise - feedback.setProgressText('Digital Evevation Model (DEM) included negative values. DEM raised with ' + str(demraise) + 'm.') + feedback.setProgressText( + "Digital Evevation Model (DEM) included negative values. DEM raised with " + + str(demraise) + + "m." + ) else: demraise = 0 alt = np.median(dem) if alt > 0: - alt = 3. + alt = 3.0 if (dsmraise != demraise) and (dsmraise - demraise > 0.5): - feedback.setProgressText('WARNiNG! DEM and DSM was raised unequally (difference > 0.5 m). Check your input data!') + feedback.setProgressText( + "WARNiNG! DEM and DSM was raised unequally (difference > 0.5 m). Check your input data!" + ) - #SVFs - zip = zipfile.ZipFile(inputSVF, 'r') + # SVFs + zip = zipfile.ZipFile(inputSVF, "r") zip.extractall(self.temp_dir) zip.close() @@ -682,24 +1107,30 @@ def processAlgorithm(self, parameters, context, feedback): svfEaveg = np.ones((rows, cols)) svfWaveg = np.ones((rows, cols)) except: - raise QgsProcessingException("SVF import error: The zipfile including the SVFs seems corrupt. Retry calcualting the SVFs in the Pre-processor or choose another file.") + raise QgsProcessingException( + "SVF import error: The zipfile including the SVFs seems corrupt. Retry calcualting the SVFs in the Pre-processor or choose another file." + ) svfsizex = svf.shape[0] svfsizey = svf.shape[1] if not (svfsizex == sizex) & (svfsizey == sizey): # & - raise QgsProcessingException("Error in svf rasters: All grids must be of same extent and resolution") + raise QgsProcessingException( + "Error in svf rasters: All grids must be of same extent and resolution" + ) - tmp = svf + svfveg - 1. - tmp[tmp < 0.] = 0. + tmp = svf + svfveg - 1.0 + tmp[tmp < 0.0] = 0.0 # %matlab crazyness around 0 - svfalfa = np.arcsin(np.exp((np.log((1. - tmp)) / 2.))) + svfalfa = np.arcsin(np.exp((np.log((1.0 - tmp)) / 2.0))) - feedback.setProgressText('Sky View Factor rasters loaded') + feedback.setProgressText("Sky View Factor rasters loaded") # wall height layer if whlayer is None: - raise QgsProcessingException("Error: No valid wall height raster layer is selected") + raise QgsProcessingException( + "Error: No valid wall height raster layer is selected" + ) provider = whlayer.dataProvider() filepath_wh = str(provider.dataSourceUri()) self.gdal_wh = gdal.Open(filepath_wh) @@ -707,11 +1138,15 @@ def processAlgorithm(self, parameters, context, feedback): vhsizex = wallheight.shape[0] vhsizey = wallheight.shape[1] if not (vhsizex == sizex) & (vhsizey == sizey): - raise QgsProcessingException("Error in Wall height raster: All rasters must be of same extent and resolution") + raise QgsProcessingException( + "Error in Wall height raster: All rasters must be of same extent and resolution" + ) # wall aspectlayer if walayer is None: - raise QgsProcessingException("Error: No valid wall aspect raster layer is selected") + raise QgsProcessingException( + "Error: No valid wall aspect raster layer is selected" + ) provider = walayer.dataProvider() filepath_wa = str(provider.dataSourceUri()) self.gdal_wa = gdal.Open(filepath_wa) @@ -719,36 +1154,52 @@ def processAlgorithm(self, parameters, context, feedback): vasizex = wallaspect.shape[0] vasizey = wallaspect.shape[1] if not (vasizex == sizex) & (vasizey == sizey): - raise QgsProcessingException("Error in Wall aspect raster: All rasters must be of same extent and resolution") + raise QgsProcessingException( + "Error in Wall aspect raster: All rasters must be of same extent and resolution" + ) # Metdata headernum = 1 - delim = ' ' + delim = " " Twater = [] try: - self.metdata = np.loadtxt(inputMet,skiprows=headernum, delimiter=delim) + self.metdata = np.loadtxt( + inputMet, skiprows=headernum, delimiter=delim + ) metfileexist = 1 except: - raise QgsProcessingException("Error: Make sure format of meteorological file is correct. You can" - "prepare your data by using 'Prepare Existing Data' in " - "the Pre-processor") - - testwhere = np.where((self.metdata[:, 14] < 0.0) | (self.metdata[:, 14] > 1300.0)) + raise QgsProcessingException( + "Error: Make sure format of meteorological file is correct. You can" + "prepare your data by using 'Prepare Existing Data' in " + "the Pre-processor" + ) + + testwhere = np.where( + (self.metdata[:, 14] < 0.0) | (self.metdata[:, 14] > 1300.0) + ) if testwhere[0].__len__() > 0: - raise QgsProcessingException("Error: Kdown - beyond what is expected at line: " + str(testwhere[0] + 1)) + raise QgsProcessingException( + "Error: Kdown - beyond what is expected at line: " + + str(testwhere[0] + 1) + ) if self.metdata.shape[1] == 24: feedback.setProgressText("Meteorological data successfully loaded") else: - raise QgsProcessingException("Error: Wrong number of columns in meteorological data. You can " - "prepare your data by using 'Prepare Existing Data' in " - "the Pre-processor") - - feedback.setProgressText("Calculating sun positions for each time step") - location = {'longitude': lon, 'latitude': lat, 'altitude': alt} - YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax = \ - Solweig_2015a_metdata_noload(self.metdata,location, utc) + raise QgsProcessingException( + "Error: Wrong number of columns in meteorological data. You can " + "prepare your data by using 'Prepare Existing Data' in " + "the Pre-processor" + ) + + feedback.setProgressText( + "Calculating sun positions for each time step" + ) + location = {"longitude": lon, "latitude": lat, "altitude": alt} + YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax = ( + Solweig_2015a_metdata_noload(self.metdata, location, utc) + ) # Creating vectors from meteorological input DOY = self.metdata[:, 1] @@ -761,33 +1212,41 @@ def processAlgorithm(self, parameters, context, feedback): radI = self.metdata[:, 22] P = self.metdata[:, 12] Ws = self.metdata[:, 9] - + # Check if diffuse and direct radiation exist if onlyglobal == 0: if np.min(radD) == -999: - raise QgsProcessingException("Diffuse radiation include NoData values", - 'Tick in the box "Estimate diffuse and direct shortwave..." or aqcuire ' - 'observed values from external data sources.') + raise QgsProcessingException( + "Diffuse radiation include NoData values", + 'Tick in the box "Estimate diffuse and direct shortwave..." or aqcuire ' + "observed values from external data sources.", + ) if np.min(radI) == -999: - raise QgsProcessingException("Direct radiation include NoData values", - 'Tick in the box "Estimate diffuse and direct shortwave..." or aqcuire ' - 'observed values from external data sources.') + raise QgsProcessingException( + "Direct radiation include NoData values", + 'Tick in the box "Estimate diffuse and direct shortwave..." or aqcuire ' + "observed values from external data sources.", + ) # POIs check - if poilyr is not None: # usePOI: - #header = 'yyyy id it imin dectime altitude azimuth kdir kdiff kglobal kdown kup keast ksouth ' \ + if poilyr is not None: # usePOI: + # header = 'yyyy id it imin dectime altitude azimuth kdir kdiff kglobal kdown kup keast ksouth ' \ # 'kwest knorth ldown lup least lsouth lwest lnorth Ta Tg RH Esky Tmrt ' \ # 'I0 CI Shadow SVF_b SVF_bv KsideI PET UTCI' - header = 'yyyy id it imin dectime altitude azimuth kdir kdiff kglobal kdown kup keast ksouth ' \ - 'kwest knorth ldown lup least lsouth lwest lnorth Ta Tg RH Esky Tmrt ' \ - 'I0 CI Shadow SVF_b SVF_bv KsideI PET UTCI CI_Tg CI_TgG KsideD Lside diffDown Kside' + header = ( + "yyyy id it imin dectime altitude azimuth kdir kdiff kglobal kdown kup keast ksouth " + "kwest knorth ldown lup least lsouth lwest lnorth Ta Tg RH Esky Tmrt " + "I0 CI Shadow SVF_b SVF_bv KsideI PET UTCI CI_Tg CI_TgG KsideD Lside diffDown Kside" + ) - # poilyr = self.parameterAsVectorLayer(parameters, self.POI_FILE, context) + # poilyr = self.parameterAsVectorLayer(parameters, self.POI_FILE, context) # if poilyr is None: - # raise QgsProcessingException("No valid point layer is selected") + # raise QgsProcessingException("No valid point layer is selected") - poi_field = self.parameterAsFields(parameters, self.POI_FIELD, context) + poi_field = self.parameterAsFields( + parameters, self.POI_FIELD, context + ) # if poi_field[0] is None: # raise QgsProcessingException("An attribute field with unique values must be selected when using a POI vector file") vlayer = poilyr @@ -806,40 +1265,57 @@ def processAlgorithm(self, parameters, context, feedback): poisxy[ind, 0] = ind poisxy[ind, 1] = np.round((x - minx) * scale) if miny >= 0: - poisxy[ind, 2] = np.round((miny + rows * (1. / scale) - y) * scale) + poisxy[ind, 2] = np.round( + (miny + rows * (1.0 / scale) - y) * scale + ) else: - poisxy[ind, 2] = np.round((miny + rows * (1. / scale) - y) * scale) + poisxy[ind, 2] = np.round( + (miny + rows * (1.0 / scale) - y) * scale + ) ind += 1 for k in range(0, poisxy.shape[0]): poi_save = [] # np.zeros((1, 33)) - data_out = outputDir + '/POI_' + str(poiname[k]) + '.txt' - np.savetxt(data_out, poi_save, delimiter=' ', header=header, comments='') - + data_out = outputDir + "/POI_" + str(poiname[k]) + ".txt" + np.savetxt( + data_out, + poi_save, + delimiter=" ", + header=header, + comments="", + ) + # Num format for POI output - numformat = '%d %d %d %d %.5f ' + '%.2f ' * 36 + numformat = "%d %d %d %d %.5f " + "%.2f " * 36 # Other PET variables mbody = self.parameterAsDouble(parameters, self.WEIGHT, context) - ht = self.parameterAsDouble(parameters, self.HEIGHT, context) / 100. + ht = ( + self.parameterAsDouble(parameters, self.HEIGHT, context) + / 100.0 + ) clo = self.parameterAsDouble(parameters, self.CLO, context) age = self.parameterAsDouble(parameters, self.AGE, context) activity = self.parameterAsDouble(parameters, self.WEIGHT, context) sex = self.parameterAsInt(parameters, self.SEX, context) + 1 - sensorheight = self.parameterAsDouble(parameters, self.SENSOR_HEIGHT, context) - - solweig_parameters['PET_settings']['Value']['Age'] = age - solweig_parameters['PET_settings']['Value']['Weight'] = mbody - solweig_parameters['PET_settings']['Value']['Height'] = ht - solweig_parameters['PET_settings']['Value']['clo'] = clo - solweig_parameters['PET_settings']['Value']['Activity'] = activity + sensorheight = self.parameterAsDouble( + parameters, self.SENSOR_HEIGHT, context + ) + + solweig_parameters["PET_settings"]["Value"]["Age"] = age + solweig_parameters["PET_settings"]["Value"]["Weight"] = mbody + solweig_parameters["PET_settings"]["Value"]["Height"] = ht + solweig_parameters["PET_settings"]["Value"]["clo"] = clo + solweig_parameters["PET_settings"]["Value"]["Activity"] = activity if sex == 1: - solweig_parameters['PET_settings']['Value']['Sex'] = 'Male' + solweig_parameters["PET_settings"]["Value"]["Sex"] = "Male" else: - solweig_parameters['PET_settings']['Value']['Sex'] = 'Female' + solweig_parameters["PET_settings"]["Value"]["Sex"] = "Female" - feedback.setProgressText("Point of interest (POI) vector data successfully loaded") + feedback.setProgressText( + "Point of interest (POI) vector data successfully loaded" + ) # %Parameterisarion for Lup if not height: @@ -847,9 +1323,9 @@ def processAlgorithm(self, parameters, context, feedback): # %Radiative surface influence, Rule of thumb by Schmid et al. (1990). first = np.round(height) - if first == 0.: - first = 1. - second = np.round((height * 20.)) + if first == 0.0: + first = 1.0 + second = np.round((height * 20.0)) if usevegdem == 1: # Conifer or deciduous @@ -858,9 +1334,9 @@ def processAlgorithm(self, parameters, context, feedback): else: leafon = np.zeros((1, DOY.shape[0])) if firstdayleaf > lastdayleaf: - leaf_bool = ((DOY > firstdayleaf) | (DOY < lastdayleaf)) + leaf_bool = (DOY > firstdayleaf) | (DOY < lastdayleaf) else: - leaf_bool = ((DOY > firstdayleaf) & (DOY < lastdayleaf)) + leaf_bool = (DOY > firstdayleaf) & (DOY < lastdayleaf) leafon[0, leaf_bool] = 1 # % Vegetation transmittivity of shortwave radiation @@ -880,9 +1356,11 @@ def processAlgorithm(self, parameters, context, feedback): # % Bush separation bush = np.logical_not((vegdsm2 * vegdsm)) * vegdsm - svfbuveg = (svf - (1. - svfveg) * (1. - transVeg)) # % major bug fixed 20141203 + svfbuveg = svf - (1.0 - svfveg) * ( + 1.0 - transVeg + ) # % major bug fixed 20141203 else: - psi = leafon * 0. + 1. + psi = leafon * 0.0 + 1.0 svfbuveg = svf bush = np.zeros([rows, cols]) amaxvalue = 0 @@ -894,7 +1372,7 @@ def processAlgorithm(self, parameters, context, feedback): Tgmap1S = np.zeros((rows, cols)) Tgmap1W = np.zeros((rows, cols)) Tgmap1N = np.zeros((rows, cols)) - + # building grid and land cover preparation # sitein = self.plugin_dir + "/landcoverclasses_2016a.txt" # f = open(sitein) @@ -918,37 +1396,41 @@ def processAlgorithm(self, parameters, context, feedback): buildings[buildings == 2] = 0 else: buildings = dsm - dem - buildings[buildings < 2.] = 1. - buildings[buildings >= 2.] = 0. + buildings[buildings < 2.0] = 1.0 + buildings[buildings >= 2.0] = 0.0 if saveBuild: - saveraster(gdal_dsm, outputDir + '/buildings.tif', buildings) + saveraster(gdal_dsm, outputDir + "/buildings.tif", buildings) # Import shadow matrices (Anisotropic sky) - if folderPathPerez: #UseAniso + if folderPathPerez: # UseAniso anisotropic_sky = 1 data = np.load(folderPathPerez) - shmat = data['shadowmat'] - vegshmat = data['vegshadowmat'] - vbshvegshmat = data['vbshmat'] + shmat = data["shadowmat"] + vegshmat = data["vegshadowmat"] + vbshvegshmat = data["vbshmat"] if usevegdem == 1: diffsh = np.zeros((rows, cols, shmat.shape[2])) for i in range(0, shmat.shape[2]): - diffsh[:, :, i] = shmat[:, :, i] - (1 - vegshmat[:, :, i]) * (1 - transVeg) # changes in psi not implemented yet + diffsh[:, :, i] = shmat[:, :, i] - ( + 1 - vegshmat[:, :, i] + ) * ( + 1 - transVeg + ) # changes in psi not implemented yet else: diffsh = shmat - #vegshmat += 1 - #vbshvegshmat += 1 + # vegshmat += 1 + # vbshvegshmat += 1 # Estimate number of patches based on shadow matrices if shmat.shape[2] == 145: - patch_option = 1 # patch_option = 1 # 145 patches + patch_option = 1 # patch_option = 1 # 145 patches elif shmat.shape[2] == 153: - patch_option = 2 # patch_option = 2 # 153 patches + patch_option = 2 # patch_option = 2 # 153 patches elif shmat.shape[2] == 306: - patch_option = 3 # patch_option = 3 # 306 patches + patch_option = 3 # patch_option = 3 # 306 patches elif shmat.shape[2] == 612: - patch_option = 4 # patch_option = 4 # 612 patches + patch_option = 4 # patch_option = 4 # 612 patches # asvf to calculate sunlit and shaded patches asvf = np.arccos(np.sqrt(svf)) @@ -956,8 +1438,12 @@ def processAlgorithm(self, parameters, context, feedback): # Empty array for steradians steradians = np.zeros((shmat.shape[2])) - anisotropic_feedback = "Sky divided into " + str(int(shmat.shape[2])) + " patches\n \ + anisotropic_feedback = ( + "Sky divided into " + + str(int(shmat.shape[2])) + + " patches\n \ Anisotropic sky for diffuse shortwave radiation (Perez et al., 1993) and longwave radiation (Martin & Berdahl, 1984)" + ) feedback.setProgressText(anisotropic_feedback) else: feedback.setProgressText("Isotropic sky") @@ -971,75 +1457,170 @@ def processAlgorithm(self, parameters, context, feedback): steradians = 0 # % Ts parameterisation maps - if landcover == 1.: + if landcover == 1.0: if folderWallScheme: unique_landcover = np.unique(lcgrid) unique_landcover = unique_landcover[unique_landcover < 100] - if np.max(unique_landcover) > 7 or np.min(unique_landcover) < 1: - raise QgsProcessingException("The land cover grid includes integer values higher (or lower) than UMEP-formatted " - "land cover grid (should be integer between 1 and 7). If other LC-classes should be included they also need to be included in landcoverclasses_2016a.txt") + if ( + np.max(unique_landcover) > 7 + or np.min(unique_landcover) < 1 + ): + raise QgsProcessingException( + "The land cover grid includes integer values higher (or lower) than UMEP-formatted " + "land cover grid (should be integer between 1 and 7). If other LC-classes should be included they also need to be included in landcoverclasses_2016a.txt" + ) else: if np.max(lcgrid) > 7 or np.min(lcgrid) < 1: - raise QgsProcessingException("The land cover grid includes integer values higher (or lower) than UMEP-formatted " - "land cover grid (should be integer between 1 and 7). If other LC-classes should be included they also need to be included in landcoverclasses_2016a.txt") + raise QgsProcessingException( + "The land cover grid includes integer values higher (or lower) than UMEP-formatted " + "land cover grid (should be integer between 1 and 7). If other LC-classes should be included they also need to be included in landcoverclasses_2016a.txt" + ) if np.where(lcgrid) == 3 or np.where(lcgrid) == 4: - raise QgsProcessingException("The land cover grid includes values (decidouos and/or conifer) not appropriate for SOLWEIG-formatted land cover grid (should not include 3 or 4).") - + raise QgsProcessingException( + "The land cover grid includes values (decidouos and/or conifer) not appropriate for SOLWEIG-formatted land cover grid (should not include 3 or 4)." + ) + # Get land cover properties for Tg wave (land cover scheme based on Bogren et al. 2000, explained in Lindberg et al., 2008 and Lindberg, Onomura & Grimmond, 2016) - [TgK, Tstart, alb_grid, emis_grid, TgK_wall, Tstart_wall, TmaxLST, TmaxLST_wall] = Tgmaps_v1(lcgrid.copy(), solweig_parameters) - + [ + TgK, + Tstart, + alb_grid, + emis_grid, + TgK_wall, + Tstart_wall, + TmaxLST, + TmaxLST_wall, + ] = Tgmaps_v1(lcgrid.copy(), solweig_parameters) + else: - TgK = Knight + solweig_parameters['Ts_deg']['Value']['Cobble_stone_2014a'] - Tstart = Knight - solweig_parameters['Tstart']['Value']['Cobble_stone_2014a'] - TmaxLST = solweig_parameters['TmaxLST']['Value']['Cobble_stone_2014a'] - alb_grid = Knight + solweig_parameters['Albedo']['Effective']['Value']['Cobble_stone_2014a'] - emis_grid = Knight + solweig_parameters['Emissivity']['Value']['Cobble_stone_2014a'] - TgK_wall = solweig_parameters['Ts_deg']['Value']['Walls'] - Tstart_wall = solweig_parameters['Tstart']['Value']['Walls'] - TmaxLST_wall = solweig_parameters['TmaxLST']['Value']['Walls'] + TgK = ( + Knight + + solweig_parameters["Ts_deg"]["Value"]["Cobble_stone_2014a"] + ) + Tstart = ( + Knight + - solweig_parameters["Tstart"]["Value"]["Cobble_stone_2014a"] + ) + TmaxLST = solweig_parameters["TmaxLST"]["Value"][ + "Cobble_stone_2014a" + ] + alb_grid = ( + Knight + + solweig_parameters["Albedo"]["Effective"]["Value"][ + "Cobble_stone_2014a" + ] + ) + emis_grid = ( + Knight + + solweig_parameters["Emissivity"]["Value"][ + "Cobble_stone_2014a" + ] + ) + TgK_wall = solweig_parameters["Ts_deg"]["Value"]["Walls"] + Tstart_wall = solweig_parameters["Tstart"]["Value"]["Walls"] + TmaxLST_wall = solweig_parameters["TmaxLST"]["Value"]["Walls"] # Import data for wall temperature parameterization if folderWallScheme: wallScheme = 1 wallData = np.load(folderWallScheme) - voxelMaps = wallData['voxelId'] - voxelTable = wallData['voxelTable'] + voxelMaps = wallData["voxelId"] + voxelTable = wallData["voxelTable"] # Get wall type set in GUI - wall_type = str(100 + int(self.parameterAsString(parameters, self.WALL_TYPE, context))) + wall_type = str( + 100 + + int( + self.parameterAsString(parameters, self.WALL_TYPE, context) + ) + ) # Calculate wall height for wall scheme, i.e. include corners (thicker walls) - walls_scheme = wa.findwalls_sp(dsm, 2, np.array([[1, 1, 1], [1, 0, 1], [1, 1, 1]])) + walls_scheme = wa.findwalls_sp( + dsm, 2, np.array([[1, 1, 1], [1, 0, 1], [1, 1, 1]]) + ) # Calculate wall aspect for wall scheme, i.e. include corners (thicker walls) - dirwalls_scheme = wa.filter1Goodwin_as_aspect_v3(walls_scheme.copy(), scale, dsm, feedback, 100./180.) - + dirwalls_scheme = wa.filter1Goodwin_as_aspect_v3( + walls_scheme.copy(), scale, dsm, feedback, 100.0 / 180.0 + ) + # Used in wall temperature parameterization scheme - first_timestep = (pd.to_datetime(YYYY[0][0], format='%Y') + pd.to_timedelta(DOY[0] - 1, unit='d') + - pd.to_timedelta(hours[0], unit='h') + pd.to_timedelta(minu[0], unit='m')) - second_timestep = (pd.to_datetime(YYYY[0][1], format='%Y') + pd.to_timedelta(DOY[1] - 1, unit='d') + - pd.to_timedelta(hours[1], unit='h') + pd.to_timedelta(minu[1], unit='m')) - + first_timestep = ( + pd.to_datetime(YYYY[0][0], format="%Y") + + pd.to_timedelta(DOY[0] - 1, unit="d") + + pd.to_timedelta(hours[0], unit="h") + + pd.to_timedelta(minu[0], unit="m") + ) + second_timestep = ( + pd.to_datetime(YYYY[0][1], format="%Y") + + pd.to_timedelta(DOY[1] - 1, unit="d") + + pd.to_timedelta(hours[1], unit="h") + + pd.to_timedelta(minu[1], unit="m") + ) + timeStep = (second_timestep - first_timestep).seconds # Load voxelTable as Pandas DataFrame - voxelTable, dirwalls_scheme = load_walls(voxelTable, solweig_parameters, wall_type, dirwalls_scheme, Ta[0], timeStep, albedo_b, ewall, alb_grid, landcover, lcgrid, dsm) + voxelTable, dirwalls_scheme = load_walls( + voxelTable, + solweig_parameters, + wall_type, + dirwalls_scheme, + Ta[0], + timeStep, + albedo_b, + ewall, + alb_grid, + landcover, + lcgrid, + dsm, + ) # Unique wall types - thermal_effusivity = voxelTable['thermalEffusivity'].unique() + thermal_effusivity = voxelTable["thermalEffusivity"].unique() # Empty wall temperature matrix for parameterization scheme - wallScheme_feedback = "Running with wall parameterization scheme. Walls divided into " + str(int(voxelTable.shape[0])) + " unique voxels." + wallScheme_feedback = ( + "Running with wall parameterization scheme. Walls divided into " + + str(int(voxelTable.shape[0])) + + " unique voxels." + ) feedback.setProgressText(wallScheme_feedback) # Use wall of interest if woilyr is not None: - (dsm_minx, dsm_x_size, dsm_x_rotation, dsm_miny, dsm_y_rotation, dsm_y_size) = gdal_dsm.GetGeoTransform() - - woi_field = self.parameterAsStrings(parameters, self.WOI_FIELD, context) - woisxy, woiname = wallOfInterest(woilyr, woi_field, minx, miny, scale, rows, outputDir, dsm_minx, dsm_x_size, dsm_miny, dsm_y_size) + ( + dsm_minx, + dsm_x_size, + dsm_x_rotation, + dsm_miny, + dsm_y_rotation, + dsm_y_size, + ) = gdal_dsm.GetGeoTransform() + + woi_field = self.parameterAsStrings( + parameters, self.WOI_FIELD, context + ) + woisxy, woiname = wallOfInterest( + woilyr, + woi_field, + minx, + miny, + scale, + rows, + outputDir, + dsm_minx, + dsm_x_size, + dsm_miny, + dsm_y_size, + ) # Create pandas datetime object to be used when createing an xarray DataSet where wall temperatures/radiation is stored and eventually saved as a NetCDf if wallNetCDF: - met_for_xarray = (pd.to_datetime(YYYY[0][:], format='%Y') + pd.to_timedelta(DOY - 1, unit='d') + - pd.to_timedelta(hours, unit='h') + pd.to_timedelta(minu, unit='m')) + met_for_xarray = ( + pd.to_datetime(YYYY[0][:], format="%Y") + + pd.to_timedelta(DOY - 1, unit="d") + + pd.to_timedelta(hours, unit="h") + + pd.to_timedelta(minu, unit="m") + ) else: wallScheme = 0 @@ -1047,76 +1628,125 @@ def processAlgorithm(self, parameters, context, feedback): voxelTable = 0 timeStep = 0 thermal_effusivity = 0 - walls_scheme = np.ones((rows, cols)) * 10. - dirwalls_scheme = np.ones((rows, cols)) * 10. + walls_scheme = np.ones((rows, cols)) * 10.0 + dirwalls_scheme = np.ones((rows, cols)) * 10.0 # Initialisation of time related variables if Ta.__len__() == 1: timestepdec = 0 else: timestepdec = dectime[1] - dectime[0] - timeadd = 0. - timeaddE = 0. - timeaddS = 0. - timeaddW = 0. - timeaddN = 0. - firstdaytime = 1. - - WriteMetadataSOLWEIG.writeRunInfo(outputDir, filepath_dsm, gdal_dsm, usevegdem, - filePath_cdsm, trunkfile, filePath_tdsm, lat, lon, utc, landcover, - filePath_lc, metfileexist, inputMet, self.metdata, self.plugin_dir, - absK, absL, albedo_b, albedo_g, ewall, eground, onlyglobal, trunkratio, - transVeg, rows, cols, pos, elvis, cyl, demforbuild, anisotropic_sky, wallScheme, thermal_effusivity) - - feedback.setProgressText("Writing settings for this model run to specified output folder (Filename: RunInfoSOLWEIG_YYYY_DOY_HHMM.txt)") + timeadd = 0.0 + timeaddE = 0.0 + timeaddS = 0.0 + timeaddW = 0.0 + timeaddN = 0.0 + firstdaytime = 1.0 + + WriteMetadataSOLWEIG.writeRunInfo( + outputDir, + filepath_dsm, + gdal_dsm, + usevegdem, + filePath_cdsm, + trunkfile, + filePath_tdsm, + lat, + lon, + utc, + landcover, + filePath_lc, + metfileexist, + inputMet, + self.metdata, + self.plugin_dir, + absK, + absL, + albedo_b, + albedo_g, + ewall, + eground, + onlyglobal, + trunkratio, + transVeg, + rows, + cols, + pos, + elvis, + cyl, + demforbuild, + anisotropic_sky, + wallScheme, + thermal_effusivity, + ) + + feedback.setProgressText( + "Writing settings for this model run to specified output folder (Filename: RunInfoSOLWEIG_YYYY_DOY_HHMM.txt)" + ) # Save svf if anisotropic_sky: if not poisxy is None: - patch_characteristics = np.zeros((shmat.shape[2], poisxy.shape[0])) + patch_characteristics = np.zeros( + (shmat.shape[2], poisxy.shape[0]) + ) for idx in range(poisxy.shape[0]): for idy in range(shmat.shape[2]): # Calculations for patches on sky, shmat = 1 = sky is visible - temp_sky = ((shmat[:,:,idy] == 1) & (vegshmat[:,:,idy] == 1)) + temp_sky = (shmat[:, :, idy] == 1) & ( + vegshmat[:, :, idy] == 1 + ) # Calculations for patches that are vegetation, vegshmat = 0 = shade from vegetation - temp_vegsh = ((vegshmat[:,:,idy] == 0) | (vbshvegshmat[:,:,idy] == 0)) + temp_vegsh = (vegshmat[:, :, idy] == 0) | ( + vbshvegshmat[:, :, idy] == 0 + ) # Calculations for patches that are buildings, shmat = 0 = shade from buildings - temp_vbsh = (1 - shmat[:,:,idy]) * vbshvegshmat[:,:,idy] - temp_sh = (temp_vbsh == 1) + temp_vbsh = (1 - shmat[:, :, idy]) * vbshvegshmat[ + :, :, idy + ] + temp_sh = temp_vbsh == 1 if wallScheme: temp_sh_w = temp_sh * voxelMaps[:, :, idy] - temp_sh_roof = temp_sh * (voxelMaps[:, :, idy] == 0) + temp_sh_roof = temp_sh * ( + voxelMaps[:, :, idy] == 0 + ) else: temp_sh_w = 0 temp_sh_roof = 0 # Sky patch if temp_sky[int(poisxy[idx, 2]), int(poisxy[idx, 1])]: - patch_characteristics[idy,idx] = 1.8 + patch_characteristics[idy, idx] = 1.8 # Vegetation patch - elif (temp_vegsh[int(poisxy[idx, 2]), int(poisxy[idx, 1])]): - patch_characteristics[idy,idx] = 2.5 + elif temp_vegsh[ + int(poisxy[idx, 2]), int(poisxy[idx, 1]) + ]: + patch_characteristics[idy, idx] = 2.5 # Building patch - elif (temp_sh[int(poisxy[idx, 2]), int(poisxy[idx, 1])]): + elif temp_sh[int(poisxy[idx, 2]), int(poisxy[idx, 1])]: if wallScheme: - if (temp_sh_w[int(poisxy[idx, 2]), int(poisxy[idx, 1])]): + if temp_sh_w[ + int(poisxy[idx, 2]), int(poisxy[idx, 1]) + ]: patch_characteristics[idy, idx] = 4.5 - elif (temp_sh_roof[int(poisxy[idx, 2]), int(poisxy[idx, 1])]): + elif temp_sh_roof[ + int(poisxy[idx, 2]), int(poisxy[idx, 1]) + ]: patch_characteristics[idy, idx] = 6.0 else: - patch_characteristics[idy,idx] = 4.5 + patch_characteristics[idy, idx] = 4.5 # Roof patch - #elif + # elif # If metfile starts at night - CI = 1. + CI = 1.0 # Save solweig_parameters in output folder - with open(outputDir + '/solweig_parameters.json', 'w') as f: + with open(outputDir + "/solweig_parameters.json", "w") as f: json.dump(solweig_parameters, f, indent=2) # Main function feedback.setProgressText("Executing main model") - + tmrtplot = np.zeros((rows, cols)) TgOut1 = np.zeros((rows, cols)) @@ -1133,7 +1763,9 @@ def processAlgorithm(self, parameters, context, feedback): rotate_deg = 0 for i in np.arange(0, Ta.__len__()): - feedback.setProgress(int(i * (100. / Ta.__len__()))) # move progressbar forward + feedback.setProgress( + int(i * (100.0 / Ta.__len__())) + ) # move progressbar forward if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") break @@ -1148,14 +1780,19 @@ def processAlgorithm(self, parameters, context, feedback): alt = altitude[0][daylines] alt2 = np.where(alt > 1) rise = alt2[0][0] - [_, CI, _, _, _] = clearnessindex_2013b(zen[0, i + rise + 1], jday[0, i + rise + 1], - Ta[i + rise + 1], - RH[i + rise + 1] / 100., radG[i + rise + 1], location, - P[i + rise + 1]) # i+rise+1 to match matlab code. correct? - if (CI > 1.) or (CI == np.inf): - CI = 1. + [_, CI, _, _, _] = clearnessindex_2013b( + zen[0, i + rise + 1], + jday[0, i + rise + 1], + Ta[i + rise + 1], + RH[i + rise + 1] / 100.0, + radG[i + rise + 1], + location, + P[i + rise + 1], + ) # i+rise+1 to match matlab code. correct? + if (CI > 1.0) or (CI == np.inf): + CI = 1.0 else: - CI = 1. + CI = 1.0 # if altitude[0][i] > 0: # # # radI[i] = (radG[i] - radD[i])/np.sin(altitude[0][i] * np.pi/180) @@ -1168,21 +1805,142 @@ def processAlgorithm(self, parameters, context, feedback): # radI[i] = radI[i]/np.sin(altitude[0][i] * np.pi/180) - Tmrt, Kdown, Kup, Ldown, Lup, Tg, ea, esky, I0, CI, shadow, firstdaytime, timestepdec, timeadd, \ - Tgmap1, Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, Keast, Ksouth, Kwest, Knorth, Least, \ - Lsouth, Lwest, Lnorth, KsideI, TgOut1, TgOut, radIout, radDout, \ - Lside, Lsky_patch_characteristics, CI_Tg, CI_TgG, KsideD, \ - dRad, Kside, steradians, voxelTable = so.Solweig_2022a_calc( - i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, svfveg, - svfNveg, svfEveg, svfSveg, svfWveg, svfaveg, svfEaveg, svfSaveg, svfWaveg, svfNaveg, \ - vegdsm, vegdsm2, albedo_b, absK, absL, ewall, Fside, Fup, Fcyl, altitude[0][i], - azimuth[0][i] + rotate_deg, zen[0][i], jday[0][i], usevegdem, onlyglobal, buildings, location, - psi[0][i], landcover, lcgrid, dectime[i], altmax[0][i], wallaspect, - wallheight, cyl, elvis, Ta[i], RH[i], radG[i], radD[i], radI[i], P[i], amaxvalue, - bush, Twater, TgK, Tstart, alb_grid, emis_grid, TgK_wall, Tstart_wall, TmaxLST, - TmaxLST_wall, first, second, svfalfa, svfbuveg, firstdaytime, timeadd, timestepdec, - Tgmap1, Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, CI, TgOut1, diffsh, shmat, vegshmat, vbshvegshmat, - anisotropic_sky, asvf, patch_option, voxelMaps, voxelTable, Ws[i], wallScheme, timeStep, steradians, walls_scheme, dirwalls_scheme) + ( + Tmrt, + Kdown, + Kup, + Ldown, + Lup, + Tg, + ea, + esky, + I0, + CI, + shadow, + firstdaytime, + timestepdec, + timeadd, + Tgmap1, + Tgmap1E, + Tgmap1S, + Tgmap1W, + Tgmap1N, + Keast, + Ksouth, + Kwest, + Knorth, + Least, + Lsouth, + Lwest, + Lnorth, + KsideI, + TgOut1, + TgOut, + radIout, + radDout, + Lside, + Lsky_patch_characteristics, + CI_Tg, + CI_TgG, + KsideD, + dRad, + Kside, + steradians, + voxelTable, + ) = so.Solweig_2022a_calc( + i, + dsm, + scale, + rows, + cols, + svf, + svfN, + svfW, + svfE, + svfS, + svfveg, + svfNveg, + svfEveg, + svfSveg, + svfWveg, + svfaveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + vegdsm, + vegdsm2, + albedo_b, + absK, + absL, + ewall, + Fside, + Fup, + Fcyl, + altitude[0][i], + azimuth[0][i] + rotate_deg, + zen[0][i], + jday[0][i], + usevegdem, + onlyglobal, + buildings, + location, + psi[0][i], + landcover, + lcgrid, + dectime[i], + altmax[0][i], + wallaspect, + wallheight, + cyl, + elvis, + Ta[i], + RH[i], + radG[i], + radD[i], + radI[i], + P[i], + amaxvalue, + bush, + Twater, + TgK, + Tstart, + alb_grid, + emis_grid, + TgK_wall, + Tstart_wall, + TmaxLST, + TmaxLST_wall, + first, + second, + svfalfa, + svfbuveg, + firstdaytime, + timeadd, + timestepdec, + Tgmap1, + Tgmap1E, + Tgmap1S, + Tgmap1W, + Tgmap1N, + CI, + TgOut1, + diffsh, + shmat, + vegshmat, + vbshvegshmat, + anisotropic_sky, + asvf, + patch_option, + voxelMaps, + voxelTable, + Ws[i], + wallScheme, + timeStep, + steradians, + walls_scheme, + dirwalls_scheme, + ) # Tmrt, Kdown, Kup, Ldown, Lup, Tg, ea, esky, I0, CI, shadow, firstdaytime, timestepdec, timeadd, \ # Tgmap1, Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, Keast, Ksouth, Kwest, Knorth, Least, \ @@ -1194,11 +1952,9 @@ def processAlgorithm(self, parameters, context, feedback): # psi[0][i], landcover, lcgrid, dectime[i], altmax[0][i], wallaspect, # wallheight, cyl, elvis, Ta[i], RH[i], radG[i], radD[i], radI[i], P[i], amaxvalue, # bush, Twater, TgK, Tstart, alb_grid, emis_grid, TgK_wall, Tstart_wall, TmaxLST, - # TmaxLST_wall, first, second, svfalfa, svfbuveg, firstdaytime, timeadd, timestepdec, + # TmaxLST_wall, first, second, svfalfa, svfbuveg, firstdaytime, timeadd, timestepdec, # Tgmap1, Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, CI, TgOut1, diffsh, ani) - - # Tmrt, Kdown, Kup, Ldown, Lup, Tg, ea, esky, I0, CI, shadow, firstdaytime, timestepdec, timeadd, \ # Tgmap1, timeaddE, Tgmap1E, timeaddS, Tgmap1S, timeaddW, Tgmap1W, timeaddN, Tgmap1N, \ # Keast, Ksouth, Kwest, Knorth, Least, Lsouth, Lwest, Lnorth, KsideI, TgOut1, TgOut, radIout, radDout \ @@ -1221,20 +1977,20 @@ def processAlgorithm(self, parameters, context, feedback): # hours_for_plot = hours[DOY == first_unique_day[0]] dectime_for_plot = dectime[DOY == first_unique_day[0]] fig, ax = plt.subplots() - ax.plot(dectime_for_plot, I0_array, label='I0') - ax.plot(dectime_for_plot, radG_for_plot, label='Kglobal') - ax.set_ylabel('Shortwave radiation [$Wm^{-2}$]') - ax.set_xlabel('Decimal time') - ax.set_title('UTC' + str(int(utc))) + ax.plot(dectime_for_plot, I0_array, label="I0") + ax.plot(dectime_for_plot, radG_for_plot, label="Kglobal") + ax.set_ylabel("Shortwave radiation [$Wm^{-2}$]") + ax.set_xlabel("Decimal time") + ax.set_title("UTC" + str(int(utc))) ax.legend() - fig.savefig(outputDir + '/metCheck.png', dpi=150) + fig.savefig(outputDir + "/metCheck.png", dpi=150) tmrtplot = tmrtplot + Tmrt if altitude[0][i] > 0: - w = 'D' + w = "D" else: - w = 'N' + w = "N" # # Write to POIs # if not poisxy is None: @@ -1289,7 +2045,7 @@ def processAlgorithm(self, parameters, context, feedback): # f_handle.close() # Write to POIs - if not poisxy is None: + if not poisxy is None: for k in range(0, poisxy.shape[0]): poi_save = np.zeros((1, 41)) poi_save[0, 0] = YYYY[0][i] @@ -1302,47 +2058,99 @@ def processAlgorithm(self, parameters, context, feedback): poi_save[0, 7] = radIout poi_save[0, 8] = radDout poi_save[0, 9] = radG[i] - poi_save[0, 10] = Kdown[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 10] = Kdown[ + int(poisxy[k, 2]), int(poisxy[k, 1]) + ] poi_save[0, 11] = Kup[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 12] = Keast[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 13] = Ksouth[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 14] = Kwest[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 15] = Knorth[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 16] = Ldown[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 12] = Keast[ + int(poisxy[k, 2]), int(poisxy[k, 1]) + ] + poi_save[0, 13] = Ksouth[ + int(poisxy[k, 2]), int(poisxy[k, 1]) + ] + poi_save[0, 14] = Kwest[ + int(poisxy[k, 2]), int(poisxy[k, 1]) + ] + poi_save[0, 15] = Knorth[ + int(poisxy[k, 2]), int(poisxy[k, 1]) + ] + poi_save[0, 16] = Ldown[ + int(poisxy[k, 2]), int(poisxy[k, 1]) + ] poi_save[0, 17] = Lup[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 18] = Least[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 19] = Lsouth[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 20] = Lwest[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 21] = Lnorth[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 18] = Least[ + int(poisxy[k, 2]), int(poisxy[k, 1]) + ] + poi_save[0, 19] = Lsouth[ + int(poisxy[k, 2]), int(poisxy[k, 1]) + ] + poi_save[0, 20] = Lwest[ + int(poisxy[k, 2]), int(poisxy[k, 1]) + ] + poi_save[0, 21] = Lnorth[ + int(poisxy[k, 2]), int(poisxy[k, 1]) + ] poi_save[0, 22] = Ta[i] - poi_save[0, 23] = TgOut[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 23] = TgOut[ + int(poisxy[k, 2]), int(poisxy[k, 1]) + ] poi_save[0, 24] = RH[i] poi_save[0, 25] = esky - poi_save[0, 26] = Tmrt[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 26] = Tmrt[ + int(poisxy[k, 2]), int(poisxy[k, 1]) + ] poi_save[0, 27] = I0 poi_save[0, 28] = CI - poi_save[0, 29] = shadow[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 29] = shadow[ + int(poisxy[k, 2]), int(poisxy[k, 1]) + ] poi_save[0, 30] = svf[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 31] = svfbuveg[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 32] = KsideI[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 31] = svfbuveg[ + int(poisxy[k, 2]), int(poisxy[k, 1]) + ] + poi_save[0, 32] = KsideI[ + int(poisxy[k, 2]), int(poisxy[k, 1]) + ] # Recalculating wind speed based on powerlaw WsPET = (1.1 / sensorheight) ** 0.2 * Ws[i] - WsUTCI = (10. / sensorheight) ** 0.2 * Ws[i] - resultPET = p._PET(Ta[i], RH[i], Tmrt[int(poisxy[k, 2]), int(poisxy[k, 1])], WsPET, - mbody, age, ht, activity, clo, sex) + WsUTCI = (10.0 / sensorheight) ** 0.2 * Ws[i] + resultPET = p._PET( + Ta[i], + RH[i], + Tmrt[int(poisxy[k, 2]), int(poisxy[k, 1])], + WsPET, + mbody, + age, + ht, + activity, + clo, + sex, + ) poi_save[0, 33] = resultPET - resultUTCI = utci.utci_calculator(Ta[i], RH[i], Tmrt[int(poisxy[k, 2]), int(poisxy[k, 1])], - WsUTCI) + resultUTCI = utci.utci_calculator( + Ta[i], + RH[i], + Tmrt[int(poisxy[k, 2]), int(poisxy[k, 1])], + WsUTCI, + ) poi_save[0, 34] = resultUTCI poi_save[0, 35] = CI_Tg poi_save[0, 36] = CI_TgG - poi_save[0, 37] = KsideD[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 38] = Lside[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 39] = dRad[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 40] = Kside[int(poisxy[k, 2]), int(poisxy[k, 1])] - data_out = outputDir + '/POI_' + str(poiname[k]) + '.txt' + poi_save[0, 37] = KsideD[ + int(poisxy[k, 2]), int(poisxy[k, 1]) + ] + poi_save[0, 38] = Lside[ + int(poisxy[k, 2]), int(poisxy[k, 1]) + ] + poi_save[0, 39] = dRad[ + int(poisxy[k, 2]), int(poisxy[k, 1]) + ] + poi_save[0, 40] = Kside[ + int(poisxy[k, 2]), int(poisxy[k, 1]) + ] + data_out = outputDir + "/POI_" + str(poiname[k]) + ".txt" # f_handle = file(data_out, 'a') - f_handle = open(data_out, 'ab') + f_handle = open(data_out, "ab") np.savetxt(f_handle, poi_save, fmt=numformat) f_handle.close() @@ -1351,110 +2159,317 @@ def processAlgorithm(self, parameters, context, feedback): # Store wall data for output if not woisxy is None: for k in range(0, woisxy.shape[0]): - temp_wall = voxelTable.loc[((voxelTable['ypos'] == woisxy[k, 2]) & (voxelTable['xpos'] == woisxy[k, 1])), 'wallTemperature'].to_numpy() - K_in = voxelTable.loc[((voxelTable['ypos'] == woisxy[k, 2]) & (voxelTable['xpos'] == woisxy[k, 1])), 'K_in'].to_numpy() - L_in = voxelTable.loc[((voxelTable['ypos'] == woisxy[k, 2]) & (voxelTable['xpos'] == woisxy[k, 1])), 'L_in'].to_numpy() - wallShade = voxelTable.loc[((voxelTable['ypos'] == woisxy[k, 2]) & (voxelTable['xpos'] == woisxy[k, 1])), 'wallShade'].to_numpy() - temp_all = np.concatenate([temp_wall, K_in, L_in, wallShade]) + temp_wall = voxelTable.loc[ + ( + (voxelTable["ypos"] == woisxy[k, 2]) + & (voxelTable["xpos"] == woisxy[k, 1]) + ), + "wallTemperature", + ].to_numpy() + K_in = voxelTable.loc[ + ( + (voxelTable["ypos"] == woisxy[k, 2]) + & (voxelTable["xpos"] == woisxy[k, 1]) + ), + "K_in", + ].to_numpy() + L_in = voxelTable.loc[ + ( + (voxelTable["ypos"] == woisxy[k, 2]) + & (voxelTable["xpos"] == woisxy[k, 1]) + ), + "L_in", + ].to_numpy() + wallShade = voxelTable.loc[ + ( + (voxelTable["ypos"] == woisxy[k, 2]) + & (voxelTable["xpos"] == woisxy[k, 1]) + ), + "wallShade", + ].to_numpy() + temp_all = np.concatenate( + [temp_wall, K_in, L_in, wallShade] + ) # temp_all = np.concatenate([temp_wall]) # wall_data = np.zeros((1, 7 + temp_wall.shape[0])) wall_data = np.zeros((1, 7 + temp_all.shape[0])) # Part of file name (wallid), i.e. WOI_wallid.txt - data_out = outputDir + '/WOI_' + str(woiname[k]) + '.txt' + data_out = ( + outputDir + "/WOI_" + str(woiname[k]) + ".txt" + ) if i == 0: # Output file header - #header = 'yyyy id it imin dectime Ta SVF Ts' - header = 'yyyy id it imin dectime Ta SVF' + ' Ts' * temp_wall.shape[0] + ' Kin' * K_in.shape[0] + ' Lin' * L_in.shape[0] + ' shade' * wallShade.shape[0] + # header = 'yyyy id it imin dectime Ta SVF Ts' + header = ( + "yyyy id it imin dectime Ta SVF" + + " Ts" * temp_wall.shape[0] + + " Kin" * K_in.shape[0] + + " Lin" * L_in.shape[0] + + " shade" * wallShade.shape[0] + ) # Part of file name (wallid), i.e. WOI_wallid.txt # woiname = voxelTable.loc[((voxelTable['ypos'] == woisxy[k, 2]) & (voxelTable['xpos'] == woisxy[k, 1])), 'wallId'].to_numpy()[0] - woi_save = [] # - np.savetxt(data_out, woi_save, delimiter=' ', header=header, comments='') + woi_save = [] # + np.savetxt( + data_out, + woi_save, + delimiter=" ", + header=header, + comments="", + ) # Fill wall_data with variables - wall_data[0, 0] = YYYY[0][i] + wall_data[0, 0] = YYYY[0][i] wall_data[0, 1] = jday[0][i] wall_data[0, 2] = hours[i] wall_data[0, 3] = minu[i] wall_data[0, 4] = dectime[i] wall_data[0, 5] = Ta[i] - wall_data[0, 6] = svf[int(woisxy[k, 2]), int(woisxy[k, 1])] + wall_data[0, 6] = svf[ + int(woisxy[k, 2]), int(woisxy[k, 1]) + ] wall_data[0, 7:] = temp_all # Num format for output file data - woi_numformat = '%d %d %d %d %.5f %.2f %.2f' + ' %.2f' * temp_all.shape[0] + woi_numformat = ( + "%d %d %d %d %.5f %.2f %.2f" + + " %.2f" * temp_all.shape[0] + ) # Open file, add data, save - f_handle = open(data_out, 'ab') + f_handle = open(data_out, "ab") np.savetxt(f_handle, wall_data, fmt=woi_numformat) - f_handle.close() + f_handle.close() # Save wall temperature/radiation as NetCDF if wallNetCDF: - netcdf_output = outputDir + '/walls.nc' - walls_as_netcdf(voxelTable, rows, cols, met_for_xarray, i, dsm, filepath_dsm, netcdf_output) + netcdf_output = outputDir + "/walls.nc" + walls_as_netcdf( + voxelTable, + rows, + cols, + met_for_xarray, + i, + dsm, + filepath_dsm, + netcdf_output, + ) if hours[i] < 10: - XH = '0' + XH = "0" else: - XH = '' + XH = "" if minu[i] < 10: - XM = '0' + XM = "0" else: - XM = '' + XM = "" if outputTmrt: - saveraster(gdal_dsm, outputDir + '/Tmrt_' + str(int(YYYY[0, i])) + '_' + str(int(DOY[i])) - + '_' + XH + str(int(hours[i])) + XM + str(int(minu[i])) + w + '.tif', Tmrt) + saveraster( + gdal_dsm, + outputDir + + "/Tmrt_" + + str(int(YYYY[0, i])) + + "_" + + str(int(DOY[i])) + + "_" + + XH + + str(int(hours[i])) + + XM + + str(int(minu[i])) + + w + + ".tif", + Tmrt, + ) if outputKup: - saveraster(gdal_dsm, outputDir + '/Kup_' + str(int(YYYY[0, i])) + '_' + str(int(DOY[i])) - + '_' + XH + str(int(hours[i])) + XM + str(int(minu[i])) + w + '.tif', Kup) + saveraster( + gdal_dsm, + outputDir + + "/Kup_" + + str(int(YYYY[0, i])) + + "_" + + str(int(DOY[i])) + + "_" + + XH + + str(int(hours[i])) + + XM + + str(int(minu[i])) + + w + + ".tif", + Kup, + ) if outputKdown: - saveraster(gdal_dsm, outputDir + '/Kdown_' + str(int(YYYY[0, i])) + '_' + str(int(DOY[i])) - + '_' + XH + str(int(hours[i])) + XM + str(int(minu[i])) + w + '.tif', Kdown) + saveraster( + gdal_dsm, + outputDir + + "/Kdown_" + + str(int(YYYY[0, i])) + + "_" + + str(int(DOY[i])) + + "_" + + XH + + str(int(hours[i])) + + XM + + str(int(minu[i])) + + w + + ".tif", + Kdown, + ) if outputLup: - saveraster(gdal_dsm, outputDir + '/Lup_' + str(int(YYYY[0, i])) + '_' + str(int(DOY[i])) - + '_' + XH + str(int(hours[i])) + XM + str(int(minu[i])) + w + '.tif', Lup) + saveraster( + gdal_dsm, + outputDir + + "/Lup_" + + str(int(YYYY[0, i])) + + "_" + + str(int(DOY[i])) + + "_" + + XH + + str(int(hours[i])) + + XM + + str(int(minu[i])) + + w + + ".tif", + Lup, + ) if outputLdown: - saveraster(gdal_dsm, outputDir + '/Ldown_' + str(int(YYYY[0, i])) + '_' + str(int(DOY[i])) - + '_' + XH + str(int(hours[i])) + XM + str(int(minu[i])) + w + '.tif', Ldown) + saveraster( + gdal_dsm, + outputDir + + "/Ldown_" + + str(int(YYYY[0, i])) + + "_" + + str(int(DOY[i])) + + "_" + + XH + + str(int(hours[i])) + + XM + + str(int(minu[i])) + + w + + ".tif", + Ldown, + ) if outputSh: - saveraster(gdal_dsm, outputDir + '/Shadow_' + str(int(YYYY[0, i])) + '_' + str(int(DOY[i])) - + '_' + XH + str(int(hours[i])) + XM + str(int(minu[i])) + w + '.tif', shadow) - + saveraster( + gdal_dsm, + outputDir + + "/Shadow_" + + str(int(YYYY[0, i])) + + "_" + + str(int(DOY[i])) + + "_" + + XH + + str(int(hours[i])) + + XM + + str(int(minu[i])) + + w + + ".tif", + shadow, + ) + if outputKdiff: - saveraster(gdal_dsm, outputDir + '/Kdiff_' + str(int(YYYY[0, i])) + '_' + str(int(DOY[i])) - + '_' + XH + str(int(hours[i])) + XM + str(int(minu[i])) + w + '.tif', dRad) + saveraster( + gdal_dsm, + outputDir + + "/Kdiff_" + + str(int(YYYY[0, i])) + + "_" + + str(int(DOY[i])) + + "_" + + XH + + str(int(hours[i])) + + XM + + str(int(minu[i])) + + w + + ".tif", + dRad, + ) # Sky view image of patches - if ((anisotropic_sky == 1) & (i == 0) & (not poisxy is None)): - for k in range(poisxy.shape[0]): - Lsky_patch_characteristics[:,2] = patch_characteristics[:,k] - skyviewimage_out = outputDir + '/POI_' + str(poiname[k]) + '.png' - PolarBarPlot(Lsky_patch_characteristics, altitude[0][i], azimuth[0][i], 'Hemisphere partitioning', skyviewimage_out, 0, 5, 0) + if (anisotropic_sky == 1) & (i == 0) & (not poisxy is None): + for k in range(poisxy.shape[0]): + Lsky_patch_characteristics[:, 2] = patch_characteristics[ + :, k + ] + skyviewimage_out = ( + outputDir + "/POI_" + str(poiname[k]) + ".png" + ) + PolarBarPlot( + Lsky_patch_characteristics, + altitude[0][i], + azimuth[0][i], + "Hemisphere partitioning", + skyviewimage_out, + 0, + 5, + 0, + ) # Save files for Tree Planter if outputTreeplanter: feedback.setProgressText("Saving files for Tree Planter tool") # Save DSM - copyfile(filepath_dsm, outputDir + '/DSM.tif') + copyfile(filepath_dsm, outputDir + "/DSM.tif") # Save CDSM if usevegdem == 1: - copyfile(filePath_cdsm, outputDir + '/CDSM.tif') + copyfile(filePath_cdsm, outputDir + "/CDSM.tif") # Saving settings from SOLWEIG for SOLWEIG1D in TreePlanter - settingsHeader = 'UTC, posture, onlyglobal, landcover, anisotropic, cylinder, albedo_walls, albedo_ground, emissivity_walls, emissivity_ground, absK, absL, elevation, patch_option' - settingsFmt = '%i', '%i', '%i', '%i', '%i', '%i', '%1.2f', '%1.2f', '%1.2f', '%1.2f', '%1.2f', '%1.2f', '%1.2f', '%i' - settingsData = np.array([[utc, pos, onlyglobal, landcover, anisotropic_sky, cyl, albedo_b, albedo_g, ewall, eground, absK, absL, alt, patch_option]]) - np.savetxt(outputDir + '/treeplantersettings.txt', settingsData, fmt=settingsFmt, header=settingsHeader, delimiter=' ') + settingsHeader = "UTC, posture, onlyglobal, landcover, anisotropic, cylinder, albedo_walls, albedo_ground, emissivity_walls, emissivity_ground, absK, absL, elevation, patch_option" + settingsFmt = ( + "%i", + "%i", + "%i", + "%i", + "%i", + "%i", + "%1.2f", + "%1.2f", + "%1.2f", + "%1.2f", + "%1.2f", + "%1.2f", + "%1.2f", + "%i", + ) + settingsData = np.array( + [ + [ + utc, + pos, + onlyglobal, + landcover, + anisotropic_sky, + cyl, + albedo_b, + albedo_g, + ewall, + eground, + absK, + absL, + alt, + patch_option, + ] + ] + ) + np.savetxt( + outputDir + "/treeplantersettings.txt", + settingsData, + fmt=settingsFmt, + header=settingsHeader, + delimiter=" ", + ) # Copying met file for SpatialTC - copyfile(inputMet, outputDir + '/metforcing.txt') - - tmrtplot = tmrtplot / Ta.__len__() # fix average Tmrt instead of sum, 20191022 - saveraster(gdal_dsm, outputDir + '/Tmrt_average.tif', tmrtplot) + copyfile(inputMet, outputDir + "/metforcing.txt") + + tmrtplot = ( + tmrtplot / Ta.__len__() + ) # fix average Tmrt instead of sum, 20191022 + saveraster(gdal_dsm, outputDir + "/Tmrt_average.tif", tmrtplot) feedback.setProgressText("SOLWEIG: Model calculation finished.") return {self.OUTPUT_DIR: outputDir} - + def name(self): """ Returns the algorithm name, used for identifying the algorithm. This @@ -1463,14 +2478,14 @@ def name(self): lowercase alphanumeric characters only and no spaces or other formatting characters. """ - return 'Outdoor Thermal Comfort: SOLWEIG' + return "Outdoor Thermal Comfort: SOLWEIG" def displayName(self): """ Returns the translated algorithm name, which should be used for any user-visible display of the algorithm name. """ - return self.tr('Outdoor Thermal Comfort: SOLWEIG v2025a') + return self.tr("Outdoor Thermal Comfort: SOLWEIG v2025a") def group(self): """ @@ -1487,32 +2502,36 @@ def groupId(self): contain lowercase alphanumeric characters only and no spaces or other formatting characters. """ - return 'Processor' + return "Processor" def shortHelpString(self): - return self.tr('SOLWEIG (v2025a) is a model which can be used to estimate spatial variations of 3D radiation fluxes and ' - 'mean radiant temperature (Tmrt) in complex urban settings. The SOLWEIG model follows the same ' - 'approach commonly adopted to observe Tmrt, with shortwave and longwave radiation fluxes from ' - 'six directions being individually calculated to derive Tmrt. The model requires a limited number ' - 'of inputs, such as direct, diffuse and global shortwave radiation, air temperature, relative ' - 'humidity, urban geometry and geographical information (latitude, longitude and elevation). ' - 'Additional vegetation and ground cover information can also be used to imporove the estimation of Tmrt.\n' - '\n' - 'Tools to generate sky view factors, wall height and aspect etc. is available in the pre-processing past in UMEP\n' - '\n' - '------------\n' - '\n' - 'Full manual available via the Help-button.') + return self.tr( + "SOLWEIG (v2025a) is a model which can be used to estimate spatial variations of 3D radiation fluxes and " + "mean radiant temperature (Tmrt) in complex urban settings. The SOLWEIG model follows the same " + "approach commonly adopted to observe Tmrt, with shortwave and longwave radiation fluxes from " + "six directions being individually calculated to derive Tmrt. The model requires a limited number " + "of inputs, such as direct, diffuse and global shortwave radiation, air temperature, relative " + "humidity, urban geometry and geographical information (latitude, longitude and elevation). " + "Additional vegetation and ground cover information can also be used to imporove the estimation of Tmrt.\n" + "\n" + "Tools to generate sky view factors, wall height and aspect etc. is available in the pre-processing past in UMEP\n" + "\n" + "------------\n" + "\n" + "Full manual available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/processor/Outdoor%20Thermal%20Comfort%20SOLWEIG.html" return url def tr(self, string): - return QCoreApplication.translate('Processing', string) + return QCoreApplication.translate("Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/icon_solweig.png") return icon diff --git a/processor/suews_algorithm.py b/processor/suews_algorithm.py index 2f688e2..98ef0f4 100644 --- a/processor/suews_algorithm.py +++ b/processor/suews_algorithm.py @@ -22,33 +22,37 @@ ***************************************************************************/ """ -__author__ = 'Fredrik Lindberg' -__date__ = '2020-04-02' -__copyright__ = '(C) 2020 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2020-04-02" +__copyright__ = "(C) 2020 by Fredrik Lindberg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QVariant + # from qgis.PyQt.QtWidgets import QMessageBox -from qgis.core import (QgsProcessingAlgorithm, - QgsProcessingParameterFile, - QgsProcessingParameterFolderDestination, - QgsProcessingParameterEnum, - QgsProcessingParameterNumber, - QgsProcessingParameterBoolean, - QgsProcessingParameterDefinition, - QgsProcessingException) +from qgis.core import ( + QgsProcessingAlgorithm, + QgsProcessingParameterFile, + QgsProcessingParameterFolderDestination, + QgsProcessingParameterEnum, + QgsProcessingParameterNumber, + QgsProcessingParameterBoolean, + QgsProcessingParameterDefinition, + QgsProcessingException, +) try: from supy import SUEWSSimulation - from supy.data_model import init_config_from_yaml + from supy.data_model import init_config_from_yaml # import supy as sp # from supy import __version__ as ver_supy except: pass from pathlib import Path + # from ..util import f90nml import sys, os from qgis.PyQt.QtGui import QIcon @@ -63,152 +67,414 @@ class ProcessingSuewsAlgorithm(QgsProcessingAlgorithm): This is a processing algorithm for the SUEWS model """ - OUTPUT_DIR = 'OUTPUT_DIR' - INPUT_FILE = 'INPUT_FILE' - - NET = 'NET' - ANTHRO = 'ANTHRO' - STORAGE = 'STORAGE' - OHM = 'OHM' - Z0M = 'Z0M' - Z0H = 'Z0H' - STAB = 'STAB' - SMD = 'SMD' - WU = 'WU' - RSLMETHOD = 'RSLMETHOD' - RSLLEVEL = 'RSLLEVEL' + OUTPUT_DIR = "OUTPUT_DIR" + INPUT_FILE = "INPUT_FILE" + + NET = "NET" + ANTHRO = "ANTHRO" + STORAGE = "STORAGE" + OHM = "OHM" + Z0M = "Z0M" + Z0H = "Z0H" + STAB = "STAB" + SMD = "SMD" + WU = "WU" + RSLMETHOD = "RSLMETHOD" + RSLLEVEL = "RSLLEVEL" # AERO = 'AERO' new Z0H - SNOW = 'SNOW' - - SPINUP = 'SPINUP' - CHUNKBOOL = 'CHUNKBOOL' - CHUNK = 'CHUNK' + SNOW = "SNOW" + + SPINUP = "SPINUP" + CHUNKBOOL = "CHUNKBOOL" + CHUNK = "CHUNK" # TIMERESOUT = 'TIMERESOUT' def initAlgorithm(self, config): - self.net = ((self.tr('0. (OBSERVED) from forcing file'), '0'), - (self.tr('1. (LDOWN_OBSERVED) Modelled (NARP) but Ldown observed'), '1'), - (self.tr('2. (LDOWN_CLOUD) Modelled (NARP), Ldown from cloud cover'), '2'), - (self.tr('3. (LDOWN_AIR) Modelled (NARP), Ldown from Ta and RH (Default)'), '3'),) - self.anthro = ((self.tr('0. (NO_EMISSIONS) = Observed QF from forcing file'), '0'), - (self.tr('1. (L11) = Loridan et al. 2011 linear temp relation'), '1'), - (self.tr('2. (J11) = Järvi et al. 2011 with HDD/CDD (Default)'), '2'), - (self.tr('4. (J19) = Järvi et al. 2019 including metabolism and traffic'), '4')) - self.storage = ((self.tr('0. (OBSERVED) = Uses observed ΔQS from forcing file'), '0'), - (self.tr('1. (OHM_WITHOUT_QF) = Objective Hysteresis Model using Q* only (Default)'), '1'), - (self.tr('5. (EHC) = Explicit Heat Conduction model with separate roof/wall/ground temperatures'), '5'), - (self.tr('6. (DyOHM) = Dynamic Objective Hysteresis Model (Liu et al., 2025) with dynamic coefficients'), '6'), - (self.tr('7. (STEBBS) = use STEBBS storage heat flux for building, others use OHM'), '6')) - self.ohm = ((self.tr('0. (EXCLUDE) = Use Q* only (required when StorageHeatMethod=1) (Default)'), '0'), - (self.tr('1. (INCLUDE) = Use Q*+QF'), '1')) - self.z0m = ((self.tr('1. (FIXED) = Fixed from site parameters'), '1'), - (self.tr('2. (VARIABLE) = Varies with vegetation LAI (Default)'), '2'), - (self.tr('3. (MACDONALD) = MacDonald et al. 1998 morphometric method'), '3'), - (self.tr('4. (LAMBDAP_DEPENDENT) = Varies with plan area fraction'), '4')) - self.z0h = ((self.tr('1. (BRUTSAERT) = Brutsaert (1982) z0h = z0m/10 (see Grimmond & Oke 1986)'), '1'), - (self.tr('2. (KAWAI) = Kawai et al. (2009) formulation (Default)'), '2'), - (self.tr('3. (VOOGT_GRIMMOND) = Voogt and Grimmond (2000) formulation'), '3'), - (self.tr('4. (KANDA) = Kanda et al. (2007) formulation'), '4'), - (self.tr('5. (ADAPTIVE) = Adaptively using z0m based on pervious coverage: if fully pervious, use method 1)'),'5')) - self.stab = ((self.tr('2. Dyer 1974 etc.'), '2'), - (self.tr('3. (CAMPBELL_NORMAN) = Campbell & Norman 1998 formulations (Default)'), '3'), - (self.tr('4. Businger et al. 1971'), '4')) - self.smd = ((self.tr('0. (MODELLED) = Calculated from water balance using soil parameters (Default)'), '0'), - (self.tr('1. (OBSERVED_VOLUMETRIC) = Uses observed volumetric soil moisture (m³/m³) from forcing file'), '1'), - (self.tr('2. (OBSERVED_GRAVIMETRIC) = Uses observed gravimetric soil moisture (kg/kg) from forcing file'), '2')) - self.wu = ((self.tr('0. (MODELLED) = Calculated based on soil moisture deficit and irrigation parameters (Default)'), '0'), - (self.tr('1. (OBSERVED) = Uses observed water use values from forcing file'), '1')) - self.rslmethod = ((self.tr('0. (MOST) = Monin-Obukhov Similarity Theory for homogeneous surfaces'), '0'), - (self.tr('1. (RST) = Roughness Sublayer Theory for heterogeneous urban surfaces'), '1'), - (self.tr('2. (VARIABLE) = Automatic selection based on surface morphology (Default)'), '2')) - self.rsllevel = ((self.tr('0. (NONE) = No local climate adjustments, use forcing file meteorology directly (Default)'), '0'), - (self.tr('1. (BASIC) = Simple adjustments for urban temperature effects on leaf area index and growing degree days'), '1'), - (self.tr('2. ((DETAILED) = Comprehensive feedbacks including moisture stress, urban CO2 dome effects, and modified phenology cycles'), '2')) - - self.addParameter(QgsProcessingParameterFile(self.INPUT_FILE, - self.tr('Input yaml file (.yml)'), extension='yml')) - - self.addParameter(QgsProcessingParameterEnum(self.NET, - self.tr('Method for calculating net all-wave radiation (Q*)'), - options=[i[0] for i in self.net], - defaultValue=3)) - self.addParameter(QgsProcessingParameterEnum(self.ANTHRO, - self.tr('Method for calculating anthropogenic heat flux (QF) and CO2 emissions'), - options=[i[0] for i in self.anthro], - defaultValue=2)) - self.addParameter(QgsProcessingParameterEnum(self.STORAGE, - self.tr('Method for calculating storage heat flux (ΔQS)'), - options=[i[0] for i in self.storage], - defaultValue=1)) - self.addParameter(QgsProcessingParameterEnum(self.OHM, - self.tr('Controls inclusion of anthropogenic heat flux in OHM storage heat calculations'), - options=[i[0] for i in self.ohm], - defaultValue=0)) - self.addParameter(QgsProcessingParameterEnum(self.Z0M, - self.tr('Method for calculating momentum roughness length (z0m)'), - options=[i[0] for i in self.z0m], - defaultValue=1)) - self.addParameter(QgsProcessingParameterEnum(self.Z0H, - self.tr('Method for calculating thermal roughness length (z0h)'), - options=[i[0] for i in self.z0h], - defaultValue=1)) - self.addParameter(QgsProcessingParameterEnum(self.STAB, - self.tr('Atmospheric stability correction functions for momentum and heat fluxes'), - options=[i[0] for i in self.stab], - defaultValue=1)) - self.addParameter(QgsProcessingParameterEnum(self.SMD, - self.tr('Method for determining soil moisture deficit (SMD)'), - options=[i[0] for i in self.smd], - defaultValue=0)) - self.addParameter(QgsProcessingParameterEnum(self.WU, - self.tr('Method for determining external water use (irrigation)'), - options=[i[0] for i in self.wu], - defaultValue=0)) - self.addParameter(QgsProcessingParameterEnum(self.RSLMETHOD, - self.tr('Method for calculating near-surface meteorological diagnostics'), - options=[i[0] for i in self.rslmethod], - defaultValue=2)) - self.addParameter(QgsProcessingParameterEnum(self.RSLLEVEL, - self.tr('Method for incorporating urban microclimate feedbacks on vegetation and evapotranspiration'), - options=[i[0] for i in self.rsllevel], - defaultValue=0)) - self.addParameter(QgsProcessingParameterBoolean(self.SNOW, - self.tr("Use snow module"), - defaultValue=False)) - self.addParameter(QgsProcessingParameterFolderDestination(self.OUTPUT_DIR, - 'Output folder')) - - #Advanced parameters - chunkBool = QgsProcessingParameterBoolean(self.CHUNKBOOL, - self.tr("Devide calculation in chunks to reduce issues with memory running low on your computer. (CURRENTLY NOT ACTIVE)"), - defaultValue=False) - chunkBool.setFlags(chunkBool.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + self.net = ( + (self.tr("0. (OBSERVED) from forcing file"), "0"), + ( + self.tr( + "1. (LDOWN_OBSERVED) Modelled (NARP) but Ldown observed" + ), + "1", + ), + ( + self.tr( + "2. (LDOWN_CLOUD) Modelled (NARP), Ldown from cloud cover" + ), + "2", + ), + ( + self.tr( + "3. (LDOWN_AIR) Modelled (NARP), Ldown from Ta and RH (Default)" + ), + "3", + ), + ) + self.anthro = ( + ( + self.tr("0. (NO_EMISSIONS) = Observed QF from forcing file"), + "0", + ), + ( + self.tr("1. (L11) = Loridan et al. 2011 linear temp relation"), + "1", + ), + ( + self.tr("2. (J11) = Järvi et al. 2011 with HDD/CDD (Default)"), + "2", + ), + ( + self.tr( + "4. (J19) = Järvi et al. 2019 including metabolism and traffic" + ), + "4", + ), + ) + self.storage = ( + ( + self.tr("0. (OBSERVED) = Uses observed ΔQS from forcing file"), + "0", + ), + ( + self.tr( + "1. (OHM_WITHOUT_QF) = Objective Hysteresis Model using Q* only (Default)" + ), + "1", + ), + ( + self.tr( + "5. (EHC) = Explicit Heat Conduction model with separate roof/wall/ground temperatures" + ), + "5", + ), + ( + self.tr( + "6. (DyOHM) = Dynamic Objective Hysteresis Model (Liu et al., 2025) with dynamic coefficients" + ), + "6", + ), + ( + self.tr( + "7. (STEBBS) = use STEBBS storage heat flux for building, others use OHM" + ), + "6", + ), + ) + self.ohm = ( + ( + self.tr( + "0. (EXCLUDE) = Use Q* only (required when StorageHeatMethod=1) (Default)" + ), + "0", + ), + (self.tr("1. (INCLUDE) = Use Q*+QF"), "1"), + ) + self.z0m = ( + (self.tr("1. (FIXED) = Fixed from site parameters"), "1"), + ( + self.tr( + "2. (VARIABLE) = Varies with vegetation LAI (Default)" + ), + "2", + ), + ( + self.tr( + "3. (MACDONALD) = MacDonald et al. 1998 morphometric method" + ), + "3", + ), + ( + self.tr( + "4. (LAMBDAP_DEPENDENT) = Varies with plan area fraction" + ), + "4", + ), + ) + self.z0h = ( + ( + self.tr( + "1. (BRUTSAERT) = Brutsaert (1982) z0h = z0m/10 (see Grimmond & Oke 1986)" + ), + "1", + ), + ( + self.tr( + "2. (KAWAI) = Kawai et al. (2009) formulation (Default)" + ), + "2", + ), + ( + self.tr( + "3. (VOOGT_GRIMMOND) = Voogt and Grimmond (2000) formulation" + ), + "3", + ), + (self.tr("4. (KANDA) = Kanda et al. (2007) formulation"), "4"), + ( + self.tr( + "5. (ADAPTIVE) = Adaptively using z0m based on pervious coverage: if fully pervious, use method 1)" + ), + "5", + ), + ) + self.stab = ( + (self.tr("2. Dyer 1974 etc."), "2"), + ( + self.tr( + "3. (CAMPBELL_NORMAN) = Campbell & Norman 1998 formulations (Default)" + ), + "3", + ), + (self.tr("4. Businger et al. 1971"), "4"), + ) + self.smd = ( + ( + self.tr( + "0. (MODELLED) = Calculated from water balance using soil parameters (Default)" + ), + "0", + ), + ( + self.tr( + "1. (OBSERVED_VOLUMETRIC) = Uses observed volumetric soil moisture (m³/m³) from forcing file" + ), + "1", + ), + ( + self.tr( + "2. (OBSERVED_GRAVIMETRIC) = Uses observed gravimetric soil moisture (kg/kg) from forcing file" + ), + "2", + ), + ) + self.wu = ( + ( + self.tr( + "0. (MODELLED) = Calculated based on soil moisture deficit and irrigation parameters (Default)" + ), + "0", + ), + ( + self.tr( + "1. (OBSERVED) = Uses observed water use values from forcing file" + ), + "1", + ), + ) + self.rslmethod = ( + ( + self.tr( + "0. (MOST) = Monin-Obukhov Similarity Theory for homogeneous surfaces" + ), + "0", + ), + ( + self.tr( + "1. (RST) = Roughness Sublayer Theory for heterogeneous urban surfaces" + ), + "1", + ), + ( + self.tr( + "2. (VARIABLE) = Automatic selection based on surface morphology (Default)" + ), + "2", + ), + ) + self.rsllevel = ( + ( + self.tr( + "0. (NONE) = No local climate adjustments, use forcing file meteorology directly (Default)" + ), + "0", + ), + ( + self.tr( + "1. (BASIC) = Simple adjustments for urban temperature effects on leaf area index and growing degree days" + ), + "1", + ), + ( + self.tr( + "2. ((DETAILED) = Comprehensive feedbacks including moisture stress, urban CO2 dome effects, and modified phenology cycles" + ), + "2", + ), + ) + + self.addParameter( + QgsProcessingParameterFile( + self.INPUT_FILE, + self.tr("Input yaml file (.yml)"), + extension="yml", + ) + ) + + self.addParameter( + QgsProcessingParameterEnum( + self.NET, + self.tr("Method for calculating net all-wave radiation (Q*)"), + options=[i[0] for i in self.net], + defaultValue=3, + ) + ) + self.addParameter( + QgsProcessingParameterEnum( + self.ANTHRO, + self.tr( + "Method for calculating anthropogenic heat flux (QF) and CO2 emissions" + ), + options=[i[0] for i in self.anthro], + defaultValue=2, + ) + ) + self.addParameter( + QgsProcessingParameterEnum( + self.STORAGE, + self.tr("Method for calculating storage heat flux (ΔQS)"), + options=[i[0] for i in self.storage], + defaultValue=1, + ) + ) + self.addParameter( + QgsProcessingParameterEnum( + self.OHM, + self.tr( + "Controls inclusion of anthropogenic heat flux in OHM storage heat calculations" + ), + options=[i[0] for i in self.ohm], + defaultValue=0, + ) + ) + self.addParameter( + QgsProcessingParameterEnum( + self.Z0M, + self.tr( + "Method for calculating momentum roughness length (z0m)" + ), + options=[i[0] for i in self.z0m], + defaultValue=1, + ) + ) + self.addParameter( + QgsProcessingParameterEnum( + self.Z0H, + self.tr( + "Method for calculating thermal roughness length (z0h)" + ), + options=[i[0] for i in self.z0h], + defaultValue=1, + ) + ) + self.addParameter( + QgsProcessingParameterEnum( + self.STAB, + self.tr( + "Atmospheric stability correction functions for momentum and heat fluxes" + ), + options=[i[0] for i in self.stab], + defaultValue=1, + ) + ) + self.addParameter( + QgsProcessingParameterEnum( + self.SMD, + self.tr("Method for determining soil moisture deficit (SMD)"), + options=[i[0] for i in self.smd], + defaultValue=0, + ) + ) + self.addParameter( + QgsProcessingParameterEnum( + self.WU, + self.tr( + "Method for determining external water use (irrigation)" + ), + options=[i[0] for i in self.wu], + defaultValue=0, + ) + ) + self.addParameter( + QgsProcessingParameterEnum( + self.RSLMETHOD, + self.tr( + "Method for calculating near-surface meteorological diagnostics" + ), + options=[i[0] for i in self.rslmethod], + defaultValue=2, + ) + ) + self.addParameter( + QgsProcessingParameterEnum( + self.RSLLEVEL, + self.tr( + "Method for incorporating urban microclimate feedbacks on vegetation and evapotranspiration" + ), + options=[i[0] for i in self.rsllevel], + defaultValue=0, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.SNOW, self.tr("Use snow module"), defaultValue=False + ) + ) + self.addParameter( + QgsProcessingParameterFolderDestination( + self.OUTPUT_DIR, "Output folder" + ) + ) + + # Advanced parameters + chunkBool = QgsProcessingParameterBoolean( + self.CHUNKBOOL, + self.tr( + "Devide calculation in chunks to reduce issues with memory running low on your computer. (CURRENTLY NOT ACTIVE)" + ), + defaultValue=False, + ) + chunkBool.setFlags( + chunkBool.flags() + | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(chunkBool) - chunk = QgsProcessingParameterNumber(self.CHUNK, self.tr('Number of chunks'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(2), optional=True, minValue=0, maxValue=1000) - chunk.setFlags(chunk.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) + chunk = QgsProcessingParameterNumber( + self.CHUNK, + self.tr("Number of chunks"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(2), + optional=True, + minValue=0, + maxValue=1000, + ) + chunk.setFlags( + chunk.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced + ) self.addParameter(chunk) - def processAlgorithm(self, parameters, context, feedback): try: import supy as sp from supy import __version__ as ver_supy except: - raise QgsProcessingException('This plugin requires the supy package ' - 'to be installed OR upgraded. Please consult the FAQ in the manual ' - 'for further information on how to install missing python packages.') + raise QgsProcessingException( + "This plugin requires the supy package " + "to be installed OR upgraded. Please consult the FAQ in the manual " + "for further information on how to install missing python packages." + ) # QMessageBox.critical(None, 'Error', 'This plugin requires the supy package ' # 'to be installed OR upgraded. Please consult the FAQ in the manual ' # 'for further information on how to install missing python packages.') # return - feedback.setProgressText('SuPy version: ' + ver_supy) + feedback.setProgressText("SuPy version: " + ver_supy) self.supylib = sys.modules["supy"].__path__[0] feedback.setProgressText(self.supylib) infile = self.parameterAsString(parameters, self.INPUT_FILE, context) - outfolder = self.parameterAsString(parameters, self.OUTPUT_DIR, context) + outfolder = self.parameterAsString( + parameters, self.OUTPUT_DIR, context + ) net = self.parameterAsString(parameters, self.NET, context) qf = self.parameterAsString(parameters, self.ANTHRO, context) @@ -231,54 +497,79 @@ def processAlgorithm(self, parameters, context, feedback): noOfChunks = self.parameterAsInt(parameters, self.CHUNK, context) feedback.setProgressText("Reading and updating YAML input file") - with open(infile, 'r') as f: + with open(infile, "r") as f: yaml_dict = yaml.load(f, Loader=yaml.SafeLoader) - - yaml_dict['model']['physics']['snowuse']['value'] = int(usesnow) - yaml_dict['model']['physics']['netradiationmethod']['value'] = int(self.net[int(net)][1]) - yaml_dict['model']['physics']['emissionsmethod']['value'] = int(self.anthro[int(qf)][1]) - yaml_dict['model']['physics']['ohmincqf']['value'] = int(self.ohm[int(ohm)][1]) - yaml_dict['model']['physics']['stabilitymethod']['value'] = int(self.stab[int(stab)][1]) - yaml_dict['model']['physics']['storageheatmethod']['value'] = int(self.storage[int(qs)][1]) - yaml_dict['model']['physics']['roughlenmommethod']['value'] = int(self.z0m[int(z0m)][1]) - yaml_dict['model']['physics']['roughlenheatmethod']['value'] = int(self.z0h[int(z0h)][1]) - yaml_dict['model']['physics']['smdmethod']['value'] = int(self.smd[int(smd)][1]) - yaml_dict['model']['physics']['waterusemethod']['value'] = int(self.wu[int(wu)][1]) - yaml_dict['model']['physics']['rslmethod'] = int(self.rslmethod[int(rslmethod)][1]) - yaml_dict['model']['physics']['rsllevel'] = int(self.rsllevel[int(rsllevel)][1]) - yaml_dict['model']['control']['output_file']['path'] = str(outfolder) + "/" - - - with open(infile, 'w') as file: - yaml.dump(yaml_dict, file, sort_keys = False) + + yaml_dict["model"]["physics"]["snowuse"]["value"] = int(usesnow) + yaml_dict["model"]["physics"]["netradiationmethod"]["value"] = int( + self.net[int(net)][1] + ) + yaml_dict["model"]["physics"]["emissionsmethod"]["value"] = int( + self.anthro[int(qf)][1] + ) + yaml_dict["model"]["physics"]["ohmincqf"]["value"] = int( + self.ohm[int(ohm)][1] + ) + yaml_dict["model"]["physics"]["stabilitymethod"]["value"] = int( + self.stab[int(stab)][1] + ) + yaml_dict["model"]["physics"]["storageheatmethod"]["value"] = int( + self.storage[int(qs)][1] + ) + yaml_dict["model"]["physics"]["roughlenmommethod"]["value"] = int( + self.z0m[int(z0m)][1] + ) + yaml_dict["model"]["physics"]["roughlenheatmethod"]["value"] = int( + self.z0h[int(z0h)][1] + ) + yaml_dict["model"]["physics"]["smdmethod"]["value"] = int( + self.smd[int(smd)][1] + ) + yaml_dict["model"]["physics"]["waterusemethod"]["value"] = int( + self.wu[int(wu)][1] + ) + yaml_dict["model"]["physics"]["rslmethod"] = int( + self.rslmethod[int(rslmethod)][1] + ) + yaml_dict["model"]["physics"]["rsllevel"] = int( + self.rsllevel[int(rsllevel)][1] + ) + yaml_dict["model"]["control"]["output_file"]["path"] = ( + str(outfolder) + "/" + ) + + with open(infile, "w") as file: + yaml.dump(yaml_dict, file, sort_keys=False) ##################################################################################### # SuPy feedback.setProgressText("Initiating model") - - #config = sp.data_model.init_config_from_yaml(infile) + + # config = sp.data_model.init_config_from_yaml(infile) config = init_config_from_yaml(infile) df_state_init = config.to_df_state() - + feedback.setProgressText("Loading forcing data") - + grid = df_state_init.index[0] - df_forcing = sp.load_forcing_grid(infile, grid, df_state_init=df_state_init) + df_forcing = sp.load_forcing_grid( + infile, grid, df_state_init=df_state_init + ) if chunkBool: noOfDays = (df_forcing.index.max() - df_forcing.index.min()).days chunkDay = np.ceil(noOfDays / noOfChunks) - feedback.setProgressText("Model run divided into " + str(int(chunkDay)) + ' day period') + feedback.setProgressText( + "Model run divided into " + str(int(chunkDay)) + " day period" + ) else: chunkDay = 3660 - - ##################################################################################### + ##################################################################################### # SuPy initialisation - yaml_path = infile # Path(pathtoplugin + f'/Input/{filecode}_suews_simple.yml') - #from supy.data_model import init_config_from_yaml - #from supy import SUEWSKernelError - + yaml_path = infile # Path(pathtoplugin + f'/Input/{filecode}_suews_simple.yml') + # from supy.data_model import init_config_from_yaml + # from supy import SUEWSKernelError # Create simulation from YAML configuration sim = SUEWSSimulation(yaml_path) @@ -290,11 +581,11 @@ def processAlgorithm(self, parameters, context, feedback): sim.run() # use SuPy function to save results - with open(yaml_path, 'r') as f: + with open(yaml_path, "r") as f: yaml_dict = yaml.load(f, Loader=yaml.SafeLoader) feedback.setProgressText("Saving to disk") - sim.save(yaml_dict['model']['control']['output_file']) + sim.save(yaml_dict["model"]["control"]["output_file"]) # # SuPy simulation OLD # feedback.setProgressText("Running model (QGIS not responsive)") @@ -311,46 +602,50 @@ def processAlgorithm(self, parameters, context, feedback): # path_dir_save = yaml_dict['model']['control']['output_file']['path']) ##################################################################################### - feedback.setProgressText('Model finished') + feedback.setProgressText("Model finished") return {self.OUTPUT_DIR: outfolder} - + def name(self): - return 'Urban Energy Balance: SUEWS' + return "Urban Energy Balance: SUEWS" def displayName(self): - return self.tr('Urban Energy Balance: SUEWS v2026.1.28rc1') + return self.tr("Urban Energy Balance: SUEWS v2026.1.28rc1") def group(self): return self.tr(self.groupId()) def groupId(self): - return 'Processor' + return "Processor" def shortHelpString(self): - return self.tr('SUEWS - Surface Urban Energy and Water Balance Scheme (Järvi et al. 2011, Ward et al. 2016) simulates the urban radiation, ' - 'energy and water balances using commonly measured/modeled meteorological variables and ' - 'information about the surface cover. It utilizes an evaporation-interception approach ' - '(Grimmond et al. 1991), similar to that used in forests, to model evaporation from urban surfaces.
' - '---------------\n' - 'Järvi, L., Grimmond, C.S.B., and Christen, A. The surface urban energy and water balance scheme (SUEWS): Evaluation in Los Angeles and Vancouver. J. Hydrol., 411(3-4):219-237, December 2011. doi:10.1016/j.jhydrol.2011.10.001.' - '\n' - 'Ward, H.C., Kotthaus, S., Järvi, L., and Grimmond, C.S.B. Surface urban energy and water balance scheme (SUEWS): Development and evaluation at two UK sites. Urban Clim., 18:1-32, December 2016. doi:10.1016/j.uclim.2016.05.001.' - '\n' - 'Grimmond, C. S. B. and Oke, T. R. An evapotranspiration-interception model for urban areas. Water Resour. Res., 27(7):1739-1755, July 1991. doi:10.1029/91wr00557.' - '\n' - '---------------\n' - 'Full manual available via the Help-button.') + return self.tr( + "SUEWS - Surface Urban Energy and Water Balance Scheme (Järvi et al. 2011, Ward et al. 2016) simulates the urban radiation, " + "energy and water balances using commonly measured/modeled meteorological variables and " + "information about the surface cover. It utilizes an evaporation-interception approach " + "(Grimmond et al. 1991), similar to that used in forests, to model evaporation from urban surfaces.
" + "---------------\n" + "Järvi, L., Grimmond, C.S.B., and Christen, A. The surface urban energy and water balance scheme (SUEWS): Evaluation in Los Angeles and Vancouver. J. Hydrol., 411(3-4):219-237, December 2011. doi:10.1016/j.jhydrol.2011.10.001." + "\n" + "Ward, H.C., Kotthaus, S., Järvi, L., and Grimmond, C.S.B. Surface urban energy and water balance scheme (SUEWS): Development and evaluation at two UK sites. Urban Clim., 18:1-32, December 2016. doi:10.1016/j.uclim.2016.05.001." + "\n" + "Grimmond, C. S. B. and Oke, T. R. An evapotranspiration-interception model for urban areas. Water Resour. Res., 27(7):1739-1755, July 1991. doi:10.1029/91wr00557." + "\n" + "---------------\n" + "Full manual available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/processor/Urban%20Energy%20Balance%20Urban%20Energy%20Balance%20(SUEWS.BLUEWS,%20advanced).html" return url - + def tr(self, string): - return QCoreApplication.translate('Processing', string) + return QCoreApplication.translate("Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/SuewsLogo.png") return icon diff --git a/processor/target_algorithm.py b/processor/target_algorithm.py index 9ea9abf..b93d3a5 100644 --- a/processor/target_algorithm.py +++ b/processor/target_algorithm.py @@ -1,20 +1,22 @@ # -*- coding: utf-8 -*- -__author__ = 'Fredrik Lindberg' -__date__ = '2021-02-04' -__copyright__ = '(C) 2021 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2021-02-04" +__copyright__ = "(C) 2021 by Fredrik Lindberg" -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QVariant -from qgis.core import (QgsProcessing, - QgsProcessingAlgorithm, - QgsProcessingParameterString, - QgsProcessingParameterBoolean, - QgsProcessingParameterFile, - QgsProcessingParameterDateTime, - QgsProcessingParameterFeatureSource, - QgsProcessingException,) +from qgis.core import ( + QgsProcessing, + QgsProcessingAlgorithm, + QgsProcessingParameterString, + QgsProcessingParameterBoolean, + QgsProcessingParameterFile, + QgsProcessingParameterDateTime, + QgsProcessingParameterFeatureSource, + QgsProcessingException, +) from qgis.PyQt.QtGui import QIcon from osgeo import osr @@ -32,7 +34,7 @@ import traceback import math -#from ..functions.target import Target +# from ..functions.target import Target # from target import Target # from .target.scripts.toolkit import Target try: @@ -46,83 +48,141 @@ class ProcessingTargetProcessorAlgorithm(QgsProcessingAlgorithm): """ This algorithm make use of UWG for the processing toolbox """ - - INPUT_FOLDER = 'INPUT_FOLDER' - INPUT_POLYGONLAYER = 'INPUT_POLYGONLAYER' + + INPUT_FOLDER = "INPUT_FOLDER" + INPUT_POLYGONLAYER = "INPUT_POLYGONLAYER" # ID_FIELD = 'ID_FIELD' - START_DATE = 'START_DATE' - START_DATE_INTEREST = 'START_DATE_INTEREST' - STOP_DATE_INTEREST = 'STOP_DATE_INTEREST' - INPUT_MET = 'INPUT_MET' - OUTPUT_DIR = 'OUTPUT_DIR' - OUTPUT_CSV = 'OUTPUT_CSV' - OUTPUT_UMEP = 'OUTPUT_UMEP' + START_DATE = "START_DATE" + START_DATE_INTEREST = "START_DATE_INTEREST" + STOP_DATE_INTEREST = "STOP_DATE_INTEREST" + INPUT_MET = "INPUT_MET" + OUTPUT_DIR = "OUTPUT_DIR" + OUTPUT_CSV = "OUTPUT_CSV" + OUTPUT_UMEP = "OUTPUT_UMEP" # DTSIM = 'DTSIM' - MOD_LDOWN = 'MOD_LDOWN' - RUN_NAME = 'RUN_NAME' - + MOD_LDOWN = "MOD_LDOWN" + RUN_NAME = "RUN_NAME" def initAlgorithm(self, config): - self.addParameter(QgsProcessingParameterFile(self.INPUT_FOLDER, - self.tr('Path to folder where TARGET input files are located (Site name folder)'), - QgsProcessingParameterFile.Behavior.Folder)) - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_POLYGONLAYER, - self.tr('Vector polygon grid'), - [QgsProcessing.SourceType.TypeVector])) + self.addParameter( + QgsProcessingParameterFile( + self.INPUT_FOLDER, + self.tr( + "Path to folder where TARGET input files are located (Site name folder)" + ), + QgsProcessingParameterFile.Behavior.Folder, + ) + ) + self.addParameter( + QgsProcessingParameterFeatureSource( + self.INPUT_POLYGONLAYER, + self.tr("Vector polygon grid"), + [QgsProcessing.SourceType.TypeVector], + ) + ) # self.addParameter(QgsProcessingParameterField(self.ID_FIELD, # self.tr('ID field'), '', self.INPUT_POLYGONLAYER, # QgsProcessingParameterField.Numeric)) - self.addParameter(QgsProcessingParameterString(self.RUN_NAME, - self.tr('Run name'))) - self.addParameter(QgsProcessingParameterDateTime(self.START_DATE, - self.tr('Start date of simulation (should be a minimum of 24 hours prior to date for period of interest)'), - QgsProcessingParameterDateTime.Type.Date)) - self.addParameter(QgsProcessingParameterDateTime(self.START_DATE_INTEREST, - self.tr('Start date for period of interest'), - QgsProcessingParameterDateTime.Type.Date)) - self.addParameter(QgsProcessingParameterDateTime(self.STOP_DATE_INTEREST, - self.tr('End date for period of interest'), - QgsProcessingParameterDateTime.Type.Date)) + self.addParameter( + QgsProcessingParameterString(self.RUN_NAME, self.tr("Run name")) + ) + self.addParameter( + QgsProcessingParameterDateTime( + self.START_DATE, + self.tr( + "Start date of simulation (should be a minimum of 24 hours prior to date for period of interest)" + ), + QgsProcessingParameterDateTime.Type.Date, + ) + ) + self.addParameter( + QgsProcessingParameterDateTime( + self.START_DATE_INTEREST, + self.tr("Start date for period of interest"), + QgsProcessingParameterDateTime.Type.Date, + ) + ) + self.addParameter( + QgsProcessingParameterDateTime( + self.STOP_DATE_INTEREST, + self.tr("End date for period of interest"), + QgsProcessingParameterDateTime.Type.Date, + ) + ) # self.addParameter(QgsProcessingParameterNumber(self.NDAYS, # self.tr('Number of days to run from period of interest'), # QgsProcessingParameterNumber.Integer, # QVariant(5), False, minValue=1, maxValue=365)) - self.addParameter(QgsProcessingParameterFile(self.INPUT_MET, - self.tr('Input meteorological file (UMEP-formatted textfile)'), - extension = 'txt')) + self.addParameter( + QgsProcessingParameterFile( + self.INPUT_MET, + self.tr("Input meteorological file (UMEP-formatted textfile)"), + extension="txt", + ) + ) # self.addParameter(QgsProcessingParameterNumber(self.DTSIM, # self.tr('Simulation time step in minutes'), # QgsProcessingParameterNumber.Integer, # QVariant(300), False, minValue=1, maxValue=1440)) - self.addParameter(QgsProcessingParameterBoolean(self.MOD_LDOWN, - self.tr('Estimate incoming longwave radiation from air temperture and relative humidity'), - defaultValue=False)) + self.addParameter( + QgsProcessingParameterBoolean( + self.MOD_LDOWN, + self.tr( + "Estimate incoming longwave radiation from air temperture and relative humidity" + ), + defaultValue=False, + ) + ) # output # self.addParameter(QgsProcessingParameterFolderDestination(self.OUTPUT_DIR, - # self.tr('Output folder'))) - self.addParameter(QgsProcessingParameterBoolean(self.OUTPUT_CSV, - self.tr('Save output as .csv text files'))) - self.addParameter(QgsProcessingParameterBoolean(self.OUTPUT_UMEP, - self.tr('Save output in UMEP-specific format (required for the TARGET Analyzer)'))) - - self.plugin_dir = os.path.dirname(__file__) + # self.tr('Output folder'))) + self.addParameter( + QgsProcessingParameterBoolean( + self.OUTPUT_CSV, self.tr("Save output as .csv text files") + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.OUTPUT_UMEP, + self.tr( + "Save output in UMEP-specific format (required for the TARGET Analyzer)" + ), + ) + ) + self.plugin_dir = os.path.dirname(__file__) def processAlgorithm(self, parameters, context, feedback): # InputParameters - inputDir = self.parameterAsString(parameters, self.INPUT_FOLDER, context) - inputPolygonlayer = self.parameterAsVectorLayer(parameters, self.INPUT_POLYGONLAYER, context) - startDate = self.parameterAsString(parameters, self.START_DATE, context) - startDateInterest = self.parameterAsString(parameters, self.START_DATE_INTEREST, context) - stopDateInterest = self.parameterAsString(parameters, self.STOP_DATE_INTEREST, context) + inputDir = self.parameterAsString( + parameters, self.INPUT_FOLDER, context + ) + inputPolygonlayer = self.parameterAsVectorLayer( + parameters, self.INPUT_POLYGONLAYER, context + ) + startDate = self.parameterAsString( + parameters, self.START_DATE, context + ) + startDateInterest = self.parameterAsString( + parameters, self.START_DATE_INTEREST, context + ) + stopDateInterest = self.parameterAsString( + parameters, self.STOP_DATE_INTEREST, context + ) inputMet = self.parameterAsString(parameters, self.INPUT_MET, context) # outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) - outputCSV = self.parameterAsBoolean(parameters, self.OUTPUT_CSV, context) + outputCSV = self.parameterAsBoolean( + parameters, self.OUTPUT_CSV, context + ) # dtSim = self.parameterAsDouble(parameters, self.DTSIM, context) - mod_Ldown = self.parameterAsBoolean(parameters, self.MOD_LDOWN, context) + mod_Ldown = self.parameterAsBoolean( + parameters, self.MOD_LDOWN, context + ) runName = self.parameterAsString(parameters, self.RUN_NAME, context) - umepformat = self.parameterAsBoolean(parameters, self.OUTPUT_UMEP,context) + umepformat = self.parameterAsBoolean( + parameters, self.OUTPUT_UMEP, context + ) # getting extent, lat lon, and number of x and y grids vlayer = inputPolygonlayer ext = vlayer.extent() @@ -131,10 +191,22 @@ def processAlgorithm(self, parameters, context, feedback): grid_crs = osr.SpatialReference() grid_crs.ImportFromWkt(vlayer.crs().toWkt()) - grid_unit = grid_crs.GetAttrValue('UNIT') - possible_units_metre = ['metre', 'Metre', 'metres', 'Metres', 'meter', 'Meter', 'meters', 'Meters', 'm'] + grid_unit = grid_crs.GetAttrValue("UNIT") + possible_units_metre = [ + "metre", + "Metre", + "metres", + "Metres", + "meter", + "Meter", + "meters", + "Meters", + "m", + ] if not grid_unit in possible_units_metre: - raise QgsProcessingException('Error! Raster projection is not in meters or feet. Please reproject.') + raise QgsProcessingException( + "Error! Raster projection is not in meters or feet. Please reproject." + ) features = vlayer.getFeatures() for feature in features: @@ -144,75 +216,117 @@ def processAlgorithm(self, parameters, context, feedback): nGridY = int(yExt / gridsize) break - latmin, lonmin = xy2latlon(vlayer.crs().toWkt(), ext.xMinimum(), ext.yMinimum()) - latmax, lonmax = xy2latlon(vlayer.crs().toWkt(), ext.xMaximum(), ext.yMaximum()) + latmin, lonmin = xy2latlon( + vlayer.crs().toWkt(), ext.xMinimum(), ext.yMinimum() + ) + latmax, lonmax = xy2latlon( + vlayer.crs().toWkt(), ext.xMaximum(), ext.yMaximum() + ) # Converting UMEP met-file to target met-file try: - metfile= pd.read_csv(inputMet, sep='\s+') + metfile = pd.read_csv(inputMet, sep="\s+") except: - raise QgsProcessingException("Error: Make sure format of meteorological file is correct. You can" - "prepare your data by using 'Prepare Existing Data' in " - "the Pre-processor") - - metfile['datetime'] = pd.to_datetime(metfile[['%iy','id','it','imin']].astype(str).agg('-'.join, axis=1), format='%Y-%j-%H-%M') - metfile=metfile[['datetime', 'Td', 'RH', 'Wind', 'press','Kdn','ldown']] - metfile.columns = ['datetime', 'Ta', 'RH', 'WS', 'P','Kd','Ld'] - startmetfile = metfile['datetime'].min() - endmetfile = metfile['datetime'].max() - t = metfile['datetime'].iloc[1]-metfile['datetime'].iloc[0] - tdiff = int(t.total_seconds()/60) - metfile.set_index('datetime', inplace=True) - metfile.to_csv(inputDir + '/input/MET/' + runName + '_metdata.txt',sep=',',header='datetime,Ta,RH,WS,P,Kd,Ld') + raise QgsProcessingException( + "Error: Make sure format of meteorological file is correct. You can" + "prepare your data by using 'Prepare Existing Data' in " + "the Pre-processor" + ) + + metfile["datetime"] = pd.to_datetime( + metfile[["%iy", "id", "it", "imin"]] + .astype(str) + .agg("-".join, axis=1), + format="%Y-%j-%H-%M", + ) + metfile = metfile[ + ["datetime", "Td", "RH", "Wind", "press", "Kdn", "ldown"] + ] + metfile.columns = ["datetime", "Ta", "RH", "WS", "P", "Kd", "Ld"] + startmetfile = metfile["datetime"].min() + endmetfile = metfile["datetime"].max() + t = metfile["datetime"].iloc[1] - metfile["datetime"].iloc[0] + tdiff = int(t.total_seconds() / 60) + metfile.set_index("datetime", inplace=True) + metfile.to_csv( + inputDir + "/input/MET/" + runName + "_metdata.txt", + sep=",", + header="datetime,Ta,RH,WS,P,Kd,Ld", + ) # creating config.ini - cfM = read_config(self.plugin_dir + '/configtarget.ini') - - cfM['work_dir'] = os.path.dirname(inputDir) - cfM['para_json_path'] = inputDir + '/parameters.json' - cfM['site_name'] = os.path.basename(inputDir) - cfM['run_name'] = runName - cfM['inpt_met_file'] = runName + '_metdata.txt' - cfM['inpt_lc_file'] = 'lc_target.txt' - cfM['date_fmt'] = '%Y-%m-%d %H:%M:%S' - cfM['timestep'] = str(tdiff) # timestep is set from input forcingfile - cfM['include roofs'] = 'Y' + cfM = read_config(self.plugin_dir + "/configtarget.ini") + + cfM["work_dir"] = os.path.dirname(inputDir) + cfM["para_json_path"] = inputDir + "/parameters.json" + cfM["site_name"] = os.path.basename(inputDir) + cfM["run_name"] = runName + cfM["inpt_met_file"] = runName + "_metdata.txt" + cfM["inpt_lc_file"] = "lc_target.txt" + cfM["date_fmt"] = "%Y-%m-%d %H:%M:%S" + cfM["timestep"] = str(tdiff) # timestep is set from input forcingfile + cfM["include roofs"] = "Y" if mod_Ldown: - cfM['mod_ldown'] = 'Y' + cfM["mod_ldown"] = "Y" else: - cfM['mod_ldown'] = 'N' - if -999 in metfile['Ld'].values: - cfM['mod_ldown'] = 'Y' - feedback.pushWarning("-999 found in Ldown. Ldown will be modelled.") - - cfM['domaindim'] = str(nGridX) + ',' + str(nGridY) - cfM['latedge'] = str(latmin) - cfM['lonedge'] = str(lonmin) - cfM['latresolution'] = str(abs(latmin - latmax)) - cfM['lonresolution'] = str(abs(lonmin - lonmax)) - cfM['date1a'] = datetime.datetime.strptime(startDate, "%Y-%m-%d").strftime("%Y,%m,%d") + ',0' - cfM['date1'] = datetime.datetime.strptime(startDateInterest, "%Y-%m-%d").strftime("%Y,%m,%d") + ',0' - cfM['date2'] = datetime.datetime.strptime(stopDateInterest, "%Y-%m-%d").strftime("%Y,%m,%d") + ',0' - - #TODO: Check so that dates are within forcing data + cfM["mod_ldown"] = "N" + if -999 in metfile["Ld"].values: + cfM["mod_ldown"] = "Y" + feedback.pushWarning( + "-999 found in Ldown. Ldown will be modelled." + ) + + cfM["domaindim"] = str(nGridX) + "," + str(nGridY) + cfM["latedge"] = str(latmin) + cfM["lonedge"] = str(lonmin) + cfM["latresolution"] = str(abs(latmin - latmax)) + cfM["lonresolution"] = str(abs(lonmin - lonmax)) + cfM["date1a"] = ( + datetime.datetime.strptime(startDate, "%Y-%m-%d").strftime( + "%Y,%m,%d" + ) + + ",0" + ) + cfM["date1"] = ( + datetime.datetime.strptime(startDateInterest, "%Y-%m-%d").strftime( + "%Y,%m,%d" + ) + + ",0" + ) + cfM["date2"] = ( + datetime.datetime.strptime(stopDateInterest, "%Y-%m-%d").strftime( + "%Y,%m,%d" + ) + + ",0" + ) + + # TODO: Check so that dates are within forcing data date1a = datetime.datetime.strptime(startDate, "%Y-%m-%d") date1 = datetime.datetime.strptime(startDateInterest, "%Y-%m-%d") date2 = datetime.datetime.strptime(stopDateInterest, "%Y-%m-%d") if date1a >= date1: - raise QgsProcessingException("Error: Start date should be at least 24h before Start date for period of interest") + raise QgsProcessingException( + "Error: Start date should be at least 24h before Start date for period of interest" + ) if date1 >= date2: - raise QgsProcessingException("Error: Start date of Interest should be later than End date for period of interest") + raise QgsProcessingException( + "Error: Start date of Interest should be later than End date for period of interest" + ) if startmetfile <= date1a <= endmetfile: - test =4 + test = 4 else: - raise QgsProcessingException("Error: Start date selected is not within dates available in the meteorological focring file") + raise QgsProcessingException( + "Error: Start date selected is not within dates available in the meteorological focring file" + ) if startmetfile <= date2 <= endmetfile: - test =4 + test = 4 else: - raise QgsProcessingException("Error: End date selected is not within dates available in the meteorological focring file") + raise QgsProcessingException( + "Error: End date selected is not within dates available in the meteorological focring file" + ) write_config_file(cfM, inputDir) @@ -220,58 +334,99 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText("Model calculation started.") start = datetime.datetime.now() tar = Target( - os.path.join(inputDir, "config.ini"), # passing the simulation's config file - progress=True # show progress bars in console + os.path.join( + inputDir, "config.ini" + ), # passing the simulation's config file + progress=True, # show progress bars in console ) tar.load_config() # run simulation if outputCSV: - tar.run_simulation(save_csv=True) # save model results in csv format + tar.run_simulation( + save_csv=True + ) # save model results in csv format else: tar.run_simulation(save_csv=False) - + # save parameters and config used for simulation tar.save_simulation_parameters() end = datetime.datetime.now() - start - feedback.setProgressText("Model calculation finished. Output is found in " + inputDir + "/output") - feedback.setProgressText("Model calculation time: " + str(end.total_seconds()) + " seconds") + feedback.setProgressText( + "Model calculation finished. Output is found in " + + inputDir + + "/output" + ) + feedback.setProgressText( + "Model calculation time: " + str(end.total_seconds()) + " seconds" + ) - #saving output as umep formatted metfile + # saving output as umep formatted metfile if umepformat: - feedback.setProgressText('Saving data in UMEP formatted text-files.') - header = '%iy id it imin Q* QH QE Qs Qf Wind RH Td press rain ' \ - ' Kdn snow ldown fcld wuh xsmd lai_hr Kdiff Kdir Wd' - numformat = '%d %d %d %d %.2f %.2f %.2f %.2f %.2f %.5f %.2f %.2f %.2f %.2f %.2f %.2f %.2f ' \ - '%.2f %.2f %.2f %.2f %.2f %.2f %.2f' - - dataout = np.load(inputDir + "/output/" + runName + '.npy', allow_pickle=True) - dfin = pd.read_csv(inputMet, sep='\s+') - dfin['datetime'] = pd.to_datetime(dfin[['%iy', 'id', 'it', 'imin']].astype(str).agg('-'.join, axis=1), format='%Y-%j-%H-%M') - dfin.set_index('datetime', inplace=True) - np.savetxt(inputDir + "/output/" + runName + '_metdata_UMEP.txt', dfin, fmt=numformat, header=header, comments='') + feedback.setProgressText( + "Saving data in UMEP formatted text-files." + ) + header = ( + "%iy id it imin Q* QH QE Qs Qf Wind RH Td press rain " + " Kdn snow ldown fcld wuh xsmd lai_hr Kdiff Kdir Wd" + ) + numformat = ( + "%d %d %d %d %.2f %.2f %.2f %.2f %.2f %.5f %.2f %.2f %.2f %.2f %.2f %.2f %.2f " + "%.2f %.2f %.2f %.2f %.2f %.2f %.2f" + ) + + dataout = np.load( + inputDir + "/output/" + runName + ".npy", allow_pickle=True + ) + dfin = pd.read_csv(inputMet, sep="\s+") + dfin["datetime"] = pd.to_datetime( + dfin[["%iy", "id", "it", "imin"]] + .astype(str) + .agg("-".join, axis=1), + format="%Y-%j-%H-%M", + ) + dfin.set_index("datetime", inplace=True) + np.savetxt( + inputDir + "/output/" + runName + "_metdata_UMEP.txt", + dfin, + fmt=numformat, + header=header, + comments="", + ) index = 1 - for f in range(0, dataout.shape[1]): # looping through each grid saving data + for f in range( + 0, dataout.shape[1] + ): # looping through each grid saving data feedback.setProgress(int((index * 100) / dataout.shape[1])) if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") break - gridID = dataout[:,f,0][0][0] - dfout001 = pd.DataFrame(dataout[:,f,0]) - dfout001['date'] = pd.to_datetime(dfout001['date']) - dfout001.set_index('date', inplace=True) - dfoutmod = dfout001['Ta'].loc[date1:date2] - dfin.loc[date1:date2,['Td']] = dfoutmod - np.savetxt(inputDir + "/output/" + runName + '_' + str(int(gridID)) + '_UMEP_TARGET.txt', dfin, fmt=numformat, header=header, comments='') - + gridID = dataout[:, f, 0][0][0] + dfout001 = pd.DataFrame(dataout[:, f, 0]) + dfout001["date"] = pd.to_datetime(dfout001["date"]) + dfout001.set_index("date", inplace=True) + dfoutmod = dfout001["Ta"].loc[date1:date2] + dfin.loc[date1:date2, ["Td"]] = dfoutmod + np.savetxt( + inputDir + + "/output/" + + runName + + "_" + + str(int(gridID)) + + "_UMEP_TARGET.txt", + dfin, + fmt=numformat, + header=header, + comments="", + ) + feedback.setProgressText("Process finished") return {self.INPUT_FOLDER: inputDir} - def name(self): - return 'Urban Heat Island: TARGET' + return "Urban Heat Island: TARGET" def displayName(self): return self.tr(self.name()) @@ -280,36 +435,40 @@ def group(self): return self.tr(self.groupId()) def groupId(self): - return 'Processor' + return "Processor" def shortHelpString(self): - return self.tr('THIS TOOL IS EXPERIMENTAL' - '\n' - 'TARGET (The Air-temperature Response to Green blue-infrastructure Evaluaition Tool) is a simple modelling framework used to examine ' - 'intra urban climate. It has specifically been developed as an efficient, easy-to-use model albe to investigate ' - 'heat mitigation effects of green and blue infrastructure within urban areas but can also be used to model the canopy urban heat island ' - '(Broadbent et al. 2019). ' - 'Possibilities to model mutiple grids or a single location is available.\n' - '\n' - 'For more detailed information during execution, open the QGIS Python console (Plugins>Python Console).' - '\n' - 'NOTE: This plugin requires the target-py python library. If UMEP was installed without issues, target-py should be installed on your system. If missing on your system, instructions on how to install missing python libraries using the pip command can be found here: ' - 'https://umep-docs.readthedocs.io/en/latest/Getting_Started.html' - '\n' - '----------------------\n' - 'Full manual is available via the Help-button.') + return self.tr( + "THIS TOOL IS EXPERIMENTAL" + "\n" + "TARGET (The Air-temperature Response to Green blue-infrastructure Evaluaition Tool) is a simple modelling framework used to examine " + "intra urban climate. It has specifically been developed as an efficient, easy-to-use model albe to investigate " + "heat mitigation effects of green and blue infrastructure within urban areas but can also be used to model the canopy urban heat island " + '(Broadbent et al. 2019). ' + "Possibilities to model mutiple grids or a single location is available.\n" + "\n" + "For more detailed information during execution, open the QGIS Python console (Plugins>Python Console)." + "\n" + "NOTE: This plugin requires the target-py python library. If UMEP was installed without issues, target-py should be installed on your system. If missing on your system, instructions on how to install missing python libraries using the pip command can be found here: " + 'https://umep-docs.readthedocs.io/en/latest/Getting_Started.html' + "\n" + "----------------------\n" + "Full manual is available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/processor/Urban%20Heat%20Island%20TARGET.html" return url def tr(self, string): - return QCoreApplication.translate('Processing', string) + return QCoreApplication.translate("Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/icon_uwg.png") return icon def createInstance(self): - return ProcessingTargetProcessorAlgorithm() \ No newline at end of file + return ProcessingTargetProcessorAlgorithm() diff --git a/processor/urock_processing_algorithm.py b/processor/urock_processing_algorithm.py index e944118..2d4677a 100644 --- a/processor/urock_processing_algorithm.py +++ b/processor/urock_processing_algorithm.py @@ -22,33 +22,36 @@ ***************************************************************************/ """ -__author__ = 'Jérémy Bernard / University of Gothenburg' -__date__ = '2021-10-04' -__copyright__ = '(C) 2021 by Jérémy Bernard / University of Gothenburg' +__author__ = "Jérémy Bernard / University of Gothenburg" +__date__ = "2021-10-04" +__copyright__ = "(C) 2021 by Jérémy Bernard / University of Gothenburg" # This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" import os from qgis.PyQt.QtWidgets import QMessageBox from qgis.PyQt.QtCore import QCoreApplication -from qgis.core import (QgsProcessing, - QgsProcessingAlgorithm, - QgsProcessingParameterField, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterNumber, - QgsProcessingParameterFolderDestination, - QgsProcessingParameterString, - QgsProcessingParameterRasterLayer, - QgsProcessingParameterBoolean, - QgsRasterLayer, - QgsVectorLayer, - QgsProject, - QgsProcessingContext, - QgsProcessingParameterEnum, - QgsProcessingParameterFile, - QgsProcessingException) +from qgis.core import ( + QgsProcessing, + QgsProcessingAlgorithm, + QgsProcessingParameterField, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterNumber, + QgsProcessingParameterFolderDestination, + QgsProcessingParameterString, + QgsProcessingParameterRasterLayer, + QgsProcessingParameterBoolean, + QgsRasterLayer, + QgsVectorLayer, + QgsProject, + QgsProcessingContext, + QgsProcessingParameterEnum, + QgsProcessingParameterFile, + QgsProcessingException, +) + # qgis.utils import iface from pathlib import Path import pandas as pd @@ -62,15 +65,20 @@ from ..functions.URock import MainCalculation from ..functions.URock.GlobalVariables import * -from ..functions.URock.H2gisConnection import getJavaDir, setJavaDir, saveJavaDir +from ..functions.URock.H2gisConnection import ( + getJavaDir, + setJavaDir, + saveJavaDir, +) from ..functions.URock import WriteMetadataURock from ..functions.URock import DataUtil + # All SQL identifiers are validated via saf_id() to prevent injection. # No user input is used directly in SQL construction. def saf_id(name): if name: - if not re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', name): + if not re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", name): raise ValueError(f"Invalid SQL identifier: {name}") return name @@ -95,14 +103,14 @@ class URockAlgorithm(QgsProcessingAlgorithm): # Input variables # JAVA_PATH = "JAVA_PATH" - BUILDING_TABLE_NAME = 'BUILDINGS' + BUILDING_TABLE_NAME = "BUILDINGS" VEGETATION_TABLE_NAME = "VEGETATION" INPUT_WIND_HEIGHT = "INPUT_WIND_HEIGHT" INPUT_WIND_SPEED = "INPUT_WIND_SPEED" INPUT_WIND_DIRECTION = "INPUT_WIND_DIRECTION" HORIZONTAL_RESOLUTION = "HORIZONTAL_RESOLUTION" VERTICAL_RESOLUTION = "VERTICAL_RESOLUTION" - #ID_FIELD_BUILD = "ID_FIELD_BUILD" + # ID_FIELD_BUILD = "ID_FIELD_BUILD" HEIGHT_FIELD_BUILD = "HEIGHT_FIELD_BUILD" # ID_FIELD_VEG = "ID_FIELD_VEG" VEGETATION_CROWN_BASE_HEIGHT = "VEGETATION_CROWN_BASE_HEIGHT" @@ -113,38 +121,42 @@ class URockAlgorithm(QgsProcessingAlgorithm): RASTER_OUTPUT = "RASTER_OUTPUT" INPUT_PROFILE_TYPE = "INPUT_PROFILE_TYPE" INPUT_PROFILE_FILE = "INPUT_PROFILE_FILE" - LIST_OF_PROFILES = pd.Series(['power', 'urban', 'user']) + LIST_OF_PROFILES = pd.Series(["power", "urban", "user"]) - # Output variables + # Output variables OUTPUT_DIRECTORY = "UROCK_OUTPUT" OUTPUT_FILENAME = "OUTPUT_FILENAME" SAVE_RASTER = "SAVE_RASTER" SAVE_VECTOR = "SAVE_VECTOR" SAVE_NETCDF = "SAVE_NETCDF" LOAD_OUTPUT = "LOAD_OUTPUT" - + def initAlgorithm(self, config): """ Here we define the inputs and output of the algorithm, along with some other properties. """ - + # We add the input parameters # First the layers used as input and output self.addParameter( QgsProcessingParameterFeatureSource( self.BUILDING_TABLE_NAME, - self.tr('Building polygons'), + self.tr("Building polygons"), [QgsProcessing.SourceType.TypeVectorPolygon], - optional = True)) + optional=True, + ) + ) self.addParameter( QgsProcessingParameterField( self.HEIGHT_FIELD_BUILD, - self.tr('Building height field'), + self.tr("Building height field"), None, self.BUILDING_TABLE_NAME, QgsProcessingParameterField.DataType.Numeric, - optional = True)) + optional=True, + ) + ) # self.addParameter( # QgsProcessingParameterField( # self.ID_FIELD_BUILD, @@ -156,130 +168,168 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterFeatureSource( self.VEGETATION_TABLE_NAME, - self.tr('Vegetation polygons'), + self.tr("Vegetation polygons"), [QgsProcessing.SourceType.TypeVectorPolygon], - optional=True)) + optional=True, + ) + ) self.addParameter( QgsProcessingParameterField( self.VEGETATION_CROWN_TOP_HEIGHT, - self.tr('Vegetation crown top height field'), + self.tr("Vegetation crown top height field"), None, self.VEGETATION_TABLE_NAME, QgsProcessingParameterField.DataType.Numeric, - optional = True)) + optional=True, + ) + ) self.addParameter( QgsProcessingParameterField( self.VEGETATION_CROWN_BASE_HEIGHT, - self.tr('Vegetation crown base height field'), + self.tr("Vegetation crown base height field"), None, self.VEGETATION_TABLE_NAME, QgsProcessingParameterField.DataType.Numeric, - optional = True)) + optional=True, + ) + ) self.addParameter( QgsProcessingParameterField( self.ATTENUATION_FIELD, - self.tr('Vegetation wind attenuation factor'), + self.tr("Vegetation wind attenuation factor"), None, self.VEGETATION_TABLE_NAME, QgsProcessingParameterField.DataType.Numeric, - optional = True)) + optional=True, + ) + ) # Then the informations related to calculation self.addParameter( QgsProcessingParameterFile( self.INPUT_PROFILE_FILE, - self.tr('Vertical wind profile file (.csv)'), - defaultValue = '', - extension='csv', - optional = True)) + self.tr("Vertical wind profile file (.csv)"), + defaultValue="", + extension="csv", + optional=True, + ) + ) self.addParameter( - QgsProcessingParameterEnum( - self.INPUT_PROFILE_TYPE, - self.tr('Vertical wind profile type'), - self.LIST_OF_PROFILES.values, - defaultValue=0, - optional = True)) + QgsProcessingParameterEnum( + self.INPUT_PROFILE_TYPE, + self.tr("Vertical wind profile type"), + self.LIST_OF_PROFILES.values, + defaultValue=0, + optional=True, + ) + ) self.addParameter( QgsProcessingParameterNumber( self.INPUT_WIND_HEIGHT, - self.tr('Height of the reference wind speed (m)'), + self.tr("Height of the reference wind speed (m)"), QgsProcessingParameterNumber.Type.Double, 10, - True)) + True, + ) + ) self.addParameter( QgsProcessingParameterNumber( self.INPUT_WIND_SPEED, - self.tr('Wind speed at the reference height (m/s)'), + self.tr("Wind speed at the reference height (m/s)"), QgsProcessingParameterNumber.Type.Double, 2, - True)) + True, + ) + ) self.addParameter( QgsProcessingParameterNumber( self.INPUT_WIND_DIRECTION, - self.tr('Wind direction (° clock-wise from North)'), + self.tr("Wind direction (° clock-wise from North)"), QgsProcessingParameterNumber.Type.Double, 45, - False)) + False, + ) + ) self.addParameter( QgsProcessingParameterRasterLayer( self.RASTER_OUTPUT, - self.tr('Raster template to use for output'), + self.tr("Raster template to use for output"), None, - optional = True)) + optional=True, + ) + ) self.addParameter( QgsProcessingParameterNumber( self.HORIZONTAL_RESOLUTION, - self.tr('Horizontal resolution (m)'), + self.tr("Horizontal resolution (m)"), QgsProcessingParameterNumber.Type.Integer, 2, - optional = True)) + optional=True, + ) + ) self.addParameter( QgsProcessingParameterNumber( self.VERTICAL_RESOLUTION, - self.tr('Vertical resolution (m)'), + self.tr("Vertical resolution (m)"), QgsProcessingParameterNumber.Type.Integer, 2, - False)) - + False, + ) + ) # We add several output parameters self.addParameter( QgsProcessingParameterString( self.WIND_HEIGHT, - self.tr('Output wind height(s) (m) - if several values, separated by ","'), - defaultValue = "1.5", - optional = False)) + self.tr( + 'Output wind height(s) (m) - if several values, separated by ","' + ), + defaultValue="1.5", + optional=False, + ) + ) self.addParameter( QgsProcessingParameterFolderDestination( - self.OUTPUT_DIRECTORY, - self.tr('Directory to save the outputs'))) + self.OUTPUT_DIRECTORY, self.tr("Directory to save the outputs") + ) + ) self.addParameter( QgsProcessingParameterString( self.OUTPUT_FILENAME, - self.tr('String used as output file base name'), + self.tr("String used as output file base name"), "urock_output", - False)) + False, + ) + ) self.addParameter( QgsProcessingParameterBoolean( self.SAVE_RASTER, self.tr("Save 2D wind speed as raster file(s)"), - defaultValue=True)) + defaultValue=True, + ) + ) self.addParameter( QgsProcessingParameterBoolean( self.SAVE_VECTOR, self.tr("Save 2D wind field as vector file(s)"), - defaultValue=True)) + defaultValue=True, + ) + ) self.addParameter( QgsProcessingParameterBoolean( self.SAVE_NETCDF, self.tr("Save 3D wind field in a NetCDF file"), - defaultValue=True)) + defaultValue=True, + ) + ) self.addParameter( QgsProcessingParameterBoolean( self.LOAD_OUTPUT, self.tr("Open output 2D file(s) after running algorithm"), - defaultValue=True)) - + defaultValue=True, + ) + ) + # Optional parameters # self.addParameter( # QgsProcessingParameterString( @@ -294,61 +344,92 @@ def initAlgorithm(self, config): # self.tr('Java environment path (should be set automatically)'), # javaDirDefault, # False, - # False)) + # False)) def processAlgorithm(self, parameters, context, feedback): """ Here is where the processing itself takes place. """ - + try: import jaydebeapi except: - raise QgsProcessingException("'jaydebeapi' Python package is missing. Most tools still work. Visit the UMEP manual (Getting Started) for instructions on how to install.") + raise QgsProcessingException( + "'jaydebeapi' Python package is missing. Most tools still work. Visit the UMEP manual (Getting Started) for instructions on how to install." + ) try: import numba except Exception: - raise QgsProcessingException("'numba' Python package is missing. Most tools still work. Visit the UMEP manual (Getting Started) for instructions on how to install.") + raise QgsProcessingException( + "'numba' Python package is missing. Most tools still work. Visit the UMEP manual (Getting Started) for instructions on how to install." + ) try: import xarray except Exception: - raise QgsProcessingException("'xarray' Python package is missing. Most tools still work. Visit the UMEP manual (Getting Started) for instructions on how to install.") + raise QgsProcessingException( + "'xarray' Python package is missing. Most tools still work. Visit the UMEP manual (Getting Started) for instructions on how to install." + ) # Get the plugin directory to save some useful files plugin_directory = self.plugin_dir = os.path.dirname(__file__) - + # Get the default value of the Java environment path if already exists - javaDirDefault = getJavaDir(plugin_directory) - - if not javaDirDefault: # Raise an error if could not find a Java installation - raise QgsProcessingException("No Java installation found") - elif ("Program Files (x86)" in javaDirDefault) and (struct.calcsize("P") * 8 != 32): + javaDirDefault = getJavaDir(plugin_directory) + + if ( + not javaDirDefault + ): # Raise an error if could not find a Java installation + raise QgsProcessingException("No Java installation found") + elif ("Program Files (x86)" in javaDirDefault) and ( + struct.calcsize("P") * 8 != 32 + ): # Raise an error if Java is 32 bits but Python 64 bits - raise QgsProcessingException('Only a 32 bits version of Java has been'+ - 'found while your Python installation is 64 bits.'+ - 'Consider installing a 64 bits Java version.') - else: # Set a Java dir if not exist and save it into a file in the plugin repository + raise QgsProcessingException( + "Only a 32 bits version of Java has been" + + "found while your Python installation is 64 bits." + + "Consider installing a 64 bits Java version." + ) + else: # Set a Java dir if not exist and save it into a file in the plugin repository setJavaDir(javaDirDefault) - saveJavaDir(javaPath = javaDirDefault, - pluginDirectory = plugin_directory) - + saveJavaDir( + javaPath=javaDirDefault, pluginDirectory=plugin_directory + ) + javaEnvVar = javaDirDefault - + # Get the resource folder where styles are located - resourceDir = os.path.join(Path(plugin_directory).parent, 'functions', 'URock') - + resourceDir = os.path.join( + Path(plugin_directory).parent, "functions", "URock" + ) + # Defines inputs - z_ref = self.parameterAsDouble(parameters, self.INPUT_WIND_HEIGHT, context) - v_ref = self.parameterAsDouble(parameters, self.INPUT_WIND_SPEED, context) - windDirection = self.parameterAsDouble(parameters, self.INPUT_WIND_DIRECTION, context) - meshSize = self.parameterAsInt(parameters, self.HORIZONTAL_RESOLUTION, context) + z_ref = self.parameterAsDouble( + parameters, self.INPUT_WIND_HEIGHT, context + ) + v_ref = self.parameterAsDouble( + parameters, self.INPUT_WIND_SPEED, context + ) + windDirection = self.parameterAsDouble( + parameters, self.INPUT_WIND_DIRECTION, context + ) + meshSize = self.parameterAsInt( + parameters, self.HORIZONTAL_RESOLUTION, context + ) dz = self.parameterAsInt(parameters, self.VERTICAL_RESOLUTION, context) - profileType = self.LIST_OF_PROFILES.loc[self.parameterAsInt(parameters, self.INPUT_PROFILE_TYPE, context)] - profileFile = self.parameterAsString(parameters, self.INPUT_PROFILE_FILE, context) - + profileType = self.LIST_OF_PROFILES.loc[ + self.parameterAsInt(parameters, self.INPUT_PROFILE_TYPE, context) + ] + profileFile = self.parameterAsString( + parameters, self.INPUT_PROFILE_FILE, context + ) + # Get building layer and then file directory - inputBuildinglayer = self.parameterAsVectorLayer(parameters, self.BUILDING_TABLE_NAME, context) - heightBuild = self.parameterAsString(parameters, self.HEIGHT_FIELD_BUILD, context) + inputBuildinglayer = self.parameterAsVectorLayer( + parameters, self.BUILDING_TABLE_NAME, context + ) + heightBuild = self.parameterAsString( + parameters, self.HEIGHT_FIELD_BUILD, context + ) # Test whether the file has at least a single geometry if inputBuildinglayer and inputBuildinglayer.featureCount() > 0: build_file = str(inputBuildinglayer.dataProvider().dataSourceUri()) @@ -358,13 +439,25 @@ def processAlgorithm(self, parameters, context, feedback): # Define the srid of the output files srid_out = srid_build if not heightBuild: - raise QgsProcessingException("A building height attribute should be defined") + raise QgsProcessingException( + "A building height attribute should be defined" + ) # Save the file as fgb in case of .gpkg format if build_file.split(".")[-1].lower() == "gpkg": - new_build_file = os.path.join(TEMPO_DIRECTORY, BUILDING_FILENAME + OUTPUT_VECTOR_EXTENSION) - processing.run("native:savefeatures", - {'INPUT': build_file, - 'OUTPUT': new_build_file,'LAYER_NAME':'','DATASOURCE_OPTIONS':'','LAYER_OPTIONS':''}) + new_build_file = os.path.join( + TEMPO_DIRECTORY, + BUILDING_FILENAME + OUTPUT_VECTOR_EXTENSION, + ) + processing.run( + "native:savefeatures", + { + "INPUT": build_file, + "OUTPUT": new_build_file, + "LAYER_NAME": "", + "DATASOURCE_OPTIONS": "", + "LAYER_OPTIONS": "", + }, + ) build_file = new_build_file else: build_file = None @@ -373,8 +466,12 @@ def processAlgorithm(self, parameters, context, feedback): # Get vegetation layer if exists, check that it has the same SRID as building layer # and then get the file directory of the layer - inputVegetationlayer = self.parameterAsVectorLayer(parameters, self.VEGETATION_TABLE_NAME, context) - topHeightVeg = self.parameterAsString(parameters, self.VEGETATION_CROWN_TOP_HEIGHT, context) + inputVegetationlayer = self.parameterAsVectorLayer( + parameters, self.VEGETATION_TABLE_NAME, context + ) + topHeightVeg = self.parameterAsString( + parameters, self.VEGETATION_CROWN_TOP_HEIGHT, context + ) # Test whether the file has at least a single geometry if inputVegetationlayer and inputVegetationlayer.featureCount() > 0: veg_file = str(inputVegetationlayer.dataProvider().dataSourceUri()) @@ -385,154 +482,251 @@ def processAlgorithm(self, parameters, context, feedback): if not srid_out: srid_out = srid_veg if srid_build and (srid_build != srid_veg): - feedback.pushWarning('Coordinate system of input building layer and vegetation layer differ!') + feedback.pushWarning( + "Coordinate system of input building layer and vegetation layer differ!" + ) if not topHeightVeg: - raise QgsProcessingException("A vegetation crown top height attribute should be defined") + raise QgsProcessingException( + "A vegetation crown top height attribute should be defined" + ) # Save the file as fgb in case of .fgb format if veg_file.split(".")[-1].lower() == "gpkg": - new_veg_file = os.path.join(TEMPO_DIRECTORY, VEGETATION_FILENAME + OUTPUT_VECTOR_EXTENSION) - processing.run("native:savefeatures", - {'INPUT': veg_file, - 'OUTPUT': new_veg_file,'LAYER_NAME':'','DATASOURCE_OPTIONS':'','LAYER_OPTIONS':''}) + new_veg_file = os.path.join( + TEMPO_DIRECTORY, + VEGETATION_FILENAME + OUTPUT_VECTOR_EXTENSION, + ) + processing.run( + "native:savefeatures", + { + "INPUT": veg_file, + "OUTPUT": new_veg_file, + "LAYER_NAME": "", + "DATASOURCE_OPTIONS": "", + "LAYER_OPTIONS": "", + }, + ) veg_file = new_veg_file else: veg_file = None srid_veg = None - + if not veg_file and not build_file: - raise QgsProcessingException("Either building or vegetation file should be provided") - - outputRaster = self.parameterAsRasterLayer(parameters, self.RASTER_OUTPUT, context) - #idBuild = self.parameterAsString(parameters, self.ID_FIELD_BUILD, context) - #idVeg = self.parameterAsString(parameters, self.ID_FIELD_VEG, context) - baseHeightVeg = self.parameterAsString(parameters, self.VEGETATION_CROWN_BASE_HEIGHT, context) - attenuationVeg = self.parameterAsString(parameters, self.ATTENUATION_FIELD, context) - #prefix = self.parameterAsString(parameters, self.PREFIX, context) - + raise QgsProcessingException( + "Either building or vegetation file should be provided" + ) + + outputRaster = self.parameterAsRasterLayer( + parameters, self.RASTER_OUTPUT, context + ) + # idBuild = self.parameterAsString(parameters, self.ID_FIELD_BUILD, context) + # idVeg = self.parameterAsString(parameters, self.ID_FIELD_VEG, context) + baseHeightVeg = self.parameterAsString( + parameters, self.VEGETATION_CROWN_BASE_HEIGHT, context + ) + attenuationVeg = self.parameterAsString( + parameters, self.ATTENUATION_FIELD, context + ) + # prefix = self.parameterAsString(parameters, self.PREFIX, context) + # Defines outputs - z_out_str = self.parameterAsString(parameters, self.WIND_HEIGHT, context).split(",") + z_out_str = self.parameterAsString( + parameters, self.WIND_HEIGHT, context + ).split(",") z_out = [float(i) for i in z_out_str] - outputDirectory = self.parameterAsString(parameters, self.OUTPUT_DIRECTORY, context) - outputFilename = self.parameterAsString(parameters, self.OUTPUT_FILENAME, context) - saveRaster = self.parameterAsBool(parameters, self.SAVE_RASTER, context) - saveVector = self.parameterAsBool(parameters, self.SAVE_VECTOR, context) - saveNetcdf = self.parameterAsBool(parameters, self.SAVE_NETCDF, context) - loadOutput = self.parameterAsBool(parameters, self.LOAD_OUTPUT, context) + outputDirectory = self.parameterAsString( + parameters, self.OUTPUT_DIRECTORY, context + ) + outputFilename = self.parameterAsString( + parameters, self.OUTPUT_FILENAME, context + ) + saveRaster = self.parameterAsBool( + parameters, self.SAVE_RASTER, context + ) + saveVector = self.parameterAsBool( + parameters, self.SAVE_VECTOR, context + ) + saveNetcdf = self.parameterAsBool( + parameters, self.SAVE_NETCDF, context + ) + loadOutput = self.parameterAsBool( + parameters, self.LOAD_OUTPUT, context + ) # Creates the output folder if it does not exist if not os.path.exists(outputDirectory): if os.path.exists(Path(outputDirectory).parent.absolute()): os.mkdir(outputDirectory) else: - raise QgsProcessingException('The output directory does not exist, neither its parent directory') + raise QgsProcessingException( + "The output directory does not exist, neither its parent directory" + ) # If there is an output raster, need to get some of its parameters if outputRaster: if srid_out != outputRaster.crs().postgisSrid(): - feedback.pushWarning('Coordinate system of input building layer and output Raster layer differ!') - xres = (outputRaster.extent().xMaximum() - outputRaster.extent().xMinimum()) / outputRaster.width() - yres = (outputRaster.extent().yMaximum() - outputRaster.extent().yMinimum()) / outputRaster.height() + feedback.pushWarning( + "Coordinate system of input building layer and output Raster layer differ!" + ) + xres = ( + outputRaster.extent().xMaximum() + - outputRaster.extent().xMinimum() + ) / outputRaster.width() + yres = ( + outputRaster.extent().yMaximum() + - outputRaster.extent().yMinimum() + ) / outputRaster.height() # If there is a raster and no meshSize, take the mean of x and y raster resolution if not meshSize: meshSize = float(xres + yres) / 2 elif not meshSize: - raise QgsProcessingException('You should either specify an output raster or a horizontal mesh size') - + raise QgsProcessingException( + "You should either specify an output raster or a horizontal mesh size" + ) + if feedback: - feedback.setProgressText("Writing settings for this model run to specified output folder (Filename: RunInfoURock_YYYY_DOY_HHMM.txt)") - WriteMetadataURock.writeRunInfo(outputDirectory, build_file, heightBuild, - veg_file, attenuationVeg, baseHeightVeg, topHeightVeg, - z_ref, v_ref, windDirection, profileType, - profileFile, - meshSize, dz) - + feedback.setProgressText( + "Writing settings for this model run to specified output folder (Filename: RunInfoURock_YYYY_DOY_HHMM.txt)" + ) + WriteMetadataURock.writeRunInfo( + outputDirectory, + build_file, + heightBuild, + veg_file, + attenuationVeg, + baseHeightVeg, + topHeightVeg, + z_ref, + v_ref, + windDirection, + profileType, + profileFile, + meshSize, + dz, + ) + # Make the calculations - u, v, w, u0, v0, w0, x, y, z, buildingCoordinates, cursor, gridName,\ - rotationCenterCoordinates, verticalWindProfile, dicVectorTables,\ - netcdf_path, net_cdf_path_ini = \ - MainCalculation.main(javaEnvironmentPath = javaEnvVar, - pluginDirectory = plugin_directory, - outputFilePath = outputDirectory, - outputFilename = outputFilename, - buildingFilePath = build_file, - vegetationFilePath = veg_file, - srid = srid_out, - z_ref = z_ref, - v_ref = v_ref, - windDirection = windDirection, - prefix = '', #prefix, - meshSize = meshSize, - dz = dz, - alongWindZoneExtend = ALONG_WIND_ZONE_EXTEND, - crossWindZoneExtend = CROSS_WIND_ZONE_EXTEND, - verticalExtend = VERTICAL_EXTEND, - cadTriangles = "", - cadTreesIntersection = "", - tempoDirectory = TEMPO_DIRECTORY, - onlyInitialization = ONLY_INITIALIZATION, - maxIterations = MAX_ITERATIONS, - thresholdIterations = THRESHOLD_ITERATIONS, - idFieldBuild = None, # idBuild, - buildingHeightField = saf_id(heightBuild), - vegetationBaseHeight = saf_id(baseHeightVeg), - vegetationTopHeight = saf_id(topHeightVeg), - idVegetation = None, #idVeg, - vegetationAttenuationFactor = saf_id(attenuationVeg), - saveRockleZones = SAVE_ROCKLE_ZONES, - outputRaster = outputRaster, - feedback = feedback, - saveRaster = saveRaster, - saveVector = saveVector, - saveNetcdf = saveNetcdf, - z_out = z_out, - debug = DEBUG, - profileType = profileType, - verticalProfileFile = profileFile) - + ( + u, + v, + w, + u0, + v0, + w0, + x, + y, + z, + buildingCoordinates, + cursor, + gridName, + rotationCenterCoordinates, + verticalWindProfile, + dicVectorTables, + netcdf_path, + net_cdf_path_ini, + ) = MainCalculation.main( + javaEnvironmentPath=javaEnvVar, + pluginDirectory=plugin_directory, + outputFilePath=outputDirectory, + outputFilename=outputFilename, + buildingFilePath=build_file, + vegetationFilePath=veg_file, + srid=srid_out, + z_ref=z_ref, + v_ref=v_ref, + windDirection=windDirection, + prefix="", # prefix, + meshSize=meshSize, + dz=dz, + alongWindZoneExtend=ALONG_WIND_ZONE_EXTEND, + crossWindZoneExtend=CROSS_WIND_ZONE_EXTEND, + verticalExtend=VERTICAL_EXTEND, + cadTriangles="", + cadTreesIntersection="", + tempoDirectory=TEMPO_DIRECTORY, + onlyInitialization=ONLY_INITIALIZATION, + maxIterations=MAX_ITERATIONS, + thresholdIterations=THRESHOLD_ITERATIONS, + idFieldBuild=None, # idBuild, + buildingHeightField=saf_id(heightBuild), + vegetationBaseHeight=saf_id(baseHeightVeg), + vegetationTopHeight=saf_id(topHeightVeg), + idVegetation=None, # idVeg, + vegetationAttenuationFactor=saf_id(attenuationVeg), + saveRockleZones=SAVE_ROCKLE_ZONES, + outputRaster=outputRaster, + feedback=feedback, + saveRaster=saveRaster, + saveVector=saveVector, + saveNetcdf=saveNetcdf, + z_out=z_out, + debug=DEBUG, + profileType=profileType, + verticalProfileFile=profileFile, + ) + # Load files into QGIS if user set it if loadOutput: for z_i in z_out: if saveVector: - loadedVector = \ - QgsVectorLayer(os.path.join(outputDirectory, - "z{0}".format(str(z_i).replace(".","_")), - outputFilename\ - + OUTPUT_VECTOR_EXTENSION), - "Wind at {0} m".format(z_i), - "ogr") + loadedVector = QgsVectorLayer( + os.path.join( + outputDirectory, + "z{0}".format(str(z_i).replace(".", "_")), + outputFilename + OUTPUT_VECTOR_EXTENSION, + ), + "Wind at {0} m".format(z_i), + "ogr", + ) if not loadedVector.isValid(): feedback.pushWarning("Vector layer failed to load!") break else: - loadedVector.loadNamedStyle(os.path.join(resourceDir,\ - "Resources", - VECTOR_STYLE_FILENAME), True) - context.addLayerToLoadOnCompletion(loadedVector.id(), - QgsProcessingContext.LayerDetails("Wind at {0} m".format(z_i), - QgsProject.instance(), - '')) + loadedVector.loadNamedStyle( + os.path.join( + resourceDir, "Resources", VECTOR_STYLE_FILENAME + ), + True, + ) + context.addLayerToLoadOnCompletion( + loadedVector.id(), + QgsProcessingContext.LayerDetails( + "Wind at {0} m".format(z_i), + QgsProject.instance(), + "", + ), + ) context.temporaryLayerStore().addMapLayer(loadedVector) - + if saveRaster: - loadedRaster = \ - QgsRasterLayer(os.path.join(outputDirectory, - "z{0}".format(str(z_i).replace(".","_")), - outputFilename\ - + WIND_SPEED + OUTPUT_RASTER_EXTENSION), - "Wind speed at {0} m".format(z_i), - "gdal") + loadedRaster = QgsRasterLayer( + os.path.join( + outputDirectory, + "z{0}".format(str(z_i).replace(".", "_")), + outputFilename + + WIND_SPEED + + OUTPUT_RASTER_EXTENSION, + ), + "Wind speed at {0} m".format(z_i), + "gdal", + ) if not loadedRaster.isValid(): feedback.pushWarning("Raster layer failed to load!") break else: - context.addLayerToLoadOnCompletion(loadedRaster.id(), - QgsProcessingContext.LayerDetails("Wind speed at {0} m".format(z_i), - QgsProject.instance(), - '')) + context.addLayerToLoadOnCompletion( + loadedRaster.id(), + QgsProcessingContext.LayerDetails( + "Wind speed at {0} m".format(z_i), + QgsProject.instance(), + "", + ), + ) context.temporaryLayerStore().addMapLayer(loadedRaster) # Return the output file names - return {self.OUTPUT_DIRECTORY: outputDirectory, - self.OUTPUT_FILENAME: outputFilename} + return { + self.OUTPUT_DIRECTORY: outputDirectory, + self.OUTPUT_FILENAME: outputFilename, + } def name(self): """ @@ -542,14 +736,14 @@ def name(self): lowercase alphanumeric characters only and no spaces or other formatting characters. """ - return 'Urban Wind Field: URock' + return "Urban Wind Field: URock" def displayName(self): """ Returns the translated algorithm name, which should be used for any user-visible display of the algorithm name. """ - return self.tr('Urban Wind Field: URock v2023a') + return self.tr("Urban Wind Field: URock v2023a") def group(self): """ @@ -566,39 +760,43 @@ def groupId(self): contain lowercase alphanumeric characters only and no spaces or other formatting characters. """ - return 'Processor' + return "Processor" def tr(self, string): - return QCoreApplication.translate('Processing', string) - + return QCoreApplication.translate("Processing", string) + def shortHelpString(self): - return self.tr('The URock plugin can be used to calculate '+\ - 'spatial variations of wind speed and wind direction'+ - ' in 3 dimensions using 2.5D building and vegetation data.\n'+ - 'At least one of building or vegetation file should '+ - 'be provided by the user. Minimum attribute column'+ - ' for building file is "roof height" '+ - '(note that roofs are considered flats in the current version)'+ - 'Minimum attribute column for vegetation file is "vegetation crown top height".' - '\n' - '\n' - 'This tools requires Java. If Java is not installed on your system,'+ - 'visit www.java.com and install the latest version. Make sure to install correct version '+ - 'based on your system architecture (32- or 64-bit). You can also download an open source java from '+ - 'https://adoptium.net/ (OpenJDK). Remember to set correct PATH and JAVA_HOME. The installation process will guide you (Windows).' - '\n' - '\n' - '---------------\n' - 'Full manual available via the Help-button.') + return self.tr( + "The URock plugin can be used to calculate " + + "spatial variations of wind speed and wind direction" + + " in 3 dimensions using 2.5D building and vegetation data.\n" + + "At least one of building or vegetation file should " + + "be provided by the user. Minimum attribute column" + + ' for building file is "roof height" ' + + "(note that roofs are considered flats in the current version)" + + 'Minimum attribute column for vegetation file is "vegetation crown top height".' + "\n" + "\n" + "This tools requires Java. If Java is not installed on your system," + + "visit www.java.com and install the latest version. Make sure to install correct version " + + "based on your system architecture (32- or 64-bit). You can also download an open source java from " + + "https://adoptium.net/ (OpenJDK). Remember to set correct PATH and JAVA_HOME. The installation process will guide you (Windows)." + "\n" + "\n" + "---------------\n" + "Full manual available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/processor/Urban%20Wind%20Filed%20URock.html" return url - + def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/urock.png") return icon def createInstance(self): - return URockAlgorithm() \ No newline at end of file + return URockAlgorithm() diff --git a/processor/uwg_algorithm.py b/processor/uwg_algorithm.py index ede8b3c..a46cc08 100644 --- a/processor/uwg_algorithm.py +++ b/processor/uwg_algorithm.py @@ -1,24 +1,27 @@ # -*- coding: utf-8 -*- -__author__ = 'Fredrik Lindberg' -__date__ = '2021-02-04' -__copyright__ = '(C) 2021 by Fredrik Lindberg' +__author__ = "Fredrik Lindberg" +__date__ = "2021-02-04" +__copyright__ = "(C) 2021 by Fredrik Lindberg" -__revision__ = '$Format:%H$' +__revision__ = "$Format:%H$" from qgis.PyQt.QtCore import QCoreApplication, QVariant -from qgis.core import (QgsProcessing, - QgsProcessingAlgorithm, - QgsProcessingParameterBoolean, - QgsProcessingParameterNumber, - QgsProcessingParameterFolderDestination, - QgsProcessingParameterFile, - QgsProcessingParameterDateTime, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterField, - QgsProcessingException,) +from qgis.core import ( + QgsProcessing, + QgsProcessingAlgorithm, + QgsProcessingParameterBoolean, + QgsProcessingParameterNumber, + QgsProcessingParameterFolderDestination, + QgsProcessingParameterFile, + QgsProcessingParameterDateTime, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterField, + QgsProcessingException, +) from qgis.PyQt.QtGui import QIcon + # from osgeo import gdal, osr, ogr from osgeo.gdalconst import * import os @@ -30,6 +33,7 @@ import traceback import math from ..util.umep_uwg_export_component import get_uwg_file, read_uwg_file + try: from uwg import UWG except: @@ -40,54 +44,102 @@ class ProcessingUWGProcessorAlgorithm(QgsProcessingAlgorithm): """ This algorithm make use of UWG for the processing toolbox """ - - INPUT_FOLDER = 'INPUT_FOLDER' - INPUT_POLYGONLAYER = 'INPUT_POLYGONLAYER' - ID_FIELD = 'ID_FIELD' - START_DATE = 'START_DATE' - NDAYS = 'NDAYS' - INPUT_MET = 'INPUT_MET' - UMEP_OUTPUT = 'UMEP_OUTPUT' - OUTPUT_DIR = 'OUTPUT_DIR' - OUTPUT_FORMAT = 'OUTPUT_FORMAT' - DTSIM = 'DTSIM' - EXCLUDE_RURAL = 'EXCLUDE_RURAL' + INPUT_FOLDER = "INPUT_FOLDER" + INPUT_POLYGONLAYER = "INPUT_POLYGONLAYER" + ID_FIELD = "ID_FIELD" + START_DATE = "START_DATE" + NDAYS = "NDAYS" + INPUT_MET = "INPUT_MET" + UMEP_OUTPUT = "UMEP_OUTPUT" + OUTPUT_DIR = "OUTPUT_DIR" + OUTPUT_FORMAT = "OUTPUT_FORMAT" + DTSIM = "DTSIM" + EXCLUDE_RURAL = "EXCLUDE_RURAL" def initAlgorithm(self, config): - self.addParameter(QgsProcessingParameterFile(self.INPUT_FOLDER, - self.tr('Path to folder where UWG input files are located'), - QgsProcessingParameterFile.Behavior.Folder)) - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_POLYGONLAYER, - self.tr('Vector data including location(s) to model'), - [QgsProcessing.SourceType.TypeVector])) - self.addParameter(QgsProcessingParameterField(self.ID_FIELD, - self.tr('ID field'), - '', - self.INPUT_POLYGONLAYER, - QgsProcessingParameterField.DataType.Numeric)) - self.addParameter(QgsProcessingParameterDateTime(self.START_DATE, - self.tr('Start date of simulation'), - QgsProcessingParameterDateTime.Type.Date)) - self.addParameter(QgsProcessingParameterNumber(self.NDAYS, - self.tr('Number of days to run simulation'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(5), False, minValue=1, maxValue=365)) - self.addParameter(QgsProcessingParameterFile(self.INPUT_MET, - self.tr('Input meteorological file (*.epw)'), - extension = 'epw')) - self.addParameter(QgsProcessingParameterNumber(self.DTSIM, - self.tr('Simulation time step in seconds'), - QgsProcessingParameterNumber.Type.Integer, - QVariant(300), False, minValue=1, maxValue=1440)) - self.addParameter(QgsProcessingParameterBoolean(self.EXCLUDE_RURAL, - self.tr('Exculde grids with very small building fractions (< 0.5%)'), defaultValue=False)) + self.addParameter( + QgsProcessingParameterFile( + self.INPUT_FOLDER, + self.tr("Path to folder where UWG input files are located"), + QgsProcessingParameterFile.Behavior.Folder, + ) + ) + self.addParameter( + QgsProcessingParameterFeatureSource( + self.INPUT_POLYGONLAYER, + self.tr("Vector data including location(s) to model"), + [QgsProcessing.SourceType.TypeVector], + ) + ) + self.addParameter( + QgsProcessingParameterField( + self.ID_FIELD, + self.tr("ID field"), + "", + self.INPUT_POLYGONLAYER, + QgsProcessingParameterField.DataType.Numeric, + ) + ) + self.addParameter( + QgsProcessingParameterDateTime( + self.START_DATE, + self.tr("Start date of simulation"), + QgsProcessingParameterDateTime.Type.Date, + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.NDAYS, + self.tr("Number of days to run simulation"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(5), + False, + minValue=1, + maxValue=365, + ) + ) + self.addParameter( + QgsProcessingParameterFile( + self.INPUT_MET, + self.tr("Input meteorological file (*.epw)"), + extension="epw", + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.DTSIM, + self.tr("Simulation time step in seconds"), + QgsProcessingParameterNumber.Type.Integer, + QVariant(300), + False, + minValue=1, + maxValue=1440, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.EXCLUDE_RURAL, + self.tr( + "Exculde grids with very small building fractions (< 0.5%)" + ), + defaultValue=False, + ) + ) # output - self.addParameter(QgsProcessingParameterFolderDestination(self.OUTPUT_DIR, - self.tr('Output folder'))) - self.addParameter(QgsProcessingParameterBoolean(self.OUTPUT_FORMAT, - self.tr('Save output in UMEP specific format (required for the UWG Analyzer)'))) - + self.addParameter( + QgsProcessingParameterFolderDestination( + self.OUTPUT_DIR, self.tr("Output folder") + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.OUTPUT_FORMAT, + self.tr( + "Save output in UMEP specific format (required for the UWG Analyzer)" + ), + ) + ) def processAlgorithm(self, parameters, context, feedback): @@ -95,21 +147,35 @@ def processAlgorithm(self, parameters, context, feedback): import uwg except: pass - raise QgsProcessingException("uwg python library not found: Instructions on how to install missing python libraries using the pip command: https://umep-docs.readthedocs.io/en/latest/Getting_Started.html") + raise QgsProcessingException( + "uwg python library not found: Instructions on how to install missing python libraries using the pip command: https://umep-docs.readthedocs.io/en/latest/Getting_Started.html" + ) # InputParameters - inputDir = self.parameterAsString(parameters, self.INPUT_FOLDER, context) - inputPolygonlayer = self.parameterAsVectorLayer(parameters, self.INPUT_POLYGONLAYER, context) + inputDir = self.parameterAsString( + parameters, self.INPUT_FOLDER, context + ) + inputPolygonlayer = self.parameterAsVectorLayer( + parameters, self.INPUT_POLYGONLAYER, context + ) idField = self.parameterAsFields(parameters, self.ID_FIELD, context) - startDate = self.parameterAsString(parameters, self.START_DATE, context) - nDays = self.parameterAsDouble(parameters, self.NDAYS, context) + startDate = self.parameterAsString( + parameters, self.START_DATE, context + ) + nDays = self.parameterAsDouble(parameters, self.NDAYS, context) inputMet = self.parameterAsString(parameters, self.INPUT_MET, context) - outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) - umepformat = self.parameterAsBoolean(parameters, self.OUTPUT_FORMAT, context) + outputDir = self.parameterAsString( + parameters, self.OUTPUT_DIR, context + ) + umepformat = self.parameterAsBoolean( + parameters, self.OUTPUT_FORMAT, context + ) dtSim = self.parameterAsDouble(parameters, self.DTSIM, context) - excludeRural = self.parameterAsBoolean(parameters, self.EXCLUDE_RURAL, context) - - if parameters['OUTPUT_DIR'] == 'TEMPORARY_OUTPUT': + excludeRural = self.parameterAsBoolean( + parameters, self.EXCLUDE_RURAL, context + ) + + if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): os.mkdir(outputDir) @@ -125,7 +191,9 @@ def processAlgorithm(self, parameters, context, feedback): idx = vlayer.fields().indexFromName(poly_field[0]) nGrids = vlayer.featureCount() - feedback.setProgressText("Number of grids to calculate: " + str(nGrids)) + feedback.setProgressText( + "Number of grids to calculate: " + str(nGrids) + ) mm = startDate[5:7] dd = startDate[8:10] @@ -133,10 +201,14 @@ def processAlgorithm(self, parameters, context, feedback): index = 1 if umepformat: - header = '%iy id it imin Q* QH QE Qs Qf Wind RH Td press rain ' \ - ' Kdn snow ldown fcld wuh xsmd lai_hr Kdiff Kdir Wd' - numformat = '%d %d %d %d %.2f %.2f %.2f %.2f %.2f %.5f %.2f %.2f %.2f %.2f %.2f %.2f %.2f ' \ - '%.2f %.2f %.2f %.2f %.2f %.2f %.2f' + header = ( + "%iy id it imin Q* QH QE Qs Qf Wind RH Td press rain " + " Kdn snow ldown fcld wuh xsmd lai_hr Kdiff Kdir Wd" + ) + numformat = ( + "%d %d %d %d %.2f %.2f %.2f %.2f %.2f %.5f %.2f %.2f %.2f %.2f %.2f %.2f %.2f " + "%.2f %.2f %.2f %.2f %.2f %.2f %.2f" + ) for f in vlayer.getFeatures(): # looping through each vector object feedback.setProgress(int((index * 100) / nGrids)) @@ -147,67 +219,150 @@ def processAlgorithm(self, parameters, context, feedback): numtype = math.modf(f.attributes()[idx]) if numtype[0] == 0.0: attr = int(f.attributes()[idx]) - + ## generate input files for UWG - uwgDict = read_uwg_file(inputDir, prefix + '_' + str(attr)) - uwgDict['Month'] = mm - uwgDict['Day'] = dd - uwgDict['nDay'] = nDays - uwgDict['dtSim'] = dtSim - get_uwg_file(uwgDict, inputDir, prefix + '_' + str(attr)) - + uwgDict = read_uwg_file(inputDir, prefix + "_" + str(attr)) + uwgDict["Month"] = mm + uwgDict["Day"] = dd + uwgDict["nDay"] = nDays + uwgDict["dtSim"] = dtSim + get_uwg_file(uwgDict, inputDir, prefix + "_" + str(attr)) + # inputUWGfile = inputDir + '/' + prefix + '_' + str(f.attributes()[idx]) + '.uwg' - epw_path = inputDir + '/' + prefix + '_' + str(attr) + '.epw' - uwg_path = inputDir + '/' + prefix + '_' + str(attr) + '_UWG.epw' - param_path = inputDir + '/' + prefix + '_' + str(attr) + '.uwg' + epw_path = inputDir + "/" + prefix + "_" + str(attr) + ".epw" + uwg_path = inputDir + "/" + prefix + "_" + str(attr) + "_UWG.epw" + param_path = inputDir + "/" + prefix + "_" + str(attr) + ".uwg" shutil.copy(inputMet, epw_path) if index == 1: if umepformat: - epwdata = np.genfromtxt(inputMet, skip_header=8, delimiter=',', filling_values=99999) + epwdata = np.genfromtxt( + inputMet, + skip_header=8, + delimiter=",", + filling_values=99999, + ) umep_forcing = self.epw2UMEP(epwdata) - np.savetxt(outputDir + '/metdata_UMEP.txt', umep_forcing, fmt=numformat, header=header, comments='') + np.savetxt( + outputDir + "/metdata_UMEP.txt", + umep_forcing, + fmt=numformat, + header=header, + comments="", + ) # else: # shutil.copy(inputMet, ) # run model if excludeRural: - if uwgDict['bldDensity'] < 0.005: - feedback.setProgressText("Grid: " + str(attr) + ' not calculated. Less than 0.005 in building fraction.') + if uwgDict["bldDensity"] < 0.005: + feedback.setProgressText( + "Grid: " + + str(attr) + + " not calculated. Less than 0.005 in building fraction." + ) if umepformat: - epwdata_uwg = np.genfromtxt(epw_path, skip_header=8, delimiter=',', filling_values=99999) + epwdata_uwg = np.genfromtxt( + epw_path, + skip_header=8, + delimiter=",", + filling_values=99999, + ) umep_uwg = self.epw2UMEP(epwdata_uwg) - np.savetxt(outputDir + '/' + prefix + '_' + str(attr) + '_UMEP_UWG.txt', umep_uwg, fmt=numformat, header=header, comments='') + np.savetxt( + outputDir + + "/" + + prefix + + "_" + + str(attr) + + "_UMEP_UWG.txt", + umep_uwg, + fmt=numformat, + header=header, + comments="", + ) os.remove(epw_path) - - shutil.move(epw_path, Path(outputDir + '/' + prefix + '_' + str(attr) + '_UWG.epw')) + + shutil.move( + epw_path, + Path( + outputDir + + "/" + + prefix + + "_" + + str(attr) + + "_UWG.epw" + ), + ) else: - feedback.setProgressText("UWG calculating grid: " + str(attr)) + feedback.setProgressText( + "UWG calculating grid: " + str(attr) + ) try: - model = UWG.from_param_file(param_path, epw_path=epw_path) + model = UWG.from_param_file( + param_path, epw_path=epw_path + ) model.generate() model.simulate() model.write_epw() if umepformat: - epwdata_uwg = np.genfromtxt(uwg_path, skip_header=8, delimiter=',', filling_values=99999) + epwdata_uwg = np.genfromtxt( + uwg_path, + skip_header=8, + delimiter=",", + filling_values=99999, + ) umep_uwg = self.epw2UMEP(epwdata_uwg) - np.savetxt(outputDir + '/' + prefix + '_' + str(attr) + '_UMEP_UWG.txt', umep_uwg, fmt=numformat, header=header, comments='') - #os.remove(uwg_path) - - shutil.move(uwg_path, Path(outputDir + '/' + prefix + '_' + str(attr) + '_UWG.epw')) - + np.savetxt( + outputDir + + "/" + + prefix + + "_" + + str(attr) + + "_UMEP_UWG.txt", + umep_uwg, + fmt=numformat, + header=header, + comments="", + ) + # os.remove(uwg_path) + + shutil.move( + uwg_path, + Path( + outputDir + + "/" + + prefix + + "_" + + str(attr) + + "_UWG.epw" + ), + ) + os.remove(epw_path) except Exception as e: - feedback.pushWarning("Calculating grid " + str(attr) + ' failed: ' + str(e)) - feedback.pushWarning('To get the full traceback error message, open the Python console in QGIS and re-run the simulation.') - feedback.pushWarning('If you cannot solve the error yourself, report an issue to our code reporitory (see UMEP-Manual for details).') - print('Traceback error message while caclulation grid: ' + str(attr)) + feedback.pushWarning( + "Calculating grid " + + str(attr) + + " failed: " + + str(e) + ) + feedback.pushWarning( + "To get the full traceback error message, open the Python console in QGIS and re-run the simulation." + ) + feedback.pushWarning( + "If you cannot solve the error yourself, report an issue to our code reporitory (see UMEP-Manual for details)." + ) + print( + "Traceback error message while caclulation grid: " + + str(attr) + ) print(traceback.format_exc()) - + else: feedback.setProgressText("UWG calculating grid: " + str(attr)) try: @@ -217,20 +372,55 @@ def processAlgorithm(self, parameters, context, feedback): model.write_epw() if umepformat: - epwdata_uwg = np.genfromtxt(uwg_path, skip_header=8, delimiter=',', filling_values=99999) + epwdata_uwg = np.genfromtxt( + uwg_path, + skip_header=8, + delimiter=",", + filling_values=99999, + ) umep_uwg = self.epw2UMEP(epwdata_uwg) - np.savetxt(outputDir + '/' + prefix + '_' + str(attr) + '_UMEP_UWG.txt', umep_uwg, fmt=numformat, header=header, comments='') - #os.remove(uwg_path) + np.savetxt( + outputDir + + "/" + + prefix + + "_" + + str(attr) + + "_UMEP_UWG.txt", + umep_uwg, + fmt=numformat, + header=header, + comments="", + ) + # os.remove(uwg_path) else: - shutil.move(uwg_path, Path(outputDir + '/' + prefix + '_' + str(attr) + '_UWG.epw')) - + shutil.move( + uwg_path, + Path( + outputDir + + "/" + + prefix + + "_" + + str(attr) + + "_UWG.epw" + ), + ) + os.remove(epw_path) except Exception as e: - feedback.pushWarning("Calculating grid " + str(attr) + ' failed: ' + str(e)) - feedback.pushWarning('To get the full traceback error message, open the Python console in QGIS and re-run the simulation.') - feedback.pushWarning('If you cannot solve the error yourself, report an issue to our code reporitory (see UMEP-Manual for details).') - print('Traceback error message while caclulation grid: ' + str(attr)) + feedback.pushWarning( + "Calculating grid " + str(attr) + " failed: " + str(e) + ) + feedback.pushWarning( + "To get the full traceback error message, open the Python console in QGIS and re-run the simulation." + ) + feedback.pushWarning( + "If you cannot solve the error yourself, report an issue to our code reporitory (see UMEP-Manual for details)." + ) + print( + "Traceback error message while caclulation grid: " + + str(attr) + ) print(traceback.format_exc()) index += 1 @@ -269,9 +459,11 @@ def epw2UMEP(self, met_old): dayspermonth = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] else: dayspermonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] - met_new[i, 1] = sum(dayspermonth[0:int(mm[i] - 1)]) + dd[i] + met_new[i, 1] = sum(dayspermonth[0 : int(mm[i] - 1)]) + dd[i] - met_new[np.where(met_new[:, 2] == 0), 1] = met_new[np.where(met_new[:, 2] == 0), 1] + 1 + met_new[np.where(met_new[:, 2] == 0), 1] = ( + met_new[np.where(met_new[:, 2] == 0), 1] + 1 + ) met_new[met_old.shape[0] - 1, 1] = 1 # minute @@ -280,7 +472,7 @@ def epw2UMEP(self, met_old): # met variables met_new[:, 11] = met_old[:, 6] # Ta met_new[:, 10] = met_old[:, 8] # Rh - met_new[:, 12] = met_old[:, 9] / 1000. # P + met_new[:, 12] = met_old[:, 9] / 1000.0 # P met_new[:, 16] = met_old[:, 12] # Ldown met_new[:, 14] = met_old[:, 13] # Kdown met_new[:, 22] = met_old[:, 14] # Kdir @@ -291,9 +483,9 @@ def epw2UMEP(self, met_old): met_new[np.where(met_new[:, 13] == 999), 13] = 0 return met_new - + def name(self): - return 'Urban Heat Island: Urban Weather Generator' + return "Urban Heat Island: Urban Weather Generator" def displayName(self): return self.tr(self.name()) @@ -302,37 +494,41 @@ def group(self): return self.tr(self.groupId()) def groupId(self): - return 'Processor' + return "Processor" def shortHelpString(self): - return self.tr('THIS TOOL IS EXPERIMENTAL' - '\n' - 'The Urban Weather Generator plugin can be used to model the urban heat island effect. Possibilities to model mutiple grids or a single location is available.
' - '\n' - 'For more detailed information during execution, open the QGIS Python console (Plugins>Python Console).' - '\n' - 'NOTE: This plugin requires the uwg python library. If UMEP was installed without issues, uwg should be installed on your system. Instructions on how to install missing python libraries using the pip command can be found here: ' - 'https://umep-docs.readthedocs.io/en/latest/Getting_Started.html' - '\n' - 'If you are having issues that certain grids fails to be calculated you can try to reduce the simulation time step, preferably to 150 or 100 seconds. ' - 'This will increase computation time but make the model more stable.' - '\n' - 'You can also increase stability by ticking in the box to exclude grids with very low building fraction.' - '\n' - '----------------------\n' - 'Full manual is available via the Help-button.') + return self.tr( + "THIS TOOL IS EXPERIMENTAL" + "\n" + "The Urban Weather Generator plugin can be used to model the urban heat island effect. Possibilities to model mutiple grids or a single location is available.
" + "\n" + "For more detailed information during execution, open the QGIS Python console (Plugins>Python Console)." + "\n" + "NOTE: This plugin requires the uwg python library. If UMEP was installed without issues, uwg should be installed on your system. Instructions on how to install missing python libraries using the pip command can be found here: " + 'https://umep-docs.readthedocs.io/en/latest/Getting_Started.html' + "\n" + "If you are having issues that certain grids fails to be calculated you can try to reduce the simulation time step, preferably to 150 or 100 seconds. " + "This will increase computation time but make the model more stable." + "\n" + "You can also increase stability by ticking in the box to exclude grids with very low building fraction." + "\n" + "----------------------\n" + "Full manual is available via the Help-button." + ) def helpUrl(self): url = "https://umep-docs.readthedocs.io/en/latest/processor/Urban%20Heat%20Island%20Urban%20Weather%20Generator.html" return url def tr(self, string): - return QCoreApplication.translate('Processing', string) + return QCoreApplication.translate("Processing", string) def icon(self): - cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent + cmd_folder = Path( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + ).parent icon = QIcon(str(cmd_folder) + "/icons/icon_uwg.png") return icon def createInstance(self): - return ProcessingUWGProcessorAlgorithm() \ No newline at end of file + return ProcessingUWGProcessorAlgorithm() diff --git a/util/RoughnessCalcFunctionV2.py b/util/RoughnessCalcFunctionV2.py index 42bdb0e..180a016 100644 --- a/util/RoughnessCalcFunctionV2.py +++ b/util/RoughnessCalcFunctionV2.py @@ -8,248 +8,359 @@ # 'Mho' - Millward-Hopkins et al. (2011) [SIMPLIFIED] # 'Kan' - Kanda et al. (2013) ##Building information -#zH - average building height, -#fai - frontal area, -#pai - plan area, -#zMax - max building height, -#zSdev - standard dev of building heights +# zH - average building height, +# fai - frontal area, +# pai - plan area, +# zMax - max building height, +# zSdev - standard dev of building heights ####OUTPUTS#### -#zd = zero-plane displacement height -#z0 = roughness length +# zd = zero-plane displacement height +# z0 = roughness length import numpy as np import math + ####FUNCTION#### def RoughnessCalcMany(Roughnessmethod, zH, fai, pai, zMax, zSdev): - z_d_output = np.zeros((fai.shape[0], 1)) - 999. - z_0_output = np.zeros((fai.shape[0], 1)) - 999. + z_d_output = np.zeros((fai.shape[0], 1)) - 999.0 + z_0_output = np.zeros((fai.shape[0], 1)) - 999.0 for i in range(0, fai.shape[0]): - if Roughnessmethod == 'RT': - #Rule of thumb method - z_d_output = 0.7*zH - z_0_output = 0.1*zH - elif Roughnessmethod == 'Rau': + if Roughnessmethod == "RT": + # Rule of thumb method + z_d_output = 0.7 * zH + z_0_output = 0.1 * zH + elif Roughnessmethod == "Rau": ##### Raupach 1994/95 #### - Cs=0.003 - Cr=0.3 - Stab=0.193 - UdivUmax=0.3 - Cdl=7.5 - k=0.4 - RauZdexpW=(math.exp(-((Cdl*2*fai[i])**0.5)))-1 - z_d_output[i] = (1 + (RauZdexpW/((Cdl*2*fai[i])**0.5)))*zH[i] - RauZoUtermW = 1/(min(((Cs+(Cr*fai[i]))**0.5), UdivUmax)) - RauZoexpW = np.exp((-k*RauZoUtermW)+Stab) - z_0_output[i] = ((1-(z_d_output[i]/zH[i]))*RauZoexpW)*zH[i] + Cs = 0.003 + Cr = 0.3 + Stab = 0.193 + UdivUmax = 0.3 + Cdl = 7.5 + k = 0.4 + RauZdexpW = (math.exp(-((Cdl * 2 * fai[i]) ** 0.5))) - 1 + z_d_output[i] = ( + 1 + (RauZdexpW / ((Cdl * 2 * fai[i]) ** 0.5)) + ) * zH[i] + RauZoUtermW = 1 / (min(((Cs + (Cr * fai[i])) ** 0.5), UdivUmax)) + RauZoexpW = np.exp((-k * RauZoUtermW) + Stab) + z_0_output[i] = ((1 - (z_d_output[i] / zH[i])) * RauZoexpW) * zH[i] - elif Roughnessmethod == 'Bot': - #Bottema + elif Roughnessmethod == "Bot": + # Bottema Cdh = 0.8 - k=0.4 - z_d_output[i] = (pai[i]**0.6)*zH[i] - BotZoexpW =np.exp(-k/((0.5*fai[i]*Cdh)**0.5)) - z_0_output[i] = (zH[i] - z_d_output[i])*(BotZoexpW) - elif Roughnessmethod == 'Mac': - #MacDonald + k = 0.4 + z_d_output[i] = (pai[i] ** 0.6) * zH[i] + BotZoexpW = np.exp(-k / ((0.5 * fai[i] * Cdh) ** 0.5)) + z_0_output[i] = (zH[i] - z_d_output[i]) * (BotZoexpW) + elif Roughnessmethod == "Mac": + # MacDonald Clb = 1.2 - k=0.4 - #Staggered array + k = 0.4 + # Staggered array Alph = 4.43 Beet = 1.0 - #Square array - #Alph = 3.59 - #Beet = 0.55 - if zH[i]>0.: - z_d_output[i] = (1+((Alph**-pai[i])*(pai[i]-1)))*zH[i] + # Square array + # Alph = 3.59 + # Beet = 0.55 + if zH[i] > 0.0: + z_d_output[i] = (1 + ((Alph ** -pai[i]) * (pai[i] - 1))) * zH[ + i + ] if z_d_output[i] != zH[i]: - z_0_output[i] = (zH[i]*((1-z_d_output[i]/zH[i]))*np.exp(-(0.5*(1.2/0.4**2)*(1-(z_d_output[i]/zH[i]))*fai[i])**-0.5)) + z_0_output[i] = ( + zH[i] + * ((1 - z_d_output[i] / zH[i])) + * np.exp( + -( + ( + 0.5 + * (1.2 / 0.4**2) + * (1 - (z_d_output[i] / zH[i])) + * fai[i] + ) + ** -0.5 + ) + ) + ) else: - z_0_output[i] = 0. + z_0_output[i] = 0.0 else: - z_d_output[i] = 0. - z_0_output[i] = 0. - elif Roughnessmethod == 'Kan': - #Kanda + z_d_output[i] = 0.0 + z_0_output[i] = 0.0 + elif Roughnessmethod == "Kan": + # Kanda Kanmeth = 1 if Kanmeth == 1: - Ao = 1.29 - Bo = 0.36 - Co = -0.17 - A1 = 0.71 - B1= 20.21 - C1 = -0.77 + Ao = 1.29 + Bo = 0.36 + Co = -0.17 + A1 = 0.71 + B1 = 20.21 + C1 = -0.77 elif Kanmeth == 2: - Ao = 0.86 - Bo = 0.28 - Co = -0.18 - A1 = 0.93 - B1 = 8.93 - C1 = 4.68 - #First perform MacD method - #MacDonald + Ao = 0.86 + Bo = 0.28 + Co = -0.18 + A1 = 0.93 + B1 = 8.93 + C1 = 4.68 + # First perform MacD method + # MacDonald Clb = 1.2 - k=0.4 - #Staggered array + k = 0.4 + # Staggered array Alph = 4.43 Beet = 1.0 - #Square array - #Alph = 3.59 - #Beet = 0.55 - if zH[i] > 0.: - z_d_output[i] = (1+((Alph**-pai[i])*(pai[i]-1)))*zH[i] + # Square array + # Alph = 3.59 + # Beet = 0.55 + if zH[i] > 0.0: + z_d_output[i] = (1 + ((Alph ** -pai[i]) * (pai[i] - 1))) * zH[ + i + ] if z_d_output[i] != zH[i]: - z0Mac= (zH[i]*((1-z_d_output[i]/zH[i]))*np.exp(-(0.5*(1.2/0.4**2)*(1-(z_d_output[i]/zH[i]))*fai[i])**-0.5)) + z0Mac = ( + zH[i] + * ((1 - z_d_output[i] / zH[i])) + * np.exp( + -( + ( + 0.5 + * (1.2 / 0.4**2) + * (1 - (z_d_output[i] / zH[i])) + * fai[i] + ) + ** -0.5 + ) + ) + ) else: - z0Mac = 0. - X=(zSdev[i]+zH[i])/zMax[i] - if (0= 0: - z_0_output[i] = ((B1*(Y**2))+(C1*Y)+A1)*z0Mac + z_0_output[i] = ((B1 * (Y**2)) + (C1 * Y) + A1) * z0Mac else: - z_0_output[i] = A1*z0Mac + z_0_output[i] = A1 * z0Mac else: - z_d_output[i] = 0. - z_0_output[i] = 0. - elif Roughnessmethod == 'Mho': - #Millward Hopkins + z_d_output[i] = 0.0 + z_0_output[i] = 0.0 + elif Roughnessmethod == "Mho": + # Millward Hopkins #### MHO - Heterogenous - Displacement Height #### CD = 1.2 CDD = 2 - k= 0.4 + k = 0.4 #### Millward-Hopkins (2011)- Uniform with correction #### - CD=1.2 + CD = 1.2 - if pai[i] >=0.19: - ZdMho_U = (((19.2*pai[i]) - 1 + (np.exp(-19.2*pai[i])))/((19.2*pai[i])*(1-(np.exp(-19.2*pai[i])))))*zH[i] + if pai[i] >= 0.19: + ZdMho_U = ( + ((19.2 * pai[i]) - 1 + (np.exp(-19.2 * pai[i]))) + / ((19.2 * pai[i]) * (1 - (np.exp(-19.2 * pai[i])))) + ) * zH[i] elif pai[i] < 0.19: - ZdMho_U = (((117*pai[i]) + ((187.2*(pai[i]**3))-6.1)*(1-np.exp(-19.2*pai[i])))/((1+(114*pai[i])+(187*pai[i]**3))*(1-(np.exp(-19.2*pai[i])))))*zH[i] - ZoMhoexp_U = np.exp(-((0.5*CD*(k**-2)*fai[i])**-0.5)) - ZoMho_U=((1-(ZdMho_U/zH[i]))* ZoMhoexp_U)*zH[i] - ZdMho_UCor=zH[i]*((ZdMho_U/zH[i])+((0.2375*np.log(pai[i])+1.1738)*(zSdev[i]/zH[i]))) - ZoMho_UCor= zH[i]*((ZoMho_U/zH[i])+ (np.exp((0.8867*fai[i])-1)*((zSdev[i]/zH[i])**np.exp(2.3271*fai[i])))) + ZdMho_U = ( + ( + (117 * pai[i]) + + ((187.2 * (pai[i] ** 3)) - 6.1) + * (1 - np.exp(-19.2 * pai[i])) + ) + / ( + (1 + (114 * pai[i]) + (187 * pai[i] ** 3)) + * (1 - (np.exp(-19.2 * pai[i]))) + ) + ) * zH[i] + ZoMhoexp_U = np.exp(-((0.5 * CD * (k**-2) * fai[i]) ** -0.5)) + ZoMho_U = ((1 - (ZdMho_U / zH[i])) * ZoMhoexp_U) * zH[i] + ZdMho_UCor = zH[i] * ( + (ZdMho_U / zH[i]) + + ((0.2375 * np.log(pai[i]) + 1.1738) * (zSdev[i] / zH[i])) + ) + ZoMho_UCor = zH[i] * ( + (ZoMho_U / zH[i]) + + ( + np.exp((0.8867 * fai[i]) - 1) + * ((zSdev[i] / zH[i]) ** np.exp(2.3271 * fai[i])) + ) + ) z_d_output[i] = ZdMho_UCor z_0_output[i] = ZoMho_UCor - return(z_d_output, z_0_output) + return (z_d_output, z_0_output) -def RoughnessCalc(Roughnessmethod,zH,fai,pai,zMax,zSdev): - if Roughnessmethod == 'RT': - #Rule of thumb method - z_d_output = 0.7*zH - z_0_output = 0.1*zH - elif Roughnessmethod == 'Rau': + +def RoughnessCalc(Roughnessmethod, zH, fai, pai, zMax, zSdev): + if Roughnessmethod == "RT": + # Rule of thumb method + z_d_output = 0.7 * zH + z_0_output = 0.1 * zH + elif Roughnessmethod == "Rau": ##### Raupach 1994/95 #### - Cs=0.003 - Cr=0.3 - Stab=0.193 - UdivUmax=0.3 - Cdl=7.5 - k=0.4 - RauZdexpW=(math.exp(-((Cdl*2*fai)**0.5)))-1 - z_d_output= (1+ (RauZdexpW/((Cdl*2*fai)**0.5)))*zH - RauZoUtermW = 1/(min(((Cs+(Cr*fai))**0.5), UdivUmax)) - RauZoexpW = np.exp((-k*RauZoUtermW)+Stab) - z_0_output = ((1-(z_d_output/zH))*RauZoexpW)*zH - elif Roughnessmethod == 'Bot': - #Bottema + Cs = 0.003 + Cr = 0.3 + Stab = 0.193 + UdivUmax = 0.3 + Cdl = 7.5 + k = 0.4 + RauZdexpW = (math.exp(-((Cdl * 2 * fai) ** 0.5))) - 1 + z_d_output = (1 + (RauZdexpW / ((Cdl * 2 * fai) ** 0.5))) * zH + RauZoUtermW = 1 / (min(((Cs + (Cr * fai)) ** 0.5), UdivUmax)) + RauZoexpW = np.exp((-k * RauZoUtermW) + Stab) + z_0_output = ((1 - (z_d_output / zH)) * RauZoexpW) * zH + elif Roughnessmethod == "Bot": + # Bottema Cdh = 0.8 - k=0.4 - z_d_output = (pai**0.6)*zH - BotZoexpW =np.exp(-k/((0.5*fai*Cdh)**0.5)) - z_0_output = (zH - z_d_output)*(BotZoexpW) - elif Roughnessmethod == 'Mac': - #MacDonald + k = 0.4 + z_d_output = (pai**0.6) * zH + BotZoexpW = np.exp(-k / ((0.5 * fai * Cdh) ** 0.5)) + z_0_output = (zH - z_d_output) * (BotZoexpW) + elif Roughnessmethod == "Mac": + # MacDonald Clb = 1.2 - k=0.4 - #Staggered array + k = 0.4 + # Staggered array Alph = 4.43 Beet = 1.0 - #Square array - #Alph = 3.59 - #Beet = 0.55 - if zH > 0.: - z_d_output = (1+((Alph**-pai)*(pai-1)))*zH + # Square array + # Alph = 3.59 + # Beet = 0.55 + if zH > 0.0: + z_d_output = (1 + ((Alph**-pai) * (pai - 1))) * zH if z_d_output != zH: - z_0_output = (zH*((1-z_d_output/zH))*np.exp(-(0.5*(1.2/0.4**2)*(1-(z_d_output/zH))*fai)**-0.5)) + z_0_output = ( + zH + * ((1 - z_d_output / zH)) + * np.exp( + -( + ( + 0.5 + * (1.2 / 0.4**2) + * (1 - (z_d_output / zH)) + * fai + ) + ** -0.5 + ) + ) + ) else: - z_0_output = 0. + z_0_output = 0.0 else: - z_0_output = 0. - z_d_output = 0. - elif Roughnessmethod == 'Kan': - #Kanda + z_0_output = 0.0 + z_d_output = 0.0 + elif Roughnessmethod == "Kan": + # Kanda Kanmeth = 1 if Kanmeth == 1: - Ao = 1.29 - Bo = 0.36 - Co = -0.17 - A1 = 0.71 - B1= 20.21 - C1 = -0.77 + Ao = 1.29 + Bo = 0.36 + Co = -0.17 + A1 = 0.71 + B1 = 20.21 + C1 = -0.77 elif Kanmeth == 2: - Ao = 0.86 - Bo = 0.28 - Co = -0.18 - A1 = 0.93 - B1 = 8.93 - C1 = 4.68 - #First perform MacD method - #MacDonald + Ao = 0.86 + Bo = 0.28 + Co = -0.18 + A1 = 0.93 + B1 = 8.93 + C1 = 4.68 + # First perform MacD method + # MacDonald Clb = 1.2 - k=0.4 - #Staggered array + k = 0.4 + # Staggered array Alph = 4.43 Beet = 1.0 - #Square array - #Alph = 3.59 - #Beet = 0.55 - if zH > 0.: - z_d_output = (1+((Alph**-pai)*(pai-1)))*zH + # Square array + # Alph = 3.59 + # Beet = 0.55 + if zH > 0.0: + z_d_output = (1 + ((Alph**-pai) * (pai - 1))) * zH if z_d_output != zH: - z0Mac= (zH*((1-z_d_output/zH))*np.exp(-(0.5*(1.2/0.4**2)*(1-(z_d_output/zH))*fai)**-0.5)) + z0Mac = ( + zH + * ((1 - z_d_output / zH)) + * np.exp( + -( + ( + 0.5 + * (1.2 / 0.4**2) + * (1 - (z_d_output / zH)) + * fai + ) + ** -0.5 + ) + ) + ) else: - z0Mac = 0. - X=(zSdev+zH)/zMax - if (0= 0: - z_0_output = ((B1*(Y**2))+(C1*Y)+A1)*z0Mac + z_0_output = ((B1 * (Y**2)) + (C1 * Y) + A1) * z0Mac else: - z_0_output = A1*z0Mac + z_0_output = A1 * z0Mac else: - z_0_output = 0. - z_d_output = 0. - elif Roughnessmethod == 'Mho': - #Millward Hopkins + z_0_output = 0.0 + z_d_output = 0.0 + elif Roughnessmethod == "Mho": + # Millward Hopkins #### MHO - Heterogenous - Displacement Height #### CD = 1.2 CDD = 2 - k= 0.4 + k = 0.4 #### Millward-Hopkins (2011)- Uniform with correction #### - CD=1.2 + CD = 1.2 - if pai >=0.19: - ZdMho_U = (((19.2*pai) - 1 + (np.exp(-19.2*pai)))/((19.2*pai)*(1-(np.exp(-19.2*pai)))))*zH + if pai >= 0.19: + ZdMho_U = ( + ((19.2 * pai) - 1 + (np.exp(-19.2 * pai))) + / ((19.2 * pai) * (1 - (np.exp(-19.2 * pai)))) + ) * zH elif pai < 0.19: - ZdMho_U = (((117*pai) + ((187.2*(pai**3))-6.1)*(1-np.exp(-19.2*pai)))/((1+(114*pai)+(187*pai**3))*(1-(np.exp(-19.2*pai)))))*zH - ZoMhoexp_U = np.exp(-((0.5*CD*(k**-2)*fai)**-0.5)) - if zH>0.: - ZoMho_U=((1-(ZdMho_U/zH))* ZoMhoexp_U)*zH - ZdMho_UCor=zH*((ZdMho_U/zH)+((0.2375*np.log(pai)+1.1738)*(zSdev/zH))) - ZoMho_UCor= zH*((ZoMho_U/zH)+ (np.exp((0.8867*fai)-1)*((zSdev/zH)**np.exp(2.3271*fai)))) + ZdMho_U = ( + ( + (117 * pai) + + ((187.2 * (pai**3)) - 6.1) * (1 - np.exp(-19.2 * pai)) + ) + / ( + (1 + (114 * pai) + (187 * pai**3)) + * (1 - (np.exp(-19.2 * pai))) + ) + ) * zH + ZoMhoexp_U = np.exp(-((0.5 * CD * (k**-2) * fai) ** -0.5)) + if zH > 0.0: + ZoMho_U = ((1 - (ZdMho_U / zH)) * ZoMhoexp_U) * zH + ZdMho_UCor = zH * ( + (ZdMho_U / zH) + + ((0.2375 * np.log(pai) + 1.1738) * (zSdev / zH)) + ) + ZoMho_UCor = zH * ( + (ZoMho_U / zH) + + ( + np.exp((0.8867 * fai) - 1) + * ((zSdev / zH) ** np.exp(2.3271 * fai)) + ) + ) z_d_output = ZdMho_UCor z_0_output = ZoMho_UCor else: - z_0_output = 0. - z_d_output = 0. - return(z_d_output,z_0_output) \ No newline at end of file + z_0_output = 0.0 + z_d_output = 0.0 + return (z_d_output, z_0_output) diff --git a/util/SEBESOLWEIGCommonFiles/Perez_v3.py b/util/SEBESOLWEIGCommonFiles/Perez_v3.py index ec6392f..2ca4c4e 100644 --- a/util/SEBESOLWEIGCommonFiles/Perez_v3.py +++ b/util/SEBESOLWEIGCommonFiles/Perez_v3.py @@ -2,26 +2,27 @@ import numpy as np from ...util.SEBESOLWEIGCommonFiles.create_patches import create_patches + def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice, patch_option): """ This function calculates distribution of luminance on the skyvault based on Perez luminince distribution model. - + Created by: Fredrik Lindberg 20120527, fredrikl@gvc.gu.se Gothenburg University, Sweden Urban Climte Group - + Input parameters: - zen: Zenith angle of the Sun (in degrees) - azimuth: Azimuth angle of the Sun (in degrees) - radD: Horizontal diffuse radiation (W m-2) - radI: Direct radiation perpendicular to the Sun beam (W m-2) - jday: Day of year - + Output parameters: - lv: Relative luminance map (same dimensions as theta. gamma) - + acoeff=[1.353 -0.258 -0.269 -1.437 -1.222 -0.773 1.415 1.102 @@ -31,7 +32,7 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice, patch_option): -1.016 -0.367 1.008 1.405 -1.000 0.021 0.503 -0.512 -1.050 0.029 0.426 0.359]; - + bcoeff=[-0.767 0.001 1.273 -0.123 -0.205 0.037 -3.913 0.916 0.278 -0.181 -4.500 1.177 @@ -40,7 +41,7 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice, patch_option): 0.288 -0.533 -3.850 3.375 -0.300 0.192 0.702 -1.632 -0.325 0.116 0.778 0.003]; - + ccoeff=[2.800 0.600 1.238 1.000 6.975 0.177 6.448 -0.124 24.22 -13.08 -37.70 34.84 @@ -49,7 +50,7 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice, patch_option): 14.00 -0.999 -7.14 7.547 19.00 -5.000 1.243 -1.91 31.06 -14.50 -46.11 55.37]; - + dcoeff=[1.874 0.630 0.974 0.281 -1.580 -0.508 -1.781 0.108 -5.00 1.522 3.923 -2.62 @@ -58,7 +59,7 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice, patch_option): -3.40 -0.108 -1.075 1.57 -4.00 0.025 0.384 0.266 -7.23 0.405 13.35 0.623]; - + ecoeff=[0.035 -0.125 -0.572 0.994 0.262 0.067 -0.219 -0.428 -0.016 0.160 0.420 -0.556 @@ -77,36 +78,94 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice, patch_option): :return: """ - m_a1 = np.array([1.3525, -1.2219, -1.1000, -0.5484, -0.6000, -1.0156, -1.0000, -1.0500]) - m_a2 = np.array([-0.2576, -0.7730, -0.2515, -0.6654, -0.3566, -0.3670, 0.0211, 0.0289]) - m_a3 = np.array([-0.2690, 1.4148, 0.8952, -0.2672, -2.5000, 1.0078, 0.5025, 0.4260]) - m_a4 = np.array([-1.4366, 1.1016, 0.0156, 0.7117, 2.3250, 1.4051, -0.5119, 0.3590]) - m_b1 = np.array([-0.7670, -0.2054, 0.2782, 0.7234, 0.2937, 0.2875, -0.3000, -0.3250]) - m_b2 = np.array([0.0007, 0.0367, -0.1812, -0.6219, 0.0496, -0.5328, 0.1922, 0.1156]) - m_b3 = np.array([1.2734, -3.9128, -4.5000, -5.6812, -5.6812, -3.8500, 0.7023, 0.7781]) - m_b4 = np.array([-0.1233, 0.9156, 1.1766, 2.6297, 1.8415, 3.3750, -1.6317, 0.0025]) - m_c1 = np.array([2.8000, 6.9750, 24.7219, 33.3389, 21.0000, 14.0000, 19.0000, 31.0625]) - m_c2 = np.array([0.6004, 0.1774, -13.0812, -18.3000, -4.7656, -0.9999, -5.0000, -14.5000]) - m_c3 = np.array([1.2375, 6.4477, -37.7000, -62.2500, -21.5906, -7.1406, 1.2438, -46.1148]) - m_c4 = np.array([1.0000, -0.1239, 34.8438, 52.0781, 7.2492, 7.5469, -1.9094, 55.3750]) - m_d1 = np.array([1.8734, -1.5798, -5.0000, -3.5000, -3.5000, -3.4000, -4.0000, -7.2312]) - m_d2 = np.array([0.6297, -0.5081, 1.5218, 0.0016, -0.1554, -0.1078, 0.0250, 0.4050]) - m_d3 = np.array([0.9738, -1.7812, 3.9229, 1.1477, 1.4062, -1.0750, 0.3844, 13.3500]) - m_d4 = np.array([0.2809, 0.1080, -2.6204, 0.1062, 0.3988, 1.5702, 0.2656, 0.6234]) - m_e1 = np.array([0.0356, 0.2624, -0.0156, 0.4659, 0.0032, -0.0672, 1.0468, 1.5000]) - m_e2 = np.array([-0.1246, 0.0672, 0.1597, -0.3296, 0.0766, 0.4016, -0.3788, -0.6426]) - m_e3 = np.array([-0.5718, -0.2190, 0.4199, -0.0876, -0.0656, 0.3017, -2.4517, 1.8564]) - m_e4 = np.array([0.9938, -0.4285, -0.5562, -0.0329, -0.1294, -0.4844, 1.4656, 0.5636]) - + m_a1 = np.array( + [1.3525, -1.2219, -1.1000, -0.5484, -0.6000, -1.0156, -1.0000, -1.0500] + ) + m_a2 = np.array( + [-0.2576, -0.7730, -0.2515, -0.6654, -0.3566, -0.3670, 0.0211, 0.0289] + ) + m_a3 = np.array( + [-0.2690, 1.4148, 0.8952, -0.2672, -2.5000, 1.0078, 0.5025, 0.4260] + ) + m_a4 = np.array( + [-1.4366, 1.1016, 0.0156, 0.7117, 2.3250, 1.4051, -0.5119, 0.3590] + ) + m_b1 = np.array( + [-0.7670, -0.2054, 0.2782, 0.7234, 0.2937, 0.2875, -0.3000, -0.3250] + ) + m_b2 = np.array( + [0.0007, 0.0367, -0.1812, -0.6219, 0.0496, -0.5328, 0.1922, 0.1156] + ) + m_b3 = np.array( + [1.2734, -3.9128, -4.5000, -5.6812, -5.6812, -3.8500, 0.7023, 0.7781] + ) + m_b4 = np.array( + [-0.1233, 0.9156, 1.1766, 2.6297, 1.8415, 3.3750, -1.6317, 0.0025] + ) + m_c1 = np.array( + [2.8000, 6.9750, 24.7219, 33.3389, 21.0000, 14.0000, 19.0000, 31.0625] + ) + m_c2 = np.array( + [ + 0.6004, + 0.1774, + -13.0812, + -18.3000, + -4.7656, + -0.9999, + -5.0000, + -14.5000, + ] + ) + m_c3 = np.array( + [ + 1.2375, + 6.4477, + -37.7000, + -62.2500, + -21.5906, + -7.1406, + 1.2438, + -46.1148, + ] + ) + m_c4 = np.array( + [1.0000, -0.1239, 34.8438, 52.0781, 7.2492, 7.5469, -1.9094, 55.3750] + ) + m_d1 = np.array( + [1.8734, -1.5798, -5.0000, -3.5000, -3.5000, -3.4000, -4.0000, -7.2312] + ) + m_d2 = np.array( + [0.6297, -0.5081, 1.5218, 0.0016, -0.1554, -0.1078, 0.0250, 0.4050] + ) + m_d3 = np.array( + [0.9738, -1.7812, 3.9229, 1.1477, 1.4062, -1.0750, 0.3844, 13.3500] + ) + m_d4 = np.array( + [0.2809, 0.1080, -2.6204, 0.1062, 0.3988, 1.5702, 0.2656, 0.6234] + ) + m_e1 = np.array( + [0.0356, 0.2624, -0.0156, 0.4659, 0.0032, -0.0672, 1.0468, 1.5000] + ) + m_e2 = np.array( + [-0.1246, 0.0672, 0.1597, -0.3296, 0.0766, 0.4016, -0.3788, -0.6426] + ) + m_e3 = np.array( + [-0.5718, -0.2190, 0.4199, -0.0876, -0.0656, 0.3017, -2.4517, 1.8564] + ) + m_e4 = np.array( + [0.9938, -0.4285, -0.5562, -0.0329, -0.1294, -0.4844, 1.4656, 0.5636] + ) + acoeff = np.transpose(np.atleast_2d([m_a1, m_a2, m_a3, m_a4])) bcoeff = np.transpose(np.atleast_2d([m_b1, m_b2, m_b3, m_b4])) ccoeff = np.transpose(np.atleast_2d([m_c1, m_c2, m_c3, m_c4])) dcoeff = np.transpose(np.atleast_2d([m_d1, m_d2, m_d3, m_d4])) ecoeff = np.transpose(np.atleast_2d([m_e1, m_e2, m_e3, m_e4])) - deg2rad = np.pi/180 - rad2deg = 180/np.pi - altitude = 90-zen + deg2rad = np.pi / 180 + rad2deg = 180 / np.pi + altitude = 90 - zen zen = zen * deg2rad azimuth = azimuth * deg2rad altitude = altitude * deg2rad @@ -115,32 +174,43 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice, patch_option): Ibn = radI # Skyclearness - PerezClearness = ((Idh+Ibn)/(Idh+1.041*np.power(zen, 3)))/(1+1.041*np.power(zen, 3)) + PerezClearness = ((Idh + Ibn) / (Idh + 1.041 * np.power(zen, 3))) / ( + 1 + 1.041 * np.power(zen, 3) + ) # Extra terrestrial radiation - day_angle = jday*2*np.pi/365 - #I0=1367*(1+0.033*np.cos((2*np.pi*jday)/365)) - I0 = 1367*(1.00011+0.034221*np.cos(day_angle) + 0.00128*np.sin(day_angle)+0.000719 * - np.cos(2*day_angle)+0.000077*np.sin(2*day_angle)) # New from robinsson + day_angle = jday * 2 * np.pi / 365 + # I0=1367*(1+0.033*np.cos((2*np.pi*jday)/365)) + I0 = 1367 * ( + 1.00011 + + 0.034221 * np.cos(day_angle) + + 0.00128 * np.sin(day_angle) + + 0.000719 * np.cos(2 * day_angle) + + 0.000077 * np.sin(2 * day_angle) + ) # New from robinsson # Optical air mass # m=1/altitude; old - if altitude >= 10*deg2rad: - AirMass = 1/np.sin(altitude) - elif altitude < 0: # below equation becomes complex - AirMass = 1/np.sin(altitude)+0.50572*np.power(180*complex(altitude)/np.pi+6.07995, -1.6364) + if altitude >= 10 * deg2rad: + AirMass = 1 / np.sin(altitude) + elif altitude < 0: # below equation becomes complex + AirMass = 1 / np.sin(altitude) + 0.50572 * np.power( + 180 * complex(altitude) / np.pi + 6.07995, -1.6364 + ) else: - AirMass = 1/np.sin(altitude)+0.50572*np.power(180*altitude/np.pi+6.07995, -1.6364) + AirMass = 1 / np.sin(altitude) + 0.50572 * np.power( + 180 * altitude / np.pi + 6.07995, -1.6364 + ) # Skybrightness # if altitude*rad2deg+6.07995>=0 - PerezBrightness = (AirMass*radD)/I0 + PerezBrightness = (AirMass * radD) / I0 if Idh <= 10: # m_a=0;m_b=0;m_c=0;m_d=0;m_e=0; PerezBrightness = 0 - #if altitude < 0: - #print("Airmass") - #print(AirMass) - #print(PerezBrightness) + # if altitude < 0: + # print("Airmass") + # print(AirMass) + # print(PerezBrightness) # sky clearness bins if PerezClearness < 1.065: intClearness = 0 @@ -159,18 +229,60 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice, patch_option): if PerezClearness > 6.200: intClearness = 7 - m_a = acoeff[intClearness, 0] + acoeff[intClearness, 1] * zen + PerezBrightness * (acoeff[intClearness, 2] + acoeff[intClearness, 3] * zen) - m_b = bcoeff[intClearness, 0] + bcoeff[intClearness, 1] * zen + PerezBrightness * (bcoeff[intClearness, 2] + bcoeff[intClearness, 3] * zen) - m_e = ecoeff[intClearness, 0] + ecoeff[intClearness, 1] * zen + PerezBrightness * (ecoeff[intClearness, 2] + ecoeff[intClearness, 3] * zen) + m_a = ( + acoeff[intClearness, 0] + + acoeff[intClearness, 1] * zen + + PerezBrightness + * (acoeff[intClearness, 2] + acoeff[intClearness, 3] * zen) + ) + m_b = ( + bcoeff[intClearness, 0] + + bcoeff[intClearness, 1] * zen + + PerezBrightness + * (bcoeff[intClearness, 2] + bcoeff[intClearness, 3] * zen) + ) + m_e = ( + ecoeff[intClearness, 0] + + ecoeff[intClearness, 1] * zen + + PerezBrightness + * (ecoeff[intClearness, 2] + ecoeff[intClearness, 3] * zen) + ) if intClearness > 0: - m_c = ccoeff[intClearness, 0] + ccoeff[intClearness, 1] * zen + PerezBrightness * (ccoeff[intClearness, 2] + ccoeff[intClearness, 3] * zen) - m_d = dcoeff[intClearness, 0] + dcoeff[intClearness, 1] * zen + PerezBrightness * (dcoeff[intClearness, 2] + dcoeff[intClearness, 3] * zen) + m_c = ( + ccoeff[intClearness, 0] + + ccoeff[intClearness, 1] * zen + + PerezBrightness + * (ccoeff[intClearness, 2] + ccoeff[intClearness, 3] * zen) + ) + m_d = ( + dcoeff[intClearness, 0] + + dcoeff[intClearness, 1] * zen + + PerezBrightness + * (dcoeff[intClearness, 2] + dcoeff[intClearness, 3] * zen) + ) else: # different equations for c & d in clearness bin no. 1, from Robinsson - m_c = np.exp(np.power(PerezBrightness * (ccoeff[intClearness, 0] + ccoeff[intClearness, 1] * zen), ccoeff[intClearness, 2]))-1 - m_d = -np.exp(PerezBrightness * (dcoeff[intClearness, 0] + dcoeff[intClearness, 1] * zen)) + dcoeff[intClearness, 2] + \ - PerezBrightness * dcoeff[intClearness, 3] * PerezBrightness + m_c = ( + np.exp( + np.power( + PerezBrightness + * ( + ccoeff[intClearness, 0] + ccoeff[intClearness, 1] * zen + ), + ccoeff[intClearness, 2], + ) + ) + - 1 + ) + m_d = ( + -np.exp( + PerezBrightness + * (dcoeff[intClearness, 0] + dcoeff[intClearness, 1] * zen) + ) + + dcoeff[intClearness, 2] + + PerezBrightness * dcoeff[intClearness, 3] * PerezBrightness + ) # print 'a = ', m_a # print 'b = ', m_b @@ -182,12 +294,12 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice, patch_option): skyvaultalt = np.atleast_2d([]) skyvaultazi = np.atleast_2d([]) # Creating skyvault at one degree intervals - skyvaultalt = np.ones([90, 361])*90 + skyvaultalt = np.ones([90, 361]) * 90 skyvaultazi = np.empty((90, 361)) for j in range(90): - skyvaultalt[j, :] = 91-j + skyvaultalt[j, :] = 91 - j skyvaultazi[j, :] = range(361) - + elif patchchoice == 1: # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) skyvaultalt, skyvaultazi, _, _, _, _, _ = create_patches(patch_option) @@ -197,12 +309,18 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice, patch_option): skyvaultazi = skyvaultazi * deg2rad # Angular distance from the sun from Robinsson - cosSkySunAngle = np.sin(skyvaultalt) * np.sin(altitude) + \ - np.cos(altitude) * np.cos(skyvaultalt) * np.cos(np.abs(skyvaultazi-azimuth)) + cosSkySunAngle = np.sin(skyvaultalt) * np.sin(altitude) + np.cos( + altitude + ) * np.cos(skyvaultalt) * np.cos(np.abs(skyvaultazi - azimuth)) # Main equation - lv = (1 + m_a * np.exp(m_b / np.cos(skyvaultzen))) * ((1 + m_c * np.exp(m_d * np.arccos(cosSkySunAngle)) + - m_e * cosSkySunAngle * cosSkySunAngle)) + lv = (1 + m_a * np.exp(m_b / np.cos(skyvaultzen))) * ( + ( + 1 + + m_c * np.exp(m_d * np.arccos(cosSkySunAngle)) + + m_e * cosSkySunAngle * cosSkySunAngle + ) + ) # Normalisation lv = lv / np.sum(lv) @@ -217,10 +335,10 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice, patch_option): # pause(1) if patchchoice == 1: - #x = np.atleast_2d([]) - #lv = np.transpose(np.append(np.append(np.append(x, skyvaultalt*rad2deg), skyvaultazi*rad2deg), lv)) - x = np.transpose(np.atleast_2d(skyvaultalt*rad2deg)) - y = np.transpose(np.atleast_2d(skyvaultazi*rad2deg)) + # x = np.atleast_2d([]) + # lv = np.transpose(np.append(np.append(np.append(x, skyvaultalt*rad2deg), skyvaultazi*rad2deg), lv)) + x = np.transpose(np.atleast_2d(skyvaultalt * rad2deg)) + y = np.transpose(np.atleast_2d(skyvaultazi * rad2deg)) z = np.transpose(np.atleast_2d(lv)) lv = np.append(np.append(x, y, axis=1), z, axis=1) - return lv, PerezClearness, PerezBrightness \ No newline at end of file + return lv, PerezClearness, PerezBrightness diff --git a/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload.py b/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload.py index 2593d35..e875532 100644 --- a/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload.py +++ b/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload.py @@ -1,11 +1,14 @@ from __future__ import absolute_import + # from importdata import importdata from . import sun_position as sp -#import sun_position as sp + +# import sun_position as sp import numpy as np import datetime import calendar + def Solweig_2015a_metdata_noload(inputdata, location, UTC): """ This function is used to process the input meteorological file. @@ -19,18 +22,18 @@ def Solweig_2015a_metdata_noload(inputdata, location, UTC): met = inputdata data_len = len(met[:, 0]) - dectime = met[:, 1]+met[:, 2] / 24 + met[:, 3] / (60*24.) - dectimemin = met[:, 3] / (60*24.) + dectime = met[:, 1] + met[:, 2] / 24 + met[:, 3] / (60 * 24.0) + dectimemin = met[:, 3] / (60 * 24.0) if data_len == 1: halftimestepdec = 0 else: - halftimestepdec = (dectime[1] - dectime[0]) / 2. + halftimestepdec = (dectime[1] - dectime[0]) / 2.0 time = dict() - time['sec'] = 0 - time['UTC'] = UTC - sunmaximum = 0. - leafon1 = 97 #TODO this should change - leafoff1 = 300 #TODO this should change + time["sec"] = 0 + time["UTC"] = UTC + sunmaximum = 0.0 + leafon1 = 97 # TODO this should change + leafoff1 = 300 # TODO this should change # initialize matrices altitude = np.empty(shape=(1, data_len)) @@ -46,46 +49,56 @@ def Solweig_2015a_metdata_noload(inputdata, location, UTC): for i, row in enumerate(met[:, 0]): if met[i, 1] == 221: test = 4 - YMD = datetime.datetime(int(met[i, 0]), 1, 1) + datetime.timedelta(int(met[i, 1]) - 1) + YMD = datetime.datetime(int(met[i, 0]), 1, 1) + datetime.timedelta( + int(met[i, 1]) - 1 + ) # Finding maximum altitude in 15 min intervals (20141027) if (i == 0) or (np.mod(dectime[i], np.floor(dectime[i])) == 0): - fifteen = 0. - sunmaximum = -90. - sunmax['zenith'] = 90. - while sunmaximum <= 90. - sunmax['zenith']: - sunmaximum = 90. - sunmax['zenith'] - fifteen = fifteen + 15. / 1440. - HM = datetime.timedelta(days=(60*10)/1440.0 + fifteen) + fifteen = 0.0 + sunmaximum = -90.0 + sunmax["zenith"] = 90.0 + while sunmaximum <= 90.0 - np.array(sunmax["zenith"]).item(): + sunmaximum = 90.0 - np.array(sunmax["zenith"]).item() + fifteen = fifteen + 15.0 / 1440.0 + HM = datetime.timedelta(days=(60 * 10) / 1440.0 + fifteen) YMDHM = YMD + HM - time['year'] = YMDHM.year - time['month'] = YMDHM.month - time['day'] = YMDHM.day - time['hour'] = YMDHM.hour - time['min'] = YMDHM.minute - sunmax = sp.sun_position(time,location) + time["year"] = YMDHM.year + time["month"] = YMDHM.month + time["day"] = YMDHM.day + time["hour"] = YMDHM.hour + time["min"] = YMDHM.minute + sunmax = sp.sun_position(time, location) altmax[0, i] = sunmaximum half = datetime.timedelta(days=halftimestepdec) H = datetime.timedelta(hours=met[i, 2]) M = datetime.timedelta(minutes=met[i, 3]) YMDHM = YMD + H + M - half - time['year'] = YMDHM.year - time['month'] = YMDHM.month - time['day'] = YMDHM.day - time['hour'] = YMDHM.hour - time['min'] = YMDHM.minute + time["year"] = YMDHM.year + time["month"] = YMDHM.month + time["day"] = YMDHM.day + time["hour"] = YMDHM.hour + time["min"] = YMDHM.minute sun = sp.sun_position(time, location) - if (sun['zenith'] > 89.0) & (sun['zenith'] <= 90.0): # Hopefully fixes weird values in Perez et al. when altitude < 1.0, i.e. close to sunrise/sunset - sun['zenith'] = 89.0 - altitude[0, i] = 90. - sun['zenith'] - zen[0, i] = sun['zenith'] * (np.pi/180.) - azimuth[0, i] = sun['azimuth'] + sun_zenith = np.array(sun["zenith"]).item() + sun_azimuth = np.array(sun["azimuth"]).item() + + if (sun_zenith > 89.0) & (sun_zenith <= 90.0): + sun_zenith = 89.0 + + altitude[0, i] = 90.0 - sun_zenith + zen[0, i] = sun_zenith * (np.pi / 180.0) + azimuth[0, i] = sun_azimuth # day of year and check for leap year - if calendar.isleap(time['year']): - dayspermonth = np.atleast_2d([31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]) + if calendar.isleap(time["year"]): + dayspermonth = np.atleast_2d( + [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + ) else: - dayspermonth = np.atleast_2d([31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]) + dayspermonth = np.atleast_2d( + [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + ) # jday[0, i] = np.sum(dayspermonth[0, 0:time['month']-1]) + time['day'] # bug when a new day 20191015 YYYY[0, i] = met[i, 0] doy = YMD.timetuple().tm_yday @@ -96,7 +109,3 @@ def Solweig_2015a_metdata_noload(inputdata, location, UTC): leafon[0, i] = 0 return YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax - - - - diff --git a/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b.py b/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b.py index 72cba49..3afcf08 100644 --- a/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b.py +++ b/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b.py @@ -1,13 +1,14 @@ from __future__ import absolute_import -author = 'xlinfr' + +author = "xlinfr" from . import sun_distance import numpy as np import math -def clearnessindex_2013b(zen, jday, Ta, RH, radG, location, P): - """ Clearness Index at the Earth's surface calculated from Crawford and Duchon 1999 +def clearnessindex_2013b(zen, jday, Ta, RH, radG, location, P): + """Clearness Index at the Earth's surface calculated from Crawford and Duchon 1999 :param zen: zenith angle in radians :param jday: day of year @@ -20,33 +21,39 @@ def clearnessindex_2013b(zen, jday, Ta, RH, radG, location, P): """ if P == -999.0: - p = 1013. # Pressure in millibars + p = 1013.0 # Pressure in millibars else: - p = P*10. # Convert from hPa to millibars + p = P * 10.0 # Convert from hPa to millibars Itoa = 1370.0 # Effective solar constant - D = sun_distance.sun_distance(jday) # irradiance differences due to Sun-Earth distances - m = 35. * np.cos(zen) * ((1224. * (np.cos(zen)**2) + 1) ** (-1/2.)) # optical air mass at p=1013 - Trpg = 1.021-0.084*(m*(0.000949*p+0.051))**0.5 # Transmission coefficient for Rayliegh scattering and permanent gases + D = sun_distance.sun_distance( + jday + ) # irradiance differences due to Sun-Earth distances + m = ( + 35.0 * np.cos(zen) * ((1224.0 * (np.cos(zen) ** 2) + 1) ** (-1 / 2.0)) + ) # optical air mass at p=1013 + Trpg = ( + 1.021 - 0.084 * (m * (0.000949 * p + 0.051)) ** 0.5 + ) # Transmission coefficient for Rayliegh scattering and permanent gases # empirical constant depending on latitude - if location['latitude'] < 10.: + if location["latitude"] < 10.0: G = [3.37, 2.85, 2.80, 2.64] - elif location['latitude'] >= 10. and location['latitude'] < 20.: + elif location["latitude"] >= 10.0 and location["latitude"] < 20.0: G = [2.99, 3.02, 2.70, 2.93] - elif location['latitude'] >= 20. and location['latitude'] <30.: + elif location["latitude"] >= 20.0 and location["latitude"] < 30.0: G = [3.60, 3.00, 2.98, 2.93] - elif location['latitude'] >= 30. and location['latitude'] <40.: + elif location["latitude"] >= 30.0 and location["latitude"] < 40.0: G = [3.04, 3.11, 2.92, 2.94] - elif location['latitude'] >= 40. and location['latitude'] <50.: + elif location["latitude"] >= 40.0 and location["latitude"] < 50.0: G = [2.70, 2.95, 2.77, 2.71] - elif location['latitude'] >= 50. and location['latitude'] <60.: + elif location["latitude"] >= 50.0 and location["latitude"] < 60.0: G = [2.52, 3.07, 2.67, 2.93] - elif location['latitude'] >= 60. and location['latitude'] <70.: + elif location["latitude"] >= 60.0 and location["latitude"] < 70.0: G = [1.76, 2.69, 2.61, 2.61] - elif location['latitude'] >= 70. and location['latitude'] <80.: + elif location["latitude"] >= 70.0 and location["latitude"] < 80.0: G = [1.60, 1.67, 2.24, 2.63] - elif location['latitude'] >= 80. and location['latitude'] <90.: + elif location["latitude"] >= 80.0 and location["latitude"] < 90.0: G = [1.11, 1.44, 1.94, 2.02] if jday > 335 or jday <= 60: @@ -61,28 +68,32 @@ def clearnessindex_2013b(zen, jday, Ta, RH, radG, location, P): # dewpoint calculation a2 = 17.27 b2 = 237.7 - Td = (b2*(((a2*Ta)/(b2+Ta))+np.log(RH)))/(a2-(((a2*Ta)/(b2+Ta))+np.log(RH))) - Td = (Td*1.8)+32 # Dewpoint (F) - u = np.exp(0.1133-np.log(G+1)+0.0393*Td) # Precipitable water - Tw = 1-0.077*((u*m)**0.3) # Transmission coefficient for water vapor + Td = (b2 * (((a2 * Ta) / (b2 + Ta)) + np.log(RH))) / ( + a2 - (((a2 * Ta) / (b2 + Ta)) + np.log(RH)) + ) + Td = (Td * 1.8) + 32 # Dewpoint (F) + u = np.exp(0.1133 - np.log(G + 1) + 0.0393 * Td) # Precipitable water + Tw = 1 - 0.077 * ( + (u * m) ** 0.3 + ) # Transmission coefficient for water vapor Tar = 0.935**m # Transmission coefficient for aerosols - I0=Itoa*np.cos(zen)*Trpg*Tw*D*Tar - if abs(zen)>np.pi/2: + I0 = Itoa * np.cos(zen) * Trpg * Tw * D * Tar + if abs(zen) > np.pi / 2: I0 = 0 # b=I0==abs(zen)>np.pi/2 # I0(b==1)=0 # clear b; - if not(np.isreal(I0)): + if not (np.isreal(I0)): I0 = 0 - corr=0.1473*np.log(90-(zen/np.pi*180))+0.3454 # 20070329 + corr = 0.1473 * np.log(90 - (zen / np.pi * 180)) + 0.3454 # 20070329 CIuncorr = radG / I0 - CI = CIuncorr + (1-corr) - I0et = Itoa*np.cos(zen)*D # extra terrestial solar radiation + CI = CIuncorr + (1 - corr) + I0et = Itoa * np.cos(zen) * D # extra terrestial solar radiation Kt = radG / I0et if math.isnan(CI): - CI = float('Inf') + CI = float("Inf") - return I0, CI, Kt, I0et, CIuncorr \ No newline at end of file + return I0, CI, Kt, I0et, CIuncorr diff --git a/util/SEBESOLWEIGCommonFiles/create_patches.py b/util/SEBESOLWEIGCommonFiles/create_patches.py index ed59e63..cf30771 100644 --- a/util/SEBESOLWEIGCommonFiles/create_patches.py +++ b/util/SEBESOLWEIGCommonFiles/create_patches.py @@ -1,52 +1,97 @@ import numpy as np + def create_patches(patch_option): - deg2rad = np.pi/180 + deg2rad = np.pi / 180 # patch_option = 1 = 145 patches (Robinson & Stone, 2004) # patch_option = 2 = 153 patches (Wallenberg et al., 2022) - # patch_option = 3 = 306 patches -> test + # patch_option = 3 = 306 patches -> test # patch_option = 4 = 612 patches -> test skyvaultalt = np.atleast_2d([]) skyvaultazi = np.atleast_2d([]) - + # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) # Patch option 1, 145 patches, Original Robinson & Stone (2004) after Tregenza (1987)/Tregenza & Sharples (1993) if patch_option == 1: annulino = np.array([0, 12, 24, 36, 48, 60, 72, 84, 90]) - skyvaultaltint = np.array([6, 18, 30, 42, 54, 66, 78, 90]) # Robinson & Stone (2004) - azistart = np.array([0, 4, 2, 5, 8, 0, 10, 0]) # Fredrik/Nils - patches_in_band = np.array([30, 30, 24, 24, 18, 12, 6, 1]) # Robinson & Stone (2004) + skyvaultaltint = np.array( + [6, 18, 30, 42, 54, 66, 78, 90] + ) # Robinson & Stone (2004) + azistart = np.array([0, 4, 2, 5, 8, 0, 10, 0]) # Fredrik/Nils + patches_in_band = np.array( + [30, 30, 24, 24, 18, 12, 6, 1] + ) # Robinson & Stone (2004) # Patch option 2, 153 patches, Wallenberg et al. (2022) elif patch_option == 2: annulino = np.array([0, 12, 24, 36, 48, 60, 72, 84, 90]) - skyvaultaltint = np.array([6, 18, 30, 42, 54, 66, 78, 90]) # Robinson & Stone (2004) - azistart = np.array([0, 4, 2, 5, 8, 0, 10, 0]) # Fredrik/Nils - patches_in_band = np.array([31, 30, 28, 24, 19, 13, 7, 1]) # Nils + skyvaultaltint = np.array( + [6, 18, 30, 42, 54, 66, 78, 90] + ) # Robinson & Stone (2004) + azistart = np.array([0, 4, 2, 5, 8, 0, 10, 0]) # Fredrik/Nils + patches_in_band = np.array([31, 30, 28, 24, 19, 13, 7, 1]) # Nils # Patch option 3, 306 patches, test elif patch_option == 3: annulino = np.array([0, 12, 24, 36, 48, 60, 72, 84, 90]) - skyvaultaltint = np.array([6, 18, 30, 42, 54, 66, 78, 90]) # Robinson & Stone (2004) - azistart = np.array([0, 4, 2, 5, 8, 0, 10, 0]) # Fredrik/Nils - patches_in_band = np.array([31*2, 30*2, 28*2, 24*2, 19*2, 13*2, 7*2, 1]) # Nils + skyvaultaltint = np.array( + [6, 18, 30, 42, 54, 66, 78, 90] + ) # Robinson & Stone (2004) + azistart = np.array([0, 4, 2, 5, 8, 0, 10, 0]) # Fredrik/Nils + patches_in_band = np.array( + [31 * 2, 30 * 2, 28 * 2, 24 * 2, 19 * 2, 13 * 2, 7 * 2, 1] + ) # Nils # Patch option 4, 612 patches, test elif patch_option == 4: - annulino = np.array([0, 4.5, 9, 15, 21, 27, 33, 39, 45, 51, 57, 63, 69, 75, 81, 90]) # Nils - skyvaultaltint = np.array([3, 9, 15, 21, 27, 33, 39, 45, 51, 57, 63, 69, 75, 81, 90]) # Nils - patches_in_band = np.array([31*2, 31*2, 30*2, 30*2, 28*2, 28*2, 24*2, 24*2, 19*2, 19*2, 13*2, 13*2, 7*2, 7*2, 1]) # Nils - azistart = np.array([0, 0, 4, 4, 2, 2, 5, 5, 8, 8, 0, 0, 10, 10, 0]) # Nils + annulino = np.array( + [0, 4.5, 9, 15, 21, 27, 33, 39, 45, 51, 57, 63, 69, 75, 81, 90] + ) # Nils + skyvaultaltint = np.array( + [3, 9, 15, 21, 27, 33, 39, 45, 51, 57, 63, 69, 75, 81, 90] + ) # Nils + patches_in_band = np.array( + [ + 31 * 2, + 31 * 2, + 30 * 2, + 30 * 2, + 28 * 2, + 28 * 2, + 24 * 2, + 24 * 2, + 19 * 2, + 19 * 2, + 13 * 2, + 13 * 2, + 7 * 2, + 7 * 2, + 1, + ] + ) # Nils + azistart = np.array( + [0, 0, 4, 4, 2, 2, 5, 5, 8, 8, 0, 0, 10, 10, 0] + ) # Nils - skyvaultaziint = np.array([360/patches for patches in patches_in_band]) + skyvaultaziint = np.array([360 / patches for patches in patches_in_band]) for j in range(0, skyvaultaltint.shape[0]): for k in range(0, patches_in_band[j]): skyvaultalt = np.append(skyvaultalt, skyvaultaltint[j]) - skyvaultazi = np.append(skyvaultazi, k*skyvaultaziint[j] + azistart[j]) + skyvaultazi = np.append( + skyvaultazi, k * skyvaultaziint[j] + azistart[j] + ) # skyvaultzen = (90 - skyvaultalt) * deg2rad # skyvaultalt = skyvaultalt * deg2rad # skyvaultazi = skyvaultazi * deg2rad - return skyvaultalt, skyvaultazi, annulino, skyvaultaltint, patches_in_band, skyvaultaziint, azistart \ No newline at end of file + return ( + skyvaultalt, + skyvaultazi, + annulino, + skyvaultaltint, + patches_in_band, + skyvaultaziint, + azistart, + ) diff --git a/util/SEBESOLWEIGCommonFiles/diffusefraction.py b/util/SEBESOLWEIGCommonFiles/diffusefraction.py index 1f74970..93a31d5 100644 --- a/util/SEBESOLWEIGCommonFiles/diffusefraction.py +++ b/util/SEBESOLWEIGCommonFiles/diffusefraction.py @@ -1,7 +1,8 @@ from __future__ import division import numpy as np -def diffusefraction(radG,altitude,Kt,Ta,RH): + +def diffusefraction(radG, altitude, Kt, Ta, RH): """ This function estimates diffuse and directbeam radiation according to Reindl et al (1990), Solar Energy 45:1 @@ -14,32 +15,46 @@ def diffusefraction(radG,altitude,Kt,Ta,RH): :return: """ - alfa = altitude*(np.pi/180) + alfa = altitude * (np.pi / 180) if Ta <= -999.00 or RH <= -999.00 or np.isnan(Ta) or np.isnan(RH): if Kt <= 0.3: - radD = radG*(1.020-0.248*Kt) + radD = radG * (1.020 - 0.248 * Kt) elif Kt > 0.3 and Kt < 0.78: - radD = radG*(1.45-1.67*Kt) + radD = radG * (1.45 - 1.67 * Kt) else: - radD = radG*0.147 + radD = radG * 0.147 else: - RH = RH/100 + RH = RH / 100 if Kt <= 0.3: - radD = radG*(1 - 0.232 * Kt + 0.0239 * np.sin(alfa) - 0.000682 * Ta + 0.0195 * RH) + radD = radG * ( + 1 + - 0.232 * Kt + + 0.0239 * np.sin(alfa) + - 0.000682 * Ta + + 0.0195 * RH + ) elif Kt > 0.3 and Kt < 0.78: - radD = radG*(1.329- 1.716 * Kt + 0.267 * np.sin(alfa) - 0.00357 * Ta + 0.106 * RH) + radD = radG * ( + 1.329 + - 1.716 * Kt + + 0.267 * np.sin(alfa) + - 0.00357 * Ta + + 0.106 * RH + ) else: - radD = radG*(0.426 * Kt - 0.256 * np.sin(alfa) + 0.00349 * Ta + 0.0734 * RH) + radD = radG * ( + 0.426 * Kt - 0.256 * np.sin(alfa) + 0.00349 * Ta + 0.0734 * RH + ) - radI = (radG - radD)/(np.sin(alfa)) + radI = (radG - radD) / (np.sin(alfa)) # Corrections for low sun altitudes (20130307) if radI < 0: radI = 0 if altitude < 1 and radI > radG: - radI=radG + radI = radG if radD > radG: radD = radG diff --git a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py index b69522c..8ac7ae0 100644 --- a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py +++ b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py @@ -2,57 +2,79 @@ from __future__ import division import numpy as np from math import radians + # from scipy.ndimage.filters import median_filter + def shade_on_walls(azimuth, aspect, walls, dsm, f): # wall shadows wall parameterization - wallbol = (walls > 0).astype(float) + wallbol = (walls > 0).astype(float) # Removing walls in shadow due to selfshadowing - azilow = azimuth-np.pi/2 - azihigh = azimuth+np.pi/2 - - if azilow >= 0 and azihigh < 2*np.pi: # 90 to 270 (SHADOW) - facesh = (np.logical_or(aspect < azilow, aspect >= azihigh).astype(float)-wallbol+1) - elif azilow < 0 and azihigh <= 2*np.pi: # 0 to 90 - azilow = azilow + 2*np.pi - facesh = np.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 # (SHADOW) # check for the -1 - elif azilow > 0 and azihigh >= 2*np.pi: # 270 to 360 - azihigh = azihigh-2*np.pi - facesh = np.logical_or(aspect > azilow, aspect <= azihigh)*-1 + 1 # (SHADOW) - - sh = np.copy(f-dsm) # shadow volume - facesun = np.logical_and(facesh + (walls > 0).astype(float) == 1, walls > 0).astype(float) - wallsun = np.copy(walls-sh) + azilow = azimuth - np.pi / 2 + azihigh = azimuth + np.pi / 2 + + if azilow >= 0 and azihigh < 2 * np.pi: # 90 to 270 (SHADOW) + facesh = ( + np.logical_or(aspect < azilow, aspect >= azihigh).astype(float) + - wallbol + + 1 + ) + elif azilow < 0 and azihigh <= 2 * np.pi: # 0 to 90 + azilow = azilow + 2 * np.pi + facesh = ( + np.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 + ) # (SHADOW) # check for the -1 + elif azilow > 0 and azihigh >= 2 * np.pi: # 270 to 360 + azihigh = azihigh - 2 * np.pi + facesh = ( + np.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 + ) # (SHADOW) + + sh = np.copy(f - dsm) # shadow volume + facesun = np.logical_and( + facesh + (walls > 0).astype(float) == 1, walls > 0 + ).astype(float) + wallsun = np.copy(walls - sh) wallsun[wallsun < 0] = 0 - wallsun[facesh == 1] = 0 # Removing walls in "self"-shadow - wallsh = np.copy(walls-wallsun) + wallsun[facesh == 1] = 0 # Removing walls in "self"-shadow + wallsh = np.copy(walls - wallsun) sh = np.logical_not(np.logical_not(sh)).astype(float) - sh = sh * -1 + 1 + sh = sh * -1 + 1 return sh, wallsh, wallsun, facesh, facesun -def shadowingfunction_wallheight_13(a, azimuth, altitude, scale, walls, aspect, walls_scheme=False, aspect_scheme=False): + +def shadowingfunction_wallheight_13( + a, + azimuth, + altitude, + scale, + walls, + aspect, + walls_scheme=False, + aspect_scheme=False, +): """ This m.file calculates shadows on a DSM and shadow height on building walls. - + INPUTS: a = DSM azimuth and altitude = sun position scale= scale of DSM (1 meter pixels=1, 2 meter pixels=0.5) walls= pixel row 'outside' buildings. will be calculated if empty aspect = normal aspect of buildings walls - + OUTPUT: sh=ground and roof shadow wallsh = height of wall that is in shadow wallsun = hieght of wall that is in sun - + Fredrik Lindberg 2012-03-19 fredrikl@gvc.gu.se - + Utdate 2013-03-13 - bugfix for walls alinged with sun azimuths :param a: @@ -91,10 +113,10 @@ def shadowingfunction_wallheight_13(a, azimuth, altitude, scale, walls, aspect, dz = 0 temp = np.zeros((sizex, sizey)) wallbol = (walls > 0).astype(float) - + # other loop parameters amaxvalue = np.max(a) - pibyfour = np.pi/4 + pibyfour = np.pi / 4 threetimespibyfour = 3 * pibyfour fivetimespibyfour = 5 * pibyfour seventimespibyfour = 7 * pibyfour @@ -103,17 +125,18 @@ def shadowingfunction_wallheight_13(a, azimuth, altitude, scale, walls, aspect, tanazimuth = np.tan(azimuth) signsinazimuth = np.sign(sinazimuth) signcosazimuth = np.sign(cosazimuth) - dssin = np.abs(1/sinazimuth) - dscos = np.abs(1/cosazimuth) - tanaltitudebyscale = np.tan(altitude)/scale + dssin = np.abs(1 / sinazimuth) + dscos = np.abs(1 / cosazimuth) + tanaltitudebyscale = np.tan(altitude) / scale index = 1 # main loop while (amaxvalue >= dz) and (np.abs(dx) < sizex) and (np.abs(dy) < sizey): - if (pibyfour <= azimuth and azimuth < threetimespibyfour) or \ - (fivetimespibyfour <= azimuth and azimuth < seventimespibyfour): + if (pibyfour <= azimuth and azimuth < threetimespibyfour) or ( + fivetimespibyfour <= azimuth and azimuth < seventimespibyfour + ): dy = signsinazimuth * index dx = -1 * signcosazimuth * np.abs(np.round(index / tanazimuth)) ds = dssin @@ -129,18 +152,18 @@ def shadowingfunction_wallheight_13(a, azimuth, altitude, scale, walls, aspect, absdx = np.abs(dx) absdy = np.abs(dy) - xc1 = int((dx+absdx)/2) - xc2 = int(sizex+(dx-absdx)/2) - yc1 = int((dy+absdy)/2) - yc2 = int(sizey+(dy-absdy)/2) + xc1 = int((dx + absdx) / 2) + xc2 = int(sizex + (dx - absdx) / 2) + yc1 = int((dy + absdy) / 2) + yc2 = int(sizey + (dy - absdy) / 2) - xp1 = int(-((dx-absdx)/2)) - xp2 = int(sizex-(dx+absdx)/2) - yp1 = int(-((dy-absdy)/2)) - yp2 = int(sizey-(dy+absdy)/2) + xp1 = int(-((dx - absdx) / 2)) + xp2 = int(sizex - (dx + absdx) / 2) + yp1 = int(-((dy - absdy) / 2)) + yp2 = int(sizey - (dy + absdy) / 2) temp[xp1:xp2, yp1:yp2] = a[xc1:xc2, yc1:yc2] - dz - f = np.fmax(f, temp) #Moving building shadow + f = np.fmax(f, temp) # Moving building shadow index = index + 1 @@ -167,9 +190,17 @@ def shadowingfunction_wallheight_13(a, azimuth, altitude, scale, walls, aspect, # sh = np.logical_not(np.logical_not(sh)).astype(float) # sh = sh * -1 + 1 - sh, wallsh, wallsun, facesh, facesun = shade_on_walls(azimuth, aspect, walls, a, f) + sh, wallsh, wallsun, facesh, facesun = shade_on_walls( + azimuth, aspect, walls, a, f + ) if walls_scheme is not False: - sh_, wallsh_, wallsun_, facesh_, facesun_ = shade_on_walls(azimuth, aspect_scheme, walls_scheme, a, f) + sh_, wallsh_, wallsun_, facesh_, facesun_ = shade_on_walls( + azimuth, aspect_scheme, walls_scheme, a, f + ) shade_on_wall = wallsh_.copy() - return (sh, wallsh, wallsun, facesh, facesun, shade_on_wall) if walls_scheme is not False else (sh, wallsh, wallsun, facesh, facesun) + return ( + (sh, wallsh, wallsun, facesh, facesun, shade_on_wall) + if walls_scheme is not False + else (sh, wallsh, wallsun, facesh, facesun) + ) diff --git a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py index 0e4b3e4..22256c4 100644 --- a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py +++ b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py @@ -1,50 +1,76 @@ from __future__ import division import numpy as np + # import matplotlib.pylab as plt + def shade_on_walls(azimuth, aspect, walls, dsm, f, shvoveg): # wall shadows wall parameterization wallbol = (walls > 0).astype(float) - + # Removing walls in shadow due to selfshadowing - azilow = azimuth - np.pi/2 - azihigh = azimuth + np.pi/2 - if azilow >= 0 and azihigh < 2*np.pi: # 90 to 270 (SHADOW) - facesh = np.logical_or(aspect < azilow, aspect >= azihigh).astype(float) - wallbol + 1 # TODO check - elif azilow < 0 and azihigh <= 2*np.pi: # 0 to 90 - azilow = azilow + 2*np.pi - facesh = np.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 # (SHADOW) - elif azilow > 0 and azihigh >= 2*np.pi: # 270 to 360 + azilow = azimuth - np.pi / 2 + azihigh = azimuth + np.pi / 2 + if azilow >= 0 and azihigh < 2 * np.pi: # 90 to 270 (SHADOW) + facesh = ( + np.logical_or(aspect < azilow, aspect >= azihigh).astype(float) + - wallbol + + 1 + ) # TODO check + elif azilow < 0 and azihigh <= 2 * np.pi: # 0 to 90 + azilow = azilow + 2 * np.pi + facesh = ( + np.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 + ) # (SHADOW) + elif azilow > 0 and azihigh >= 2 * np.pi: # 270 to 360 azihigh -= 2 * np.pi - facesh = np.logical_or(aspect > azilow, aspect <= azihigh)*-1 + 1 # (SHADOW) + facesh = ( + np.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 + ) # (SHADOW) - shvo = f - dsm # building shadow volume - facesun = np.logical_and(facesh + (walls > 0).astype(float) == 1, walls > 0).astype(float) - wallsun = np.copy(walls-shvo) + shvo = f - dsm # building shadow volume + facesun = np.logical_and( + facesh + (walls > 0).astype(float) == 1, walls > 0 + ).astype(float) + wallsun = np.copy(walls - shvo) wallsun[wallsun < 0] = 0 - wallsun[facesh == 1] = 0 # Removing walls in "self"-shadow - wallsh = np.copy(walls-wallsun) + wallsun[facesh == 1] = 0 # Removing walls in "self"-shadow + wallsh = np.copy(walls - wallsun) wallshve = shvoveg * wallbol wallshve = wallshve - wallsh wallshve[wallshve < 0] = 0 id = np.where(wallshve > walls) wallshve[id] = walls[id] - wallsun = wallsun-wallshve # problem with wallshve only + wallsun = wallsun - wallshve # problem with wallshve only id = np.where(wallsun < 0) wallshve[id] = 0 wallsun[id] = 0 # if np.sum(wallshve <= 0) == wallshve.size: - # wallshve[:, :] = 0 + # wallshve[:, :] = 0 return wallsh, wallsun, wallshve, facesh, facesun -def shadowingfunction_wallheight_23(a, vegdem, vegdem2, azimuth, altitude, scale, amaxvalue, bush, walls, aspect, walls_scheme=False, aspect_scheme=False): + +def shadowingfunction_wallheight_23( + a, + vegdem, + vegdem2, + azimuth, + altitude, + scale, + amaxvalue, + bush, + walls, + aspect, + walls_scheme=False, + aspect_scheme=False, +): """ This function calculates shadows on a DSM and shadow height on building walls including both buildings and vegetion units. New functionallity to deal with pergolas, August 2021 - + INPUTS: a = DSM vegdem = Vegetation canopy DSM (magl) @@ -53,7 +79,7 @@ def shadowingfunction_wallheight_23(a, vegdem, vegdem2, azimuth, altitude, scale scale= scale of DSM (1 meter pixels=1, 2 meter pixels=0.5) walls= pixel row 'outside' buildings. will be calculated if empty aspect=normal aspect of walls - + OUTPUT: sh=ground and roof shadow @@ -78,14 +104,14 @@ def shadowingfunction_wallheight_23(a, vegdem, vegdem2, azimuth, altitude, scale """ # conversion - degrees = np.pi/180. + degrees = np.pi / 180.0 azimuth *= degrees altitude *= degrees - + # measure the size of the image sizex = np.shape(a)[0] sizey = np.shape(a)[1] - + # initialise parameters dx = 0 dy = 0 @@ -96,27 +122,29 @@ def shadowingfunction_wallheight_23(a, vegdem, vegdem2, azimuth, altitude, scale templastfabovea = np.zeros((sizex, sizey)) templastgabovea = np.zeros((sizex, sizey)) bushplant = bush > 1 - sh = np.zeros((sizex, sizey)) #shadows from buildings - vbshvegsh = np.copy(sh) #vegetation blocking buildings - vegsh = np.add(np.zeros((sizex, sizey)), bushplant, dtype=float) #vegetation shadow + sh = np.zeros((sizex, sizey)) # shadows from buildings + vbshvegsh = np.copy(sh) # vegetation blocking buildings + vegsh = np.add( + np.zeros((sizex, sizey)), bushplant, dtype=float + ) # vegetation shadow f = np.copy(a) - shvoveg = np.copy(vegdem) # for vegetation shadowvolume + shvoveg = np.copy(vegdem) # for vegetation shadowvolume # g = np.copy(sh) wallbol = (walls > 0).astype(float) # other loop parameters - pibyfour = np.pi/4 - threetimespibyfour = 3*pibyfour - fivetimespibyfour = 5*pibyfour - seventimespibyfour = 7*pibyfour + pibyfour = np.pi / 4 + threetimespibyfour = 3 * pibyfour + fivetimespibyfour = 5 * pibyfour + seventimespibyfour = 7 * pibyfour sinazimuth = np.sin(azimuth) cosazimuth = np.cos(azimuth) tanazimuth = np.tan(azimuth) signsinazimuth = np.sign(sinazimuth) signcosazimuth = np.sign(cosazimuth) - dssin = np.abs(1/sinazimuth) - dscos = np.abs(1/cosazimuth) - tanaltitudebyscale = np.tan(altitude)/scale + dssin = np.abs(1 / sinazimuth) + dscos = np.abs(1 / cosazimuth) + tanaltitudebyscale = np.tan(altitude) / scale index = 0 @@ -125,7 +153,9 @@ def shadowingfunction_wallheight_23(a, vegdem, vegdem2, azimuth, altitude, scale # main loop while (amaxvalue >= dz) and (np.abs(dx) < sizex) and (np.abs(dy) < sizey): - if ((pibyfour <= azimuth) and (azimuth < threetimespibyfour)) or ((fivetimespibyfour <= azimuth) and (azimuth < seventimespibyfour)): + if ((pibyfour <= azimuth) and (azimuth < threetimespibyfour)) or ( + (fivetimespibyfour <= azimuth) and (azimuth < seventimespibyfour) + ): dy = signsinazimuth * index dx = -1 * signcosazimuth * np.abs(np.round(index / tanazimuth)) ds = dssin @@ -139,64 +169,94 @@ def shadowingfunction_wallheight_23(a, vegdem, vegdem2, azimuth, altitude, scale tempvegdem[0:sizex, 0:sizey] = 0 tempvegdem2[0:sizex, 0:sizey] = 0 temp[0:sizex, 0:sizey] = 0 - templastfabovea[0:sizex, 0:sizey] = 0. - templastgabovea[0:sizex, 0:sizey] = 0. + templastfabovea[0:sizex, 0:sizey] = 0.0 + templastgabovea[0:sizex, 0:sizey] = 0.0 absdx = np.abs(dx) absdy = np.abs(dy) - xc1 = int((dx+absdx)/2) - xc2 = int(sizex+(dx-absdx)/2) - yc1 = int((dy+absdy)/2) - yc2 = int(sizey+(dy-absdy)/2) - xp1 = -int((dx-absdx)/2) - xp2 = int(sizex-(dx+absdx)/2) - yp1 = -int((dy-absdy)/2) - yp2 = int(sizey-(dy+absdy)/2) + xc1 = int((dx + absdx) / 2) + xc2 = int(sizex + (dx - absdx) / 2) + yc1 = int((dy + absdy) / 2) + yc2 = int(sizey + (dy - absdy) / 2) + xp1 = -int((dx - absdx) / 2) + xp2 = int(sizex - (dx + absdx) / 2) + yp1 = -int((dy - absdy) / 2) + yp2 = int(sizey - (dy + absdy) / 2) tempvegdem[xp1:xp2, yp1:yp2] = vegdem[xc1:xc2, yc1:yc2] - dz tempvegdem2[xp1:xp2, yp1:yp2] = vegdem2[xc1:xc2, yc1:yc2] - dz - temp[xp1:xp2, yp1:yp2] = a[xc1:xc2, yc1:yc2]-dz + temp[xp1:xp2, yp1:yp2] = a[xc1:xc2, yc1:yc2] - dz + + f = np.fmax(f, temp) # Moving building shadow + shvoveg = np.fmax( + shvoveg, tempvegdem + ) # moving vegetation shadow volume - f = np.fmax(f, temp) #Moving building shadow - shvoveg = np.fmax(shvoveg, tempvegdem) # moving vegetation shadow volume - sh[f > a] = 1 - sh[f <= a] = 0 - fabovea = (tempvegdem > a).astype(int) #vegdem above DEM - gabovea = (tempvegdem2 > a).astype(int) #vegdem2 above DEM - - #new pergola condition - templastfabovea[xp1:xp2, yp1:yp2] = vegdem[xc1:xc2, yc1:yc2]-dzprev - templastgabovea[xp1:xp2, yp1:yp2] = vegdem2[xc1:xc2, yc1:yc2]-dzprev + sh[f <= a] = 0 + fabovea = (tempvegdem > a).astype(int) # vegdem above DEM + gabovea = (tempvegdem2 > a).astype(int) # vegdem2 above DEM + + # new pergola condition + templastfabovea[xp1:xp2, yp1:yp2] = vegdem[xc1:xc2, yc1:yc2] - dzprev + templastgabovea[xp1:xp2, yp1:yp2] = vegdem2[xc1:xc2, yc1:yc2] - dzprev lastfabovea = templastfabovea > a lastgabovea = templastgabovea > a dzprev = dz - vegsh2 = np.add(np.add(np.add(fabovea, gabovea, dtype=float),lastfabovea, dtype=float),lastgabovea, dtype=float) - vegsh2[vegsh2 == 4] = 0. + vegsh2 = np.add( + np.add( + np.add(fabovea, gabovea, dtype=float), lastfabovea, dtype=float + ), + lastgabovea, + dtype=float, + ) + vegsh2[vegsh2 == 4] = 0.0 # vegsh2[vegsh2 == 1] = 0. # This one is the ultimate question... - vegsh2[vegsh2 > 0] = 1. + vegsh2[vegsh2 > 0] = 1.0 vegsh = np.fmax(vegsh, vegsh2) - vegsh[vegsh*sh > 0] = 0 - vbshvegsh = np.copy(vegsh) + vbshvegsh # removing shadows 'behind' buildings - + vegsh[vegsh * sh > 0] = 0 + vbshvegsh = ( + np.copy(vegsh) + vbshvegsh + ) # removing shadows 'behind' buildings + index += 1 - sh = 1-sh + sh = 1 - sh vbshvegsh[vbshvegsh > 0] = 1 - vbshvegsh = vbshvegsh-vegsh + vbshvegsh = vbshvegsh - vegsh vegsh[vegsh > 0] = 1 - shvoveg = (shvoveg-a) * vegsh #Vegetation shadow volume - vegsh = 1-vegsh - vbshvegsh = 1-vbshvegsh - #print(np.max(shvoveg)) - wallsh, wallsun, wallshve, facesh, facesun = shade_on_walls(azimuth, aspect, walls, a, f, shvoveg) - #print(np.max(wallshve)) + shvoveg = (shvoveg - a) * vegsh # Vegetation shadow volume + vegsh = 1 - vegsh + vbshvegsh = 1 - vbshvegsh + # print(np.max(shvoveg)) + wallsh, wallsun, wallshve, facesh, facesun = shade_on_walls( + azimuth, aspect, walls, a, f, shvoveg + ) + # print(np.max(wallshve)) if walls_scheme is not False: - wallsh_, wallsun_, wallshve_, facesh_, facesun_ = shade_on_walls(azimuth, aspect_scheme, walls_scheme, a, f, shvoveg) - #print(np.max(wallshve_)) + wallsh_, wallsun_, wallshve_, facesh_, facesun_ = shade_on_walls( + azimuth, aspect_scheme, walls_scheme, a, f, shvoveg + ) + # print(np.max(wallshve_)) shade_on_wall = wallsh_.copy() - shade_on_wall[shade_on_wall < wallshve_] = wallshve_[shade_on_wall < wallshve_] + shade_on_wall[shade_on_wall < wallshve_] = wallshve_[ + shade_on_wall < wallshve_ + ] - #return vegsh, sh, vbshvegsh, wallsh, wallsun, wallshve, facesh, facesun, shade_on_wall - return (vegsh, sh, vbshvegsh, wallsh, wallsun, wallshve, facesh, facesun, shade_on_wall) if walls_scheme is not False else (vegsh, sh, vbshvegsh, wallsh, wallsun, wallshve, facesh, facesun) \ No newline at end of file + # return vegsh, sh, vbshvegsh, wallsh, wallsun, wallshve, facesh, facesun, shade_on_wall + return ( + ( + vegsh, + sh, + vbshvegsh, + wallsh, + wallsun, + wallshve, + facesh, + facesun, + shade_on_wall, + ) + if walls_scheme is not False + else (vegsh, sh, vbshvegsh, wallsh, wallsun, wallshve, facesh, facesun) + ) diff --git a/util/SEBESOLWEIGCommonFiles/sun_distance.py b/util/SEBESOLWEIGCommonFiles/sun_distance.py index b6e0673..a46a91d 100644 --- a/util/SEBESOLWEIGCommonFiles/sun_distance.py +++ b/util/SEBESOLWEIGCommonFiles/sun_distance.py @@ -1,4 +1,4 @@ -__author__ = 'xlinfr' +__author__ = "xlinfr" import numpy as np @@ -9,7 +9,14 @@ def sun_distance(jday): #% with day of year as input. #% Partridge and Platt, 1975 """ - b = 2.*np.pi*jday/365. - D = np.sqrt((1.00011+np.dot(0.034221, np.cos(b))+np.dot(0.001280, np.sin(b))+np.dot(0.000719, - np.cos((2.*b)))+np.dot(0.000077, np.sin((2.*b))))) + b = 2.0 * np.pi * jday / 365.0 + D = np.sqrt( + ( + 1.00011 + + np.dot(0.034221, np.cos(b)) + + np.dot(0.001280, np.sin(b)) + + np.dot(0.000719, np.cos((2.0 * b))) + + np.dot(0.000077, np.sin((2.0 * b))) + ) + ) return D diff --git a/util/SEBESOLWEIGCommonFiles/sun_position.py b/util/SEBESOLWEIGCommonFiles/sun_position.py index dfc00d0..4bd8075 100644 --- a/util/SEBESOLWEIGCommonFiles/sun_position.py +++ b/util/SEBESOLWEIGCommonFiles/sun_position.py @@ -106,14 +106,18 @@ def sun_position(time, location): # 1. Calculate the Julian Day, and Century. Julian Ephemeris day, century # and millenium are calculated using a mean delta_t of 33.184 seconds. julian = julian_calculation(time) - #print(julian) + # print(julian) # 2. Calculate the Earth heliocentric longitude, latitude, and radius # vector (L, B, and R) - earth_heliocentric_position = earth_heliocentric_position_calculation(julian) + earth_heliocentric_position = earth_heliocentric_position_calculation( + julian + ) # 3. Calculate the geocentric longitude and latitude - sun_geocentric_position = sun_geocentric_position_calculation(earth_heliocentric_position) + sun_geocentric_position = sun_geocentric_position_calculation( + earth_heliocentric_position + ) # 4. Calculate the nutation in longitude and obliquity (in degrees). nutation = nutation_calculation(julian) @@ -122,36 +126,55 @@ def sun_position(time, location): true_obliquity = true_obliquity_calculation(julian, nutation) # 6. Calculate the aberration correction (in degrees) - aberration_correction = abberation_correction_calculation(earth_heliocentric_position) + aberration_correction = abberation_correction_calculation( + earth_heliocentric_position + ) # 7. Calculate the apparent sun longitude in degrees) - apparent_sun_longitude = apparent_sun_longitude_calculation(sun_geocentric_position, nutation, aberration_correction) + apparent_sun_longitude = apparent_sun_longitude_calculation( + sun_geocentric_position, nutation, aberration_correction + ) # 8. Calculate the apparent sideral time at Greenwich (in degrees) - apparent_stime_at_greenwich = apparent_stime_at_greenwich_calculation(julian, nutation, true_obliquity) + apparent_stime_at_greenwich = apparent_stime_at_greenwich_calculation( + julian, nutation, true_obliquity + ) # 9. Calculate the sun rigth ascension (in degrees) - sun_rigth_ascension = sun_rigth_ascension_calculation(apparent_sun_longitude, true_obliquity, sun_geocentric_position) + sun_rigth_ascension = sun_rigth_ascension_calculation( + apparent_sun_longitude, true_obliquity, sun_geocentric_position + ) # 10. Calculate the geocentric sun declination (in degrees). Positive or # negative if the sun is north or south of the celestial equator. - sun_geocentric_declination = sun_geocentric_declination_calculation(apparent_sun_longitude, true_obliquity, - sun_geocentric_position) + sun_geocentric_declination = sun_geocentric_declination_calculation( + apparent_sun_longitude, true_obliquity, sun_geocentric_position + ) # 11. Calculate the observer local hour angle (in degrees, westward from south). - observer_local_hour = observer_local_hour_calculation(apparent_stime_at_greenwich, location, sun_rigth_ascension) + observer_local_hour = observer_local_hour_calculation( + apparent_stime_at_greenwich, location, sun_rigth_ascension + ) # 12. Calculate the topocentric sun position (rigth ascension, declination and # rigth ascension parallax in degrees) - topocentric_sun_position = topocentric_sun_position_calculate(earth_heliocentric_position, location, - observer_local_hour, sun_rigth_ascension, - sun_geocentric_declination) + topocentric_sun_position = topocentric_sun_position_calculate( + earth_heliocentric_position, + location, + observer_local_hour, + sun_rigth_ascension, + sun_geocentric_declination, + ) # 13. Calculate the topocentric local hour angle (in degrees) - topocentric_local_hour = topocentric_local_hour_calculate(observer_local_hour, topocentric_sun_position) + topocentric_local_hour = topocentric_local_hour_calculate( + observer_local_hour, topocentric_sun_position + ) # 14. Calculate the topocentric zenith and azimuth angle (in degrees) - sun = sun_topocentric_zenith_angle_calculate(location, topocentric_sun_position, topocentric_local_hour) + sun = sun_topocentric_zenith_angle_calculate( + location, topocentric_sun_position, topocentric_local_hour + ) return sun @@ -170,58 +193,76 @@ def julian_calculation(t_input): # tt = datetime.datetime.strptime(t_input, "%Y-%m-%d %H:%M:%S.%f") # if t_input is a string of this format # t_input should be a datetime object time = dict() - time['UTC'] = 0 - time['year'] = t_input.year - time['month'] = t_input.month - time['day'] = t_input.day - time['hour'] = t_input.hour - time['min'] = t_input.minute - time['sec'] = t_input.second + time["UTC"] = 0 + time["year"] = t_input.year + time["month"] = t_input.month + time["day"] = t_input.day + time["hour"] = t_input.hour + time["min"] = t_input.minute + time["sec"] = t_input.second else: time = t_input - if time['month'] == 1 or time['month'] == 2: - Y = time['year'] - 1 - M = time['month'] + 12 + if time["month"] == 1 or time["month"] == 2: + Y = time["year"] - 1 + M = time["month"] + 12 else: - Y = time['year'] - M = time['month'] - - ut_time = ((time['hour'] - time['UTC'])/24) + (time['min']/(60*24)) + (time['sec']/(60*60*24)) # time of day in UT time. - D = time['day'] + ut_time # Day of month in decimal time, ex. 2sd day of month at 12:30:30UT, D=2.521180556 + Y = time["year"] + M = time["month"] + + ut_time = ( + ((time["hour"] - time["UTC"]) / 24) + + (time["min"] / (60 * 24)) + + (time["sec"] / (60 * 60 * 24)) + ) # time of day in UT time. + D = ( + time["day"] + ut_time + ) # Day of month in decimal time, ex. 2sd day of month at 12:30:30UT, D=2.521180556 # In 1582, the gregorian calendar was adopted - if time['year'] == 1582: - if time['month'] == 10: - if time['day'] <= 4: # The Julian calendar ended on October 4, 1582 - B = (0) - elif time['day'] >= 15: # The Gregorian calendar started on October 15, 1582 - A = np.floor(Y/100) - B = 2 - A + np.floor(A/4) + if time["year"] == 1582: + if time["month"] == 10: + if ( + time["day"] <= 4 + ): # The Julian calendar ended on October 4, 1582 + B = 0 + elif ( + time["day"] >= 15 + ): # The Gregorian calendar started on October 15, 1582 + A = np.floor(Y / 100) + B = 2 - A + np.floor(A / 4) else: - print('This date never existed!. Date automatically set to October 4, 1582') - time['month'] = 10 - time['day'] = 4 + print( + "This date never existed!. Date automatically set to October 4, 1582" + ) + time["month"] = 10 + time["day"] = 4 B = 0 - elif time['month'] < 10: # Julian calendar + elif time["month"] < 10: # Julian calendar B = 0 - else: # Gregorian calendar - A = np.floor(Y/100) - B = 2 - A + np.floor(A/4) - elif time['year'] < 1582: # Julian calendar + else: # Gregorian calendar + A = np.floor(Y / 100) + B = 2 - A + np.floor(A / 4) + elif time["year"] < 1582: # Julian calendar B = 0 else: - A = np.floor(Y/100) # Gregorian calendar - B = 2 - A + np.floor(A/4) + A = np.floor(Y / 100) # Gregorian calendar + B = 2 - A + np.floor(A / 4) julian = dict() - julian['day'] = D + B + np.floor(365.25*(Y+4716)) + np.floor(30.6001*(M+1)) - 1524.5 - - delta_t = 0 # 33.184; - julian['ephemeris_day'] = (julian['day']) + (delta_t/86400) - julian['century'] = (julian['day'] - 2451545) / 36525 - julian['ephemeris_century'] = (julian['ephemeris_day'] - 2451545) / 36525 - julian['ephemeris_millenium'] = (julian['ephemeris_century']) / 10 + julian["day"] = ( + D + + B + + np.floor(365.25 * (Y + 4716)) + + np.floor(30.6001 * (M + 1)) + - 1524.5 + ) + + delta_t = 0 # 33.184; + julian["ephemeris_day"] = (julian["day"]) + (delta_t / 86400) + julian["century"] = (julian["day"] - 2451545) / 36525 + julian["ephemeris_century"] = (julian["ephemeris_day"] - 2451545) / 36525 + julian["ephemeris_millenium"] = (julian["ephemeris_century"]) / 10 return julian @@ -229,147 +270,165 @@ def julian_calculation(t_input): def earth_heliocentric_position_calculation(julian): """ % This function compute the earth position relative to the sun, using - % tabulated values. - + % tabulated values. + % Tabulated values for the longitude calculation % L terms from the original code. """ # Tabulated values for the longitude calculation - # L terms from the original code. - L0_terms = np.array([[175347046.0, 0, 0], - [3341656.0, 4.6692568, 6283.07585], - [34894.0, 4.6261, 12566.1517], - [3497.0, 2.7441, 5753.3849], - [3418.0, 2.8289, 3.5231], - [3136.0, 3.6277, 77713.7715], - [2676.0, 4.4181, 7860.4194], - [2343.0, 6.1352, 3930.2097], - [1324.0, 0.7425, 11506.7698], - [1273.0, 2.0371, 529.691], - [1199.0, 1.1096, 1577.3435], - [990, 5.233, 5884.927], - [902, 2.045, 26.298], - [857, 3.508, 398.149], - [780, 1.179, 5223.694], - [753, 2.533, 5507.553], - [505, 4.583, 18849.228], - [492, 4.205, 775.523], - [357, 2.92, 0.067], - [317, 5.849, 11790.629], - [284, 1.899, 796.298], - [271, 0.315, 10977.079], - [243, 0.345, 5486.778], - [206, 4.806, 2544.314], - [205, 1.869, 5573.143], - [202, 2.4458, 6069.777], - [156, 0.833, 213.299], - [132, 3.411, 2942.463], - [126, 1.083, 20.775], - [115, 0.645, 0.98], - [103, 0.636, 4694.003], - [102, 0.976, 15720.839], - [102, 4.267, 7.114], - [99, 6.21, 2146.17], - [98, 0.68, 155.42], - [86, 5.98, 161000.69], - [85, 1.3, 6275.96], - [85, 3.67, 71430.7], - [80, 1.81, 17260.15], - [79, 3.04, 12036.46], - [71, 1.76, 5088.63], - [74, 3.5, 3154.69], - [74, 4.68, 801.82], - [70, 0.83, 9437.76], - [62, 3.98, 8827.39], - [61, 1.82, 7084.9], - [57, 2.78, 6286.6], - [56, 4.39, 14143.5], - [56, 3.47, 6279.55], - [52, 0.19, 12139.55], - [52, 1.33, 1748.02], - [51, 0.28, 5856.48], - [49, 0.49, 1194.45], - [41, 5.37, 8429.24], - [41, 2.4, 19651.05], - [39, 6.17, 10447.39], - [37, 6.04, 10213.29], - [37, 2.57, 1059.38], - [36, 1.71, 2352.87], - [36, 1.78, 6812.77], - [33, 0.59, 17789.85], - [30, 0.44, 83996.85], - [30, 2.74, 1349.87], - [25, 3.16, 4690.48]]) - - L1_terms = np.array([[628331966747.0, 0, 0], - [206059.0, 2.678235, 6283.07585], - [4303.0, 2.6351, 12566.1517], - [425.0, 1.59, 3.523], - [119.0, 5.796, 26.298], - [109.0, 2.966, 1577.344], - [93, 2.59, 18849.23], - [72, 1.14, 529.69], - [68, 1.87, 398.15], - [67, 4.41, 5507.55], - [59, 2.89, 5223.69], - [56, 2.17, 155.42], - [45, 0.4, 796.3], - [36, 0.47, 775.52], - [29, 2.65, 7.11], - [21, 5.34, 0.98], - [19, 1.85, 5486.78], - [19, 4.97, 213.3], - [17, 2.99, 6275.96], - [16, 0.03, 2544.31], - [16, 1.43, 2146.17], - [15, 1.21, 10977.08], - [12, 2.83, 1748.02], - [12, 3.26, 5088.63], - [12, 5.27, 1194.45], - [12, 2.08, 4694], - [11, 0.77, 553.57], - [10, 1.3, 3286.6], - [10, 4.24, 1349.87], - [9, 2.7, 242.73], - [9, 5.64, 951.72], - [8, 5.3, 2352.87], - [6, 2.65, 9437.76], - [6, 4.67, 4690.48]]) - - L2_terms = np.array([[52919.0, 0, 0], - [8720.0, 1.0721, 6283.0758], - [309.0, 0.867, 12566.152], - [27, 0.05, 3.52], - [16, 5.19, 26.3], - [16, 3.68, 155.42], - [10, 0.76, 18849.23], - [9, 2.06, 77713.77], - [7, 0.83, 775.52], - [5, 4.66, 1577.34], - [4, 1.03, 7.11], - [4, 3.44, 5573.14], - [3, 5.14, 796.3], - [3, 6.05, 5507.55], - [3, 1.19, 242.73], - [3, 6.12, 529.69], - [3, 0.31, 398.15], - [3, 2.28, 553.57], - [2, 4.38, 5223.69], - [2, 3.75, 0.98]]) - - L3_terms = np.array([[289.0, 5.844, 6283.076], - [35, 0, 0], - [17, 5.49, 12566.15], - [3, 5.2, 155.42], - [1, 4.72, 3.52], - [1, 5.3, 18849.23], - [1, 5.97, 242.73]]) - L4_terms = np.array([[114.0, 3.142, 0], - [8, 4.13, 6283.08], - [1, 3.84, 12566.15]]) + # L terms from the original code. + L0_terms = np.array( + [ + [175347046.0, 0, 0], + [3341656.0, 4.6692568, 6283.07585], + [34894.0, 4.6261, 12566.1517], + [3497.0, 2.7441, 5753.3849], + [3418.0, 2.8289, 3.5231], + [3136.0, 3.6277, 77713.7715], + [2676.0, 4.4181, 7860.4194], + [2343.0, 6.1352, 3930.2097], + [1324.0, 0.7425, 11506.7698], + [1273.0, 2.0371, 529.691], + [1199.0, 1.1096, 1577.3435], + [990, 5.233, 5884.927], + [902, 2.045, 26.298], + [857, 3.508, 398.149], + [780, 1.179, 5223.694], + [753, 2.533, 5507.553], + [505, 4.583, 18849.228], + [492, 4.205, 775.523], + [357, 2.92, 0.067], + [317, 5.849, 11790.629], + [284, 1.899, 796.298], + [271, 0.315, 10977.079], + [243, 0.345, 5486.778], + [206, 4.806, 2544.314], + [205, 1.869, 5573.143], + [202, 2.4458, 6069.777], + [156, 0.833, 213.299], + [132, 3.411, 2942.463], + [126, 1.083, 20.775], + [115, 0.645, 0.98], + [103, 0.636, 4694.003], + [102, 0.976, 15720.839], + [102, 4.267, 7.114], + [99, 6.21, 2146.17], + [98, 0.68, 155.42], + [86, 5.98, 161000.69], + [85, 1.3, 6275.96], + [85, 3.67, 71430.7], + [80, 1.81, 17260.15], + [79, 3.04, 12036.46], + [71, 1.76, 5088.63], + [74, 3.5, 3154.69], + [74, 4.68, 801.82], + [70, 0.83, 9437.76], + [62, 3.98, 8827.39], + [61, 1.82, 7084.9], + [57, 2.78, 6286.6], + [56, 4.39, 14143.5], + [56, 3.47, 6279.55], + [52, 0.19, 12139.55], + [52, 1.33, 1748.02], + [51, 0.28, 5856.48], + [49, 0.49, 1194.45], + [41, 5.37, 8429.24], + [41, 2.4, 19651.05], + [39, 6.17, 10447.39], + [37, 6.04, 10213.29], + [37, 2.57, 1059.38], + [36, 1.71, 2352.87], + [36, 1.78, 6812.77], + [33, 0.59, 17789.85], + [30, 0.44, 83996.85], + [30, 2.74, 1349.87], + [25, 3.16, 4690.48], + ] + ) + + L1_terms = np.array( + [ + [628331966747.0, 0, 0], + [206059.0, 2.678235, 6283.07585], + [4303.0, 2.6351, 12566.1517], + [425.0, 1.59, 3.523], + [119.0, 5.796, 26.298], + [109.0, 2.966, 1577.344], + [93, 2.59, 18849.23], + [72, 1.14, 529.69], + [68, 1.87, 398.15], + [67, 4.41, 5507.55], + [59, 2.89, 5223.69], + [56, 2.17, 155.42], + [45, 0.4, 796.3], + [36, 0.47, 775.52], + [29, 2.65, 7.11], + [21, 5.34, 0.98], + [19, 1.85, 5486.78], + [19, 4.97, 213.3], + [17, 2.99, 6275.96], + [16, 0.03, 2544.31], + [16, 1.43, 2146.17], + [15, 1.21, 10977.08], + [12, 2.83, 1748.02], + [12, 3.26, 5088.63], + [12, 5.27, 1194.45], + [12, 2.08, 4694], + [11, 0.77, 553.57], + [10, 1.3, 3286.6], + [10, 4.24, 1349.87], + [9, 2.7, 242.73], + [9, 5.64, 951.72], + [8, 5.3, 2352.87], + [6, 2.65, 9437.76], + [6, 4.67, 4690.48], + ] + ) + + L2_terms = np.array( + [ + [52919.0, 0, 0], + [8720.0, 1.0721, 6283.0758], + [309.0, 0.867, 12566.152], + [27, 0.05, 3.52], + [16, 5.19, 26.3], + [16, 3.68, 155.42], + [10, 0.76, 18849.23], + [9, 2.06, 77713.77], + [7, 0.83, 775.52], + [5, 4.66, 1577.34], + [4, 1.03, 7.11], + [4, 3.44, 5573.14], + [3, 5.14, 796.3], + [3, 6.05, 5507.55], + [3, 1.19, 242.73], + [3, 6.12, 529.69], + [3, 0.31, 398.15], + [3, 2.28, 553.57], + [2, 4.38, 5223.69], + [2, 3.75, 0.98], + ] + ) + + L3_terms = np.array( + [ + [289.0, 5.844, 6283.076], + [35, 0, 0], + [17, 5.49, 12566.15], + [3, 5.2, 155.42], + [1, 4.72, 3.52], + [1, 5.3, 18849.23], + [1, 5.97, 242.73], + ] + ) + L4_terms = np.array( + [[114.0, 3.142, 0], [8, 4.13, 6283.08], [1, 3.84, 12566.15]] + ) L5_terms = np.array([1, 3.14, 0]) - L5_terms = np.atleast_2d(L5_terms) # since L5_terms is 1D, we have to convert it to 2D to avoid indexErrors + L5_terms = np.atleast_2d( + L5_terms + ) # since L5_terms is 1D, we have to convert it to 2D to avoid indexErrors A0 = L0_terms[:, 0] B0 = L0_terms[:, 1] @@ -395,7 +454,7 @@ def earth_heliocentric_position_calculation(julian): B5 = L5_terms[:, 1] C5 = L5_terms[:, 2] - JME = julian['ephemeris_millenium'] + JME = julian["ephemeris_millenium"] # Compute the Earth Heliochentric longitude from the tabulated values. L0 = np.sum(A0 * np.cos(B0 + (C0 * JME))) @@ -406,129 +465,157 @@ def earth_heliocentric_position_calculation(julian): L5 = A5 * np.cos(B5 + (C5 * JME)) earth_heliocentric_position = dict() - earth_heliocentric_position['longitude'] = (L0 + (L1 * JME) + (L2 * np.power(JME, 2)) + - (L3 * np.power(JME, 3)) + - (L4 * np.power(JME, 4)) + - (L5 * np.power(JME, 5))) / 1e8 + earth_heliocentric_position["longitude"] = ( + L0 + + (L1 * JME) + + (L2 * np.power(JME, 2)) + + (L3 * np.power(JME, 3)) + + (L4 * np.power(JME, 4)) + + (L5 * np.power(JME, 5)) + ) / 1e8 # Convert the longitude to degrees. - earth_heliocentric_position['longitude'] = earth_heliocentric_position['longitude'] * 180/np.pi + earth_heliocentric_position["longitude"] = ( + earth_heliocentric_position["longitude"] * 180 / np.pi + ) # Limit the range to [0,360] - earth_heliocentric_position['longitude'] = set_to_range(earth_heliocentric_position['longitude'], 0, 360) - - # Tabulated values for the earth heliocentric latitude. - # B terms from the original code. - B0_terms = np.array([[280.0, 3.199, 84334.662], - [102.0, 5.422, 5507.553], - [80, 3.88, 5223.69], - [44, 3.7, 2352.87], - [32, 4, 1577.34]]) - - B1_terms = np.array([[9, 3.9, 5507.55], - [6, 1.73, 5223.69]]) + earth_heliocentric_position["longitude"] = set_to_range( + earth_heliocentric_position["longitude"], 0, 360 + ) + + # Tabulated values for the earth heliocentric latitude. + # B terms from the original code. + B0_terms = np.array( + [ + [280.0, 3.199, 84334.662], + [102.0, 5.422, 5507.553], + [80, 3.88, 5223.69], + [44, 3.7, 2352.87], + [32, 4, 1577.34], + ] + ) + + B1_terms = np.array([[9, 3.9, 5507.55], [6, 1.73, 5223.69]]) A0 = B0_terms[:, 0] B0 = B0_terms[:, 1] C0 = B0_terms[:, 2] - + A1 = B1_terms[:, 0] B1 = B1_terms[:, 1] C1 = B1_terms[:, 2] - + L0 = np.sum(A0 * np.cos(B0 + (C0 * JME))) L1 = np.sum(A1 * np.cos(B1 + (C1 * JME))) - earth_heliocentric_position['latitude'] = (L0 + (L1 * JME)) / 1e8 + earth_heliocentric_position["latitude"] = (L0 + (L1 * JME)) / 1e8 - # Convert the latitude to degrees. - earth_heliocentric_position['latitude'] = earth_heliocentric_position['latitude'] * 180/np.pi + # Convert the latitude to degrees. + earth_heliocentric_position["latitude"] = ( + earth_heliocentric_position["latitude"] * 180 / np.pi + ) # Limit the range to [0,360]; - earth_heliocentric_position['latitude'] = set_to_range(earth_heliocentric_position['latitude'], 0, 360) + earth_heliocentric_position["latitude"] = set_to_range( + earth_heliocentric_position["latitude"], 0, 360 + ) - # Tabulated values for radius vector. + # Tabulated values for radius vector. # R terms from the original code - R0_terms = np.array([[100013989.0, 0, 0], - [1670700.0, 3.0984635, 6283.07585], - [13956.0, 3.05525, 12566.1517], - [3084.0, 5.1985, 77713.7715], - [1628.0, 1.1739, 5753.3849], - [1576.0, 2.8469, 7860.4194], - [925.0, 5.453, 11506.77], - [542.0, 4.564, 3930.21], - [472.0, 3.661, 5884.927], - [346.0, 0.964, 5507.553], - [329.0, 5.9, 5223.694], - [307.0, 0.299, 5573.143], - [243.0, 4.273, 11790.629], - [212.0, 5.847, 1577.344], - [186.0, 5.022, 10977.079], - [175.0, 3.012, 18849.228], - [110.0, 5.055, 5486.778], - [98, 0.89, 6069.78], - [86, 5.69, 15720.84], - [86, 1.27, 161000.69], - [85, 0.27, 17260.15], - [63, 0.92, 529.69], - [57, 2.01, 83996.85], - [56, 5.24, 71430.7], - [49, 3.25, 2544.31], - [47, 2.58, 775.52], - [45, 5.54, 9437.76], - [43, 6.01, 6275.96], - [39, 5.36, 4694], - [38, 2.39, 8827.39], - [37, 0.83, 19651.05], - [37, 4.9, 12139.55], - [36, 1.67, 12036.46], - [35, 1.84, 2942.46], - [33, 0.24, 7084.9], - [32, 0.18, 5088.63], - [32, 1.78, 398.15], - [28, 1.21, 6286.6], - [28, 1.9, 6279.55], - [26, 4.59, 10447.39]]) - - R1_terms = np.array([[103019.0, 1.10749, 6283.07585], - [1721.0, 1.0644, 12566.1517], - [702.0, 3.142, 0], - [32, 1.02, 18849.23], - [31, 2.84, 5507.55], - [25, 1.32, 5223.69], - [18, 1.42, 1577.34], - [10, 5.91, 10977.08], - [9, 1.42, 6275.96], - [9, 0.27, 5486.78]]) - - R2_terms = np.array([[4359.0, 5.7846, 6283.0758], - [124.0, 5.579, 12566.152], - [12, 3.14, 0], - [9, 3.63, 77713.77], - [6, 1.87, 5573.14], - [3, 5.47, 18849]]) - - R3_terms = np.array([[145.0, 4.273, 6283.076], - [7, 3.92, 12566.15]]) - + R0_terms = np.array( + [ + [100013989.0, 0, 0], + [1670700.0, 3.0984635, 6283.07585], + [13956.0, 3.05525, 12566.1517], + [3084.0, 5.1985, 77713.7715], + [1628.0, 1.1739, 5753.3849], + [1576.0, 2.8469, 7860.4194], + [925.0, 5.453, 11506.77], + [542.0, 4.564, 3930.21], + [472.0, 3.661, 5884.927], + [346.0, 0.964, 5507.553], + [329.0, 5.9, 5223.694], + [307.0, 0.299, 5573.143], + [243.0, 4.273, 11790.629], + [212.0, 5.847, 1577.344], + [186.0, 5.022, 10977.079], + [175.0, 3.012, 18849.228], + [110.0, 5.055, 5486.778], + [98, 0.89, 6069.78], + [86, 5.69, 15720.84], + [86, 1.27, 161000.69], + [85, 0.27, 17260.15], + [63, 0.92, 529.69], + [57, 2.01, 83996.85], + [56, 5.24, 71430.7], + [49, 3.25, 2544.31], + [47, 2.58, 775.52], + [45, 5.54, 9437.76], + [43, 6.01, 6275.96], + [39, 5.36, 4694], + [38, 2.39, 8827.39], + [37, 0.83, 19651.05], + [37, 4.9, 12139.55], + [36, 1.67, 12036.46], + [35, 1.84, 2942.46], + [33, 0.24, 7084.9], + [32, 0.18, 5088.63], + [32, 1.78, 398.15], + [28, 1.21, 6286.6], + [28, 1.9, 6279.55], + [26, 4.59, 10447.39], + ] + ) + + R1_terms = np.array( + [ + [103019.0, 1.10749, 6283.07585], + [1721.0, 1.0644, 12566.1517], + [702.0, 3.142, 0], + [32, 1.02, 18849.23], + [31, 2.84, 5507.55], + [25, 1.32, 5223.69], + [18, 1.42, 1577.34], + [10, 5.91, 10977.08], + [9, 1.42, 6275.96], + [9, 0.27, 5486.78], + ] + ) + + R2_terms = np.array( + [ + [4359.0, 5.7846, 6283.0758], + [124.0, 5.579, 12566.152], + [12, 3.14, 0], + [9, 3.63, 77713.77], + [6, 1.87, 5573.14], + [3, 5.47, 18849], + ] + ) + + R3_terms = np.array([[145.0, 4.273, 6283.076], [7, 3.92, 12566.15]]) + R4_terms = [4, 2.56, 6283.08] - R4_terms = np.atleast_2d(R4_terms) # since L5_terms is 1D, we have to convert it to 2D to avoid indexErrors + R4_terms = np.atleast_2d( + R4_terms + ) # since L5_terms is 1D, we have to convert it to 2D to avoid indexErrors A0 = R0_terms[:, 0] B0 = R0_terms[:, 1] C0 = R0_terms[:, 2] - + A1 = R1_terms[:, 0] B1 = R1_terms[:, 1] C1 = R1_terms[:, 2] - + A2 = R2_terms[:, 0] B2 = R2_terms[:, 1] C2 = R2_terms[:, 2] - + A3 = R3_terms[:, 0] B3 = R3_terms[:, 1] C3 = R3_terms[:, 2] - + A4 = R4_terms[:, 0] B4 = R4_terms[:, 1] C4 = R4_terms[:, 2] @@ -541,9 +628,13 @@ def earth_heliocentric_position_calculation(julian): L4 = A4 * np.cos(B4 + (C4 * JME)) # Units are in AU - earth_heliocentric_position['radius'] = (L0 + (L1 * JME) + (L2 * np.power(JME, 2)) + - (L3 * np.power(JME, 3)) + - (L4 * np.power(JME, 4))) / 1e8 + earth_heliocentric_position["radius"] = ( + L0 + + (L1 * JME) + + (L2 * np.power(JME, 2)) + + (L3 * np.power(JME, 3)) + + (L4 * np.power(JME, 4)) + ) / 1e8 return earth_heliocentric_position @@ -553,13 +644,21 @@ def sun_geocentric_position_calculation(earth_heliocentric_position): % This function compute the sun position relative to the earth. """ sun_geocentric_position = dict() - sun_geocentric_position['longitude'] = earth_heliocentric_position['longitude'] + 180 + sun_geocentric_position["longitude"] = ( + earth_heliocentric_position["longitude"] + 180 + ) # Limit the range to [0,360]; - sun_geocentric_position['longitude'] = set_to_range(sun_geocentric_position['longitude'], 0, 360) + sun_geocentric_position["longitude"] = set_to_range( + sun_geocentric_position["longitude"], 0, 360 + ) - sun_geocentric_position['latitude'] = -earth_heliocentric_position['latitude'] + sun_geocentric_position["latitude"] = -earth_heliocentric_position[ + "latitude" + ] # Limit the range to [0,360] - sun_geocentric_position['latitude'] = set_to_range(sun_geocentric_position['latitude'], 0, 360) + sun_geocentric_position["latitude"] = set_to_range( + sun_geocentric_position["latitude"], 0, 360 + ) return sun_geocentric_position @@ -572,183 +671,220 @@ def nutation_calculation(julian): """ # All Xi are in degrees. - JCE = julian['ephemeris_century'] + JCE = julian["ephemeris_century"] # 1. Mean elongation of the moon from the sun - p = np.atleast_2d([(1/189474), -0.0019142, 445267.11148, 297.85036]) + p = np.atleast_2d([(1 / 189474), -0.0019142, 445267.11148, 297.85036]) # X0 = polyval(p, JCE); - X0 = p[0, 0] * np.power(JCE, 3) + p[0, 1] * np.power(JCE, 2) + p[0, 2] * JCE + p[0, 3] # This is faster than polyval... + X0 = ( + p[0, 0] * np.power(JCE, 3) + + p[0, 1] * np.power(JCE, 2) + + p[0, 2] * JCE + + p[0, 3] + ) # This is faster than polyval... # 2. Mean anomaly of the sun (earth) - p = np.atleast_2d([-(1/300000), -0.0001603, 35999.05034, 357.52772]) + p = np.atleast_2d([-(1 / 300000), -0.0001603, 35999.05034, 357.52772]) # X1 = polyval(p, JCE) - X1 = p[0, 0] * np.power(JCE, 3) + p[0, 1] * np.power(JCE, 2) + p[0, 2] * JCE + p[0, 3] + X1 = ( + p[0, 0] * np.power(JCE, 3) + + p[0, 1] * np.power(JCE, 2) + + p[0, 2] * JCE + + p[0, 3] + ) # 3. Mean anomaly of the moon - p = np.atleast_2d([(1/56250), 0.0086972, 477198.867398, 134.96298]) - + p = np.atleast_2d([(1 / 56250), 0.0086972, 477198.867398, 134.96298]) + # X2 = polyval(p, JCE); - X2 = p[0, 0] * np.power(JCE, 3) + p[0, 1] * np.power(JCE, 2) + p[0, 2] * JCE + p[0, 3] + X2 = ( + p[0, 0] * np.power(JCE, 3) + + p[0, 1] * np.power(JCE, 2) + + p[0, 2] * JCE + + p[0, 3] + ) # 4. Moon argument of latitude - p = np.atleast_2d([(1/327270), -0.0036825, 483202.017538, 93.27191]) + p = np.atleast_2d([(1 / 327270), -0.0036825, 483202.017538, 93.27191]) # X3 = polyval(p, JCE) - X3 = p[0, 0] * np.power(JCE, 3) + p[0, 1] * np.power(JCE, 2) + p[0, 2] * JCE + p[0, 3] + X3 = ( + p[0, 0] * np.power(JCE, 3) + + p[0, 1] * np.power(JCE, 2) + + p[0, 2] * JCE + + p[0, 3] + ) # 5. Longitude of the ascending node of the moon's mean orbit on the # ecliptic, measured from the mean equinox of the date - p = np.atleast_2d([(1/450000), 0.0020708, -1934.136261, 125.04452]) + p = np.atleast_2d([(1 / 450000), 0.0020708, -1934.136261, 125.04452]) # X4 = polyval(p, JCE); - X4 = p[0, 0] * np.power(JCE, 3) + p[0, 1] * np.power(JCE, 2) + p[0, 2] * JCE + p[0, 3] + X4 = ( + p[0, 0] * np.power(JCE, 3) + + p[0, 1] * np.power(JCE, 2) + + p[0, 2] * JCE + + p[0, 3] + ) # Y tabulated terms from the original code - Y_terms = np.array([[0, 0, 0, 0, 1], - [-2, 0, 0, 2, 2], - [0, 0, 0, 2, 2], - [0, 0, 0, 0, 2], - [0, 1, 0, 0, 0], - [0, 0, 1, 0, 0], - [-2, 1, 0, 2, 2], - [0, 0, 0, 2, 1], - [0, 0, 1, 2, 2], - [-2, -1, 0, 2, 2], - [-2, 0, 1, 0, 0], - [-2, 0, 0, 2, 1], - [0, 0, -1, 2, 2], - [2, 0, 0, 0, 0], - [0, 0, 1, 0, 1], - [2, 0, -1, 2, 2], - [0, 0, -1, 0, 1], - [0, 0, 1, 2, 1], - [-2, 0, 2, 0, 0], - [0, 0, -2, 2, 1], - [2, 0, 0, 2, 2], - [0, 0, 2, 2, 2], - [0, 0, 2, 0, 0], - [-2, 0, 1, 2, 2], - [0, 0, 0, 2, 0], - [-2, 0, 0, 2, 0], - [0, 0, -1, 2, 1], - [0, 2, 0, 0, 0], - [2, 0, -1, 0, 1], - [-2, 2, 0, 2, 2], - [0, 1, 0, 0, 1], - [-2, 0, 1, 0, 1], - [0, -1, 0, 0, 1], - [0, 0, 2, -2, 0], - [2, 0, -1, 2, 1], - [2, 0, 1, 2, 2], - [0, 1, 0, 2, 2], - [-2, 1, 1, 0, 0], - [0, -1, 0, 2, 2], - [2, 0, 0, 2, 1], - [2, 0, 1, 0, 0], - [-2, 0, 2, 2, 2], - [-2, 0, 1, 2, 1], - [2, 0, -2, 0, 1], - [2, 0, 0, 0, 1], - [0, -1, 1, 0, 0], - [-2, -1, 0, 2, 1], - [-2, 0, 0, 0, 1], - [0, 0, 2, 2, 1], - [-2, 0, 2, 0, 1], - [-2, 1, 0, 2, 1], - [0, 0, 1, -2, 0], - [-1, 0, 1, 0, 0], - [-2, 1, 0, 0, 0], - [1, 0, 0, 0, 0], - [0, 0, 1, 2, 0], - [0, 0, -2, 2, 2], - [-1, -1, 1, 0, 0], - [0, 1, 1, 0, 0], - [0, -1, 1, 2, 2], - [2, -1, -1, 2, 2], - [0, 0, 3, 2, 2], - [2, -1, 0, 2, 2]]) - - nutation_terms = np.array([[-171996, -174.2, 92025, 8.9], - [-13187, -1.6, 5736, -3.1], - [-2274, -0.2, 977, -0.5], - [2062, 0.2, -895, 0.5], - [1426, -3.4, 54, -0.1], - [712, 0.1, -7, 0], - [-517, 1.2, 224, -0.6], - [-386, -0.4, 200, 0], - [-301, 0, 129, -0.1], - [217, -0.5, -95, 0.3], - [-158, 0, 0, 0], - [129, 0.1, -70, 0], - [123, 0, -53, 0], - [63, 0, 0, 0], - [63, 0.1, -33, 0], - [-59, 0, 26, 0], - [-58, -0.1, 32, 0], - [-51, 0, 27, 0], - [48, 0, 0, 0], - [46, 0, -24, 0], - [-38, 0, 16, 0], - [-31, 0, 13, 0], - [29, 0, 0, 0], - [29, 0, -12, 0], - [26, 0, 0, 0], - [-22, 0, 0, 0], - [21, 0, -10, 0], - [17, -0.1, 0, 0], - [16, 0, -8, 0], - [-16, 0.1, 7, 0], - [-15, 0, 9, 0], - [-13, 0, 7, 0], - [-12, 0, 6, 0], - [11, 0, 0, 0], - [-10, 0, 5, 0], - [-8, 0, 3, 0], - [7, 0, -3, 0], - [-7, 0, 0, 0], - [-7, 0, 3, 0], - [-7, 0, 3, 0], - [6, 0, 0, 0], - [6, 0, -3, 0], - [6, 0, -3, 0], - [-6, 0, 3, 0], - [-6, 0, 3, 0], - [5, 0, 0, 0], - [-5, 0, 3, 0], - [-5, 0, 3, 0], - [-5, 0, 3, 0], - [4, 0, 0, 0], - [4, 0, 0, 0], - [4, 0, 0, 0], - [-4, 0, 0, 0], - [-4, 0, 0, 0], - [-4, 0, 0, 0], - [3, 0, 0, 0], - [-3, 0, 0, 0], - [-3, 0, 0, 0], - [-3, 0, 0, 0], - [-3, 0, 0, 0], - [-3, 0, 0, 0], - [-3, 0, 0, 0], - [-3, 0, 0, 0]]) + Y_terms = np.array( + [ + [0, 0, 0, 0, 1], + [-2, 0, 0, 2, 2], + [0, 0, 0, 2, 2], + [0, 0, 0, 0, 2], + [0, 1, 0, 0, 0], + [0, 0, 1, 0, 0], + [-2, 1, 0, 2, 2], + [0, 0, 0, 2, 1], + [0, 0, 1, 2, 2], + [-2, -1, 0, 2, 2], + [-2, 0, 1, 0, 0], + [-2, 0, 0, 2, 1], + [0, 0, -1, 2, 2], + [2, 0, 0, 0, 0], + [0, 0, 1, 0, 1], + [2, 0, -1, 2, 2], + [0, 0, -1, 0, 1], + [0, 0, 1, 2, 1], + [-2, 0, 2, 0, 0], + [0, 0, -2, 2, 1], + [2, 0, 0, 2, 2], + [0, 0, 2, 2, 2], + [0, 0, 2, 0, 0], + [-2, 0, 1, 2, 2], + [0, 0, 0, 2, 0], + [-2, 0, 0, 2, 0], + [0, 0, -1, 2, 1], + [0, 2, 0, 0, 0], + [2, 0, -1, 0, 1], + [-2, 2, 0, 2, 2], + [0, 1, 0, 0, 1], + [-2, 0, 1, 0, 1], + [0, -1, 0, 0, 1], + [0, 0, 2, -2, 0], + [2, 0, -1, 2, 1], + [2, 0, 1, 2, 2], + [0, 1, 0, 2, 2], + [-2, 1, 1, 0, 0], + [0, -1, 0, 2, 2], + [2, 0, 0, 2, 1], + [2, 0, 1, 0, 0], + [-2, 0, 2, 2, 2], + [-2, 0, 1, 2, 1], + [2, 0, -2, 0, 1], + [2, 0, 0, 0, 1], + [0, -1, 1, 0, 0], + [-2, -1, 0, 2, 1], + [-2, 0, 0, 0, 1], + [0, 0, 2, 2, 1], + [-2, 0, 2, 0, 1], + [-2, 1, 0, 2, 1], + [0, 0, 1, -2, 0], + [-1, 0, 1, 0, 0], + [-2, 1, 0, 0, 0], + [1, 0, 0, 0, 0], + [0, 0, 1, 2, 0], + [0, 0, -2, 2, 2], + [-1, -1, 1, 0, 0], + [0, 1, 1, 0, 0], + [0, -1, 1, 2, 2], + [2, -1, -1, 2, 2], + [0, 0, 3, 2, 2], + [2, -1, 0, 2, 2], + ] + ) + + nutation_terms = np.array( + [ + [-171996, -174.2, 92025, 8.9], + [-13187, -1.6, 5736, -3.1], + [-2274, -0.2, 977, -0.5], + [2062, 0.2, -895, 0.5], + [1426, -3.4, 54, -0.1], + [712, 0.1, -7, 0], + [-517, 1.2, 224, -0.6], + [-386, -0.4, 200, 0], + [-301, 0, 129, -0.1], + [217, -0.5, -95, 0.3], + [-158, 0, 0, 0], + [129, 0.1, -70, 0], + [123, 0, -53, 0], + [63, 0, 0, 0], + [63, 0.1, -33, 0], + [-59, 0, 26, 0], + [-58, -0.1, 32, 0], + [-51, 0, 27, 0], + [48, 0, 0, 0], + [46, 0, -24, 0], + [-38, 0, 16, 0], + [-31, 0, 13, 0], + [29, 0, 0, 0], + [29, 0, -12, 0], + [26, 0, 0, 0], + [-22, 0, 0, 0], + [21, 0, -10, 0], + [17, -0.1, 0, 0], + [16, 0, -8, 0], + [-16, 0.1, 7, 0], + [-15, 0, 9, 0], + [-13, 0, 7, 0], + [-12, 0, 6, 0], + [11, 0, 0, 0], + [-10, 0, 5, 0], + [-8, 0, 3, 0], + [7, 0, -3, 0], + [-7, 0, 0, 0], + [-7, 0, 3, 0], + [-7, 0, 3, 0], + [6, 0, 0, 0], + [6, 0, -3, 0], + [6, 0, -3, 0], + [-6, 0, 3, 0], + [-6, 0, 3, 0], + [5, 0, 0, 0], + [-5, 0, 3, 0], + [-5, 0, 3, 0], + [-5, 0, 3, 0], + [4, 0, 0, 0], + [4, 0, 0, 0], + [4, 0, 0, 0], + [-4, 0, 0, 0], + [-4, 0, 0, 0], + [-4, 0, 0, 0], + [3, 0, 0, 0], + [-3, 0, 0, 0], + [-3, 0, 0, 0], + [-3, 0, 0, 0], + [-3, 0, 0, 0], + [-3, 0, 0, 0], + [-3, 0, 0, 0], + [-3, 0, 0, 0], + ] + ) # Using the tabulated values, compute the delta_longitude and # delta_obliquity. - Xi = np.array([X0, X1, X2, X3, X4]) # a col mat in octave + Xi = np.array([X0, X1, X2, X3, X4]) # a col mat in octave - tabulated_argument = Y_terms.dot(np.transpose(Xi)) * (np.pi/180) + tabulated_argument = Y_terms.dot(np.transpose(Xi)) * (np.pi / 180) - delta_longitude = (nutation_terms[:, 0] + (nutation_terms[:, 1] * JCE)) * np.sin(tabulated_argument) - delta_obliquity = (nutation_terms[:, 2] + (nutation_terms[:, 3] * JCE)) * np.cos(tabulated_argument) + delta_longitude = ( + nutation_terms[:, 0] + (nutation_terms[:, 1] * JCE) + ) * np.sin(tabulated_argument) + delta_obliquity = ( + nutation_terms[:, 2] + (nutation_terms[:, 3] * JCE) + ) * np.cos(tabulated_argument) - nutation = dict() # init nutation dictionary + nutation = dict() # init nutation dictionary # Nutation in longitude - nutation['longitude'] = np.sum(delta_longitude) / 36000000 + nutation["longitude"] = np.sum(delta_longitude) / 36000000 # Nutation in obliquity - nutation['obliquity'] = np.sum(delta_obliquity) / 36000000 + nutation["obliquity"] = np.sum(delta_obliquity) / 36000000 return nutation @@ -762,17 +898,39 @@ def true_obliquity_calculation(julian, nutation): :return: """ - p = np.atleast_2d([2.45, 5.79, 27.87, 7.12, -39.05, -249.67, -51.38, 1999.25, -1.55, -4680.93, 84381.448]) + p = np.atleast_2d( + [ + 2.45, + 5.79, + 27.87, + 7.12, + -39.05, + -249.67, + -51.38, + 1999.25, + -1.55, + -4680.93, + 84381.448, + ] + ) # mean_obliquity = polyval(p, julian.ephemeris_millenium/10); - U = julian['ephemeris_millenium'] / 10 - mean_obliquity = p[0, 0] * np.power(U, 10) + p[0, 1] * np.power(U, 9) + \ - p[0, 2] * np.power(U, 8) + p[0, 3] * np.power(U, 7) + \ - p[0, 4] * np.power(U, 6) + p[0, 5] * np.power(U, 5) + \ - p[0, 6] * np.power(U, 4) + p[0, 7] * np.power(U, 3) + \ - p[0, 8] * np.power(U, 2) + p[0, 9] * U + p[0, 10] - - true_obliquity = (mean_obliquity/3600) + nutation['obliquity'] + U = julian["ephemeris_millenium"] / 10 + mean_obliquity = ( + p[0, 0] * np.power(U, 10) + + p[0, 1] * np.power(U, 9) + + p[0, 2] * np.power(U, 8) + + p[0, 3] * np.power(U, 7) + + p[0, 4] * np.power(U, 6) + + p[0, 5] * np.power(U, 5) + + p[0, 6] * np.power(U, 4) + + p[0, 7] * np.power(U, 3) + + p[0, 8] * np.power(U, 2) + + p[0, 9] * U + + p[0, 10] + ) + + true_obliquity = (mean_obliquity / 3600) + nutation["obliquity"] return true_obliquity @@ -785,11 +943,15 @@ def abberation_correction_calculation(earth_heliocentric_position): :param earth_heliocentric_position: :return: """ - aberration_correction = -20.4898/(3600*earth_heliocentric_position['radius']) + aberration_correction = -20.4898 / ( + 3600 * earth_heliocentric_position["radius"] + ) return aberration_correction -def apparent_sun_longitude_calculation(sun_geocentric_position, nutation, aberration_correction): +def apparent_sun_longitude_calculation( + sun_geocentric_position, nutation, aberration_correction +): """ This function compute the sun apparent longitude @@ -798,7 +960,11 @@ def apparent_sun_longitude_calculation(sun_geocentric_position, nutation, aberra :param aberration_correction: :return: """ - apparent_sun_longitude = sun_geocentric_position['longitude'] + nutation['longitude'] + aberration_correction + apparent_sun_longitude = ( + sun_geocentric_position["longitude"] + + nutation["longitude"] + + aberration_correction + ) return apparent_sun_longitude @@ -812,22 +978,29 @@ def apparent_stime_at_greenwich_calculation(julian, nutation, true_obliquity): :return: """ - JD = julian['day'] - JC = julian['century'] + JD = julian["day"] + JC = julian["century"] # Mean sideral time, in degrees - mean_stime = 280.46061837 + (360.98564736629*(JD-2451545)) + \ - (0.000387933*np.power(JC, 2)) - \ - (np.power(JC, 3)/38710000) + mean_stime = ( + 280.46061837 + + (360.98564736629 * (JD - 2451545)) + + (0.000387933 * np.power(JC, 2)) + - (np.power(JC, 3) / 38710000) + ) # Limit the range to [0-360]; mean_stime = set_to_range(mean_stime, 0, 360) - apparent_stime_at_greenwich = mean_stime + (nutation['longitude'] * np.cos(true_obliquity * np.pi/180)) + apparent_stime_at_greenwich = mean_stime + ( + nutation["longitude"] * np.cos(true_obliquity * np.pi / 180) + ) return apparent_stime_at_greenwich -def sun_rigth_ascension_calculation(apparent_sun_longitude, true_obliquity, sun_geocentric_position): +def sun_rigth_ascension_calculation( + apparent_sun_longitude, true_obliquity, sun_geocentric_position +): """ This function compute the sun rigth ascension. :param apparent_sun_longitude: @@ -836,17 +1009,26 @@ def sun_rigth_ascension_calculation(apparent_sun_longitude, true_obliquity, sun_ :return: """ - argument_numerator = (np.sin(apparent_sun_longitude * np.pi/180) * np.cos(true_obliquity * np.pi/180)) - \ - (np.tan(sun_geocentric_position['latitude'] * np.pi/180) * np.sin(true_obliquity * np.pi/180)) - argument_denominator = np.cos(apparent_sun_longitude * np.pi/180); - - sun_rigth_ascension = np.arctan2(argument_numerator, argument_denominator) * 180/np.pi + argument_numerator = ( + np.sin(apparent_sun_longitude * np.pi / 180) + * np.cos(true_obliquity * np.pi / 180) + ) - ( + np.tan(sun_geocentric_position["latitude"] * np.pi / 180) + * np.sin(true_obliquity * np.pi / 180) + ) + argument_denominator = np.cos(apparent_sun_longitude * np.pi / 180) + + sun_rigth_ascension = ( + np.arctan2(argument_numerator, argument_denominator) * 180 / np.pi + ) # Limit the range to [0,360]; sun_rigth_ascension = set_to_range(sun_rigth_ascension, 0, 360) return sun_rigth_ascension -def sun_geocentric_declination_calculation(apparent_sun_longitude, true_obliquity, sun_geocentric_position): +def sun_geocentric_declination_calculation( + apparent_sun_longitude, true_obliquity, sun_geocentric_position +): """ :param apparent_sun_longitude: @@ -855,15 +1037,22 @@ def sun_geocentric_declination_calculation(apparent_sun_longitude, true_obliquit :return: """ - argument = (np.sin(sun_geocentric_position['latitude'] * np.pi/180) * np.cos(true_obliquity * np.pi/180)) + \ - (np.cos(sun_geocentric_position['latitude'] * np.pi/180) * np.sin(true_obliquity * np.pi/180) * - np.sin(apparent_sun_longitude * np.pi/180)) + argument = ( + np.sin(sun_geocentric_position["latitude"] * np.pi / 180) + * np.cos(true_obliquity * np.pi / 180) + ) + ( + np.cos(sun_geocentric_position["latitude"] * np.pi / 180) + * np.sin(true_obliquity * np.pi / 180) + * np.sin(apparent_sun_longitude * np.pi / 180) + ) - sun_geocentric_declination = np.arcsin(argument) * 180/np.pi + sun_geocentric_declination = np.arcsin(argument) * 180 / np.pi return sun_geocentric_declination -def observer_local_hour_calculation(apparent_stime_at_greenwich, location, sun_rigth_ascension): +def observer_local_hour_calculation( + apparent_stime_at_greenwich, location, sun_rigth_ascension +): """ This function computes observer local hour. @@ -873,14 +1062,23 @@ def observer_local_hour_calculation(apparent_stime_at_greenwich, location, sun_r :return: """ - observer_local_hour = apparent_stime_at_greenwich + location['longitude'] - sun_rigth_ascension + observer_local_hour = ( + apparent_stime_at_greenwich + + location["longitude"] + - sun_rigth_ascension + ) # Set the range to [0-360] observer_local_hour = set_to_range(observer_local_hour, 0, 360) return observer_local_hour -def topocentric_sun_position_calculate(earth_heliocentric_position, location, - observer_local_hour, sun_rigth_ascension, sun_geocentric_declination): +def topocentric_sun_position_calculate( + earth_heliocentric_position, + location, + observer_local_hour, + sun_rigth_ascension, + sun_geocentric_declination, +): """ This function compute the sun position (rigth ascension and declination) with respect to the observer local position at the Earth surface. @@ -894,39 +1092,65 @@ def topocentric_sun_position_calculate(earth_heliocentric_position, location, """ # Equatorial horizontal parallax of the sun in degrees - eq_horizontal_parallax = 8.794 / (3600 * earth_heliocentric_position['radius']) + eq_horizontal_parallax = 8.794 / ( + 3600 * earth_heliocentric_position["radius"] + ) # Term u, used in the following calculations (in radians) - u = np.arctan(0.99664719 * np.tan(location['latitude'] * np.pi/180)) + u = np.arctan(0.99664719 * np.tan(location["latitude"] * np.pi / 180)) # Term x, used in the following calculations - x = np.cos(u) + ((location['altitude']/6378140) * np.cos(location['latitude'] * np.pi/180)) + x = np.cos(u) + ( + (location["altitude"] / 6378140) + * np.cos(location["latitude"] * np.pi / 180) + ) # Term y, used in the following calculations - y = (0.99664719 * np.sin(u)) + ((location['altitude']/6378140) * np.sin(location['latitude'] * np.pi/180)) + y = (0.99664719 * np.sin(u)) + ( + (location["altitude"] / 6378140) + * np.sin(location["latitude"] * np.pi / 180) + ) # Parallax in the sun rigth ascension (in radians) - nominator = -x * np.sin(eq_horizontal_parallax * np.pi/180) * np.sin(observer_local_hour * np.pi/180) - denominator = np.cos(sun_geocentric_declination * np.pi/180) - (x * np.sin(eq_horizontal_parallax * np.pi/180) * - np.cos(observer_local_hour * np.pi/180)) + nominator = ( + -x + * np.sin(eq_horizontal_parallax * np.pi / 180) + * np.sin(observer_local_hour * np.pi / 180) + ) + denominator = np.cos(sun_geocentric_declination * np.pi / 180) - ( + x + * np.sin(eq_horizontal_parallax * np.pi / 180) + * np.cos(observer_local_hour * np.pi / 180) + ) sun_rigth_ascension_parallax = np.arctan2(nominator, denominator) # Conversion to degrees. topocentric_sun_position = dict() - topocentric_sun_position['rigth_ascension_parallax'] = sun_rigth_ascension_parallax * 180/np.pi + topocentric_sun_position["rigth_ascension_parallax"] = ( + sun_rigth_ascension_parallax * 180 / np.pi + ) # Topocentric sun rigth ascension (in degrees) - topocentric_sun_position['rigth_ascension'] = sun_rigth_ascension + (sun_rigth_ascension_parallax * 180/np.pi) + topocentric_sun_position["rigth_ascension"] = sun_rigth_ascension + ( + sun_rigth_ascension_parallax * 180 / np.pi + ) # Topocentric sun declination (in degrees) - nominator = (np.sin(sun_geocentric_declination * np.pi/180) - (y*np.sin(eq_horizontal_parallax * np.pi/180))) * \ - np.cos(sun_rigth_ascension_parallax) - denominator = np.cos(sun_geocentric_declination * np.pi/180) - (y*np.sin(eq_horizontal_parallax * np.pi/180)) * \ - np.cos(observer_local_hour * np.pi/180) - topocentric_sun_position['declination'] = np.arctan2(nominator, denominator) * 180/np.pi + nominator = ( + np.sin(sun_geocentric_declination * np.pi / 180) + - (y * np.sin(eq_horizontal_parallax * np.pi / 180)) + ) * np.cos(sun_rigth_ascension_parallax) + denominator = np.cos(sun_geocentric_declination * np.pi / 180) - ( + y * np.sin(eq_horizontal_parallax * np.pi / 180) + ) * np.cos(observer_local_hour * np.pi / 180) + topocentric_sun_position["declination"] = ( + np.arctan2(nominator, denominator) * 180 / np.pi + ) return topocentric_sun_position -def topocentric_local_hour_calculate(observer_local_hour, topocentric_sun_position): +def topocentric_local_hour_calculate( + observer_local_hour, topocentric_sun_position +): """ This function compute the topocentric local jour angle in degrees @@ -935,11 +1159,16 @@ def topocentric_local_hour_calculate(observer_local_hour, topocentric_sun_positi :return: """ - topocentric_local_hour = observer_local_hour - topocentric_sun_position['rigth_ascension_parallax'] + topocentric_local_hour = ( + observer_local_hour + - topocentric_sun_position["rigth_ascension_parallax"] + ) return topocentric_local_hour -def sun_topocentric_zenith_angle_calculate(location, topocentric_sun_position, topocentric_local_hour): +def sun_topocentric_zenith_angle_calculate( + location, topocentric_sun_position, topocentric_local_hour +): """ This function compute the sun zenith angle, taking into account the atmospheric refraction. A default temperature of 283K and a @@ -952,14 +1181,19 @@ def sun_topocentric_zenith_angle_calculate(location, topocentric_sun_position, t """ # Topocentric elevation, without atmospheric refraction - argument = (np.sin(location['latitude'] * np.pi/180) * np.sin(topocentric_sun_position['declination'] * np.pi/180)) + \ - (np.cos(location['latitude'] * np.pi/180) * np.cos(topocentric_sun_position['declination'] * np.pi/180) * - np.cos(topocentric_local_hour * np.pi/180)) - true_elevation = np.arcsin(argument) * 180/np.pi + argument = ( + np.sin(location["latitude"] * np.pi / 180) + * np.sin(topocentric_sun_position["declination"] * np.pi / 180) + ) + ( + np.cos(location["latitude"] * np.pi / 180) + * np.cos(topocentric_sun_position["declination"] * np.pi / 180) + * np.cos(topocentric_local_hour * np.pi / 180) + ) + true_elevation = np.arcsin(argument) * 180 / np.pi # Atmospheric refraction correction (in degrees) - argument = true_elevation + (10.3/(true_elevation + 5.11)) - refraction_corr = 1.02 / (60 * np.tan(argument * np.pi/180)) + argument = true_elevation + (10.3 / (true_elevation + 5.11)) + refraction_corr = 1.02 / (60 * np.tan(argument * np.pi / 180)) # For exact pressure and temperature correction, use this, # with P the pressure in mbar amd T the temperature in Kelvins: @@ -969,18 +1203,23 @@ def sun_topocentric_zenith_angle_calculate(location, topocentric_sun_position, t apparent_elevation = true_elevation + refraction_corr sun = dict() - sun['zenith'] = 90 - apparent_elevation + sun["zenith"] = 90 - apparent_elevation # Topocentric azimuth angle. The +180 conversion is to pass from astronomer # notation (westward from south) to navigation notation (eastward from # north); - nominator = np.sin(topocentric_local_hour * np.pi/180) - denominator = (np.cos(topocentric_local_hour * np.pi/180) * np.sin(location['latitude'] * np.pi/180)) - \ - (np.tan(topocentric_sun_position['declination'] * np.pi/180) * np.cos(location['latitude'] * np.pi/180)) - sun['azimuth'] = (np.arctan2(nominator, denominator) * 180/np.pi) + 180 + nominator = np.sin(topocentric_local_hour * np.pi / 180) + denominator = ( + np.cos(topocentric_local_hour * np.pi / 180) + * np.sin(location["latitude"] * np.pi / 180) + ) - ( + np.tan(topocentric_sun_position["declination"] * np.pi / 180) + * np.cos(location["latitude"] * np.pi / 180) + ) + sun["azimuth"] = (np.arctan2(nominator, denominator) * 180 / np.pi) + 180 # Set the range to [0-360] - sun['azimuth'] = set_to_range(sun['azimuth'], 0, 360) + sun["azimuth"] = set_to_range(sun["azimuth"], 0, 360) return sun @@ -993,9 +1232,8 @@ def set_to_range(var, min_interval, max_interval): :param max_interval: :return: """ - var = var - max_interval * np.floor(var/max_interval) + var = var - max_interval * np.floor(var / max_interval) if var < min_interval: var = var + max_interval return var - diff --git a/util/__init__.py b/util/__init__.py index adc285c..1fcd232 100644 --- a/util/__init__.py +++ b/util/__init__.py @@ -8,23 +8,33 @@ # This works around https://github.com/qgis/QGIS/issues/55258 import site import sys + sys.path.insert(0, site.getusersitepackages()) from qgis.PyQt.QtWidgets import QMessageBox from .umep_installer import locate_py, setup_umep_python from qgis.core import Qgis, QgsMessageLog - try: - #import timezonefinder + # import timezonefinder from supy import __version__ as ver_supy - QgsMessageLog.logMessage("UMEP - SuPy Version installed: " + ver_supy, level=Qgis.MessageLevel.Info) + + QgsMessageLog.logMessage( + "UMEP - SuPy Version installed: " + ver_supy, + level=Qgis.MessageLevel.Info, + ) except: - if QMessageBox.question(None, "UMEP Python dependencies not installed or need to be updated", - "Do you automatically want install missing python modules? \r\n" - "QGIS will be non-responsive for a couple of minutes.", - QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel) == QMessageBox.StandardButton.Ok: + if ( + QMessageBox.question( + None, + "UMEP Python dependencies not installed or need to be updated", + "Do you automatically want install missing python modules? \r\n" + "QGIS will be non-responsive for a couple of minutes.", + QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel, + ) + == QMessageBox.StandardButton.Ok + ): try: path_pybin = locate_py() except Exception: @@ -34,17 +44,26 @@ "Please report at https://github.com/UMEP-dev/UMEP-processing/issues", ) try: - setup_umep_python(ver='4.0') - QMessageBox.information(None, "Packages successfully installed", - "To make all parts of the plugin work it is recommended to restart your QGIS-session.") + setup_umep_python(ver="4.0") + QMessageBox.information( + None, + "Packages successfully installed", + "To make all parts of the plugin work it is recommended to restart your QGIS-session.", + ) except Exception as e: - QgsMessageLog.logMessage(traceback.format_exc(), level=Qgis.MessageLevel.Warning) - QMessageBox.information(None, "An error occurred", - "UMEP couldn't install Python packages!\n" - "See 'General' tab in 'Log Messages' panel for details.\n" - "Report any errors to https://github.com/UMEP-dev/UMEP-processing/issues") + QgsMessageLog.logMessage( + traceback.format_exc(), level=Qgis.MessageLevel.Warning + ) + QMessageBox.information( + None, + "An error occurred", + "UMEP couldn't install Python packages!\n" + "See 'General' tab in 'Log Messages' panel for details.\n" + "Report any errors to https://github.com/UMEP-dev/UMEP-processing/issues", + ) else: - QMessageBox.information(None, - "Information", "Packages not installed. Some UMEP tools will not be fully operational.") - - + QMessageBox.information( + None, + "Information", + "Packages not installed. Some UMEP tools will not be fully operational.", + ) diff --git a/util/f90nml/__init__.py b/util/f90nml/__init__.py index 6410f3d..00a6457 100644 --- a/util/f90nml/__init__.py +++ b/util/f90nml/__init__.py @@ -1,16 +1,18 @@ """f90nml - ====== +====== - A Fortran 90 namelist parser and generator. +A Fortran 90 namelist parser and generator. - :copyright: Copyright 2014 Marshall Ward, see AUTHORS for details. - :license: Apache License, Version 2.0, see LICENSE for details. +:copyright: Copyright 2014 Marshall Ward, see AUTHORS for details. +:license: Apache License, Version 2.0, see LICENSE for details. """ + from __future__ import absolute_import + # from f90nml.parser import Parser from .parser import Parser -__version__ = '0.12' +__version__ = "0.12" def read(nml_fname): diff --git a/util/f90nml/fpy.py b/util/f90nml/fpy.py index 74ebadf..950fcc5 100644 --- a/util/f90nml/fpy.py +++ b/util/f90nml/fpy.py @@ -1,11 +1,11 @@ """f90nml.fpy - ============= +============= - Module for conversion between basic data types and Fortran string - representations. +Module for conversion between basic data types and Fortran string +representations. - :copyright: Copyright 2014 Marshall Ward, see AUTHORS for details. - :license: Apache License, Version 2.0, see LICENSE for details. +:copyright: Copyright 2014 Marshall Ward, see AUTHORS for details. +:license: Apache License, Version 2.0, see LICENSE for details. """ @@ -13,21 +13,22 @@ def pyfloat(v_str): """Convert string repr of Fortran floating point to Python double.""" # NOTE: There is no loss of information from SP to DP floats - return float(v_str.lower().replace('d', 'e')) + return float(v_str.lower().replace("d", "e")) def pycomplex(v_str): """Convert string repr of Fortran complex to Python complex.""" assert isinstance(v_str, str) - if v_str[0] == '(' and v_str[-1] == ')' and len(v_str.split(',')) == 2: - v_re, v_im = v_str[1:-1].split(',', 1) + if v_str[0] == "(" and v_str[-1] == ")" and len(v_str.split(",")) == 2: + v_re, v_im = v_str[1:-1].split(",", 1) # NOTE: Failed float(str) will raise ValueError return complex(pyfloat(v_re), pyfloat(v_im)) else: - raise ValueError('{0} must be in complex number form (x, y).' - ''.format(v_str)) + raise ValueError( + "{0} must be in complex number form (x, y)." "".format(v_str) + ) def pybool(v_str): @@ -35,19 +36,19 @@ def pybool(v_str): assert isinstance(v_str, str) try: - if v_str.startswith('.'): + if v_str.startswith("."): v_bool = v_str[1].lower() else: v_bool = v_str[0].lower() except IndexError: - raise ValueError('{0} is not a valid logical constant.'.format(v_str)) + raise ValueError("{0} is not a valid logical constant.".format(v_str)) - if v_bool == 't': + if v_bool == "t": return True - elif v_bool == 'f': + elif v_bool == "f": return False else: - raise ValueError('{0} is not a valid logical constant.'.format(v_str)) + raise ValueError("{0} is not a valid logical constant.".format(v_str)) def pystr(v_str): diff --git a/util/f90nml/namelist.py b/util/f90nml/namelist.py index b8221da..0da7f84 100644 --- a/util/f90nml/namelist.py +++ b/util/f90nml/namelist.py @@ -1,15 +1,17 @@ """f90nml.namelist - =============== +=============== - Tools for creating Fortran namelist files from Python ``dict``s. +Tools for creating Fortran namelist files from Python ``dict``s. - :copyright: Copyright 2014 Marshall Ward, see AUTHORS for details. - :license: Apache License, Version 2.0, see LICENSE for details. +:copyright: Copyright 2014 Marshall Ward, see AUTHORS for details. +:license: Apache License, Version 2.0, see LICENSE for details. """ + from __future__ import print_function from builtins import str import os + try: from collections import OrderedDict except ImportError: @@ -29,26 +31,20 @@ def __init__(self, *args, **kwds): # Formatting properties self._colwidth = 72 - self._indent = 4 * ' ' + self._indent = 4 * " " self._end_comma = False self._uppercase = False - self._floatformat = '' - self._logical_repr = {False: '.false.', True: '.true.'} + self._floatformat = "" + self._logical_repr = {False: ".false.", True: ".true."} # Representatation functions self.f90str = { - bool: - lambda x: self.logical_repr[x], - int: - lambda x: str(x), - float: - lambda x: '{0:{fmt}}'.format(x, fmt=self.floatformat), - complex: - lambda x: '({0}, {1})'.format(x.real, x.imag), - str: - lambda x: repr(x).replace("\\'", "''").replace('\\"', '""'), - type(None): - lambda x: '' + bool: lambda x: self.logical_repr[x], + int: lambda x: str(x), + float: lambda x: "{0:{fmt}}".format(x, fmt=self.floatformat), + complex: lambda x: "({0}, {1})".format(x.real, x.imag), + str: lambda x: repr(x).replace("\\'", "''").replace('\\"', '""'), + type(None): lambda x: "", } def __contains__(self, key): @@ -78,9 +74,9 @@ def colwidth(self, width): if width >= 0: self._colwidth = width else: - raise ValueError('Column width must be nonnegative.') + raise ValueError("Column width must be nonnegative.") else: - raise TypeError('Column width must be a nonnegative integer.') + raise TypeError("Column width must be a nonnegative integer.") # Variable indent @property @@ -99,19 +95,21 @@ def indent(self, value): if value.isspace(): self._indent = value else: - raise ValueError('String indentation can only contain ' - 'whitespace.') + raise ValueError( + "String indentation can only contain " "whitespace." + ) # Set indent width elif isinstance(value, int): if value >= 0: - self._indent = value * ' ' + self._indent = value * " " else: - raise ValueError('Indentation spacing must be nonnegative.') + raise ValueError("Indentation spacing must be nonnegative.") else: - raise TypeError('Indentation must be specified by string or space ' - 'width.') + raise TypeError( + "Indentation must be specified by string or space " "width." + ) # Terminal comma @property @@ -123,7 +121,7 @@ def end_comma(self): def end_comma(self, value): """Validate and set the comma termination flag.""" if not isinstance(value, bool): - raise TypeError('end_comma attribute must be a logical type.') + raise TypeError("end_comma attribute must be a logical type.") self._end_comma = value # Uppercase @@ -136,7 +134,7 @@ def uppercase(self): def uppercase(self, value): """Validate and set the upper case flag.""" if not isinstance(value, bool): - raise TypeError('uppercase attribute must be a logical type.') + raise TypeError("uppercase attribute must be a logical type.") self._uppercase = value # Float format @@ -150,11 +148,11 @@ def floatformat(self, value): """Validate and set the upper case flag.""" if isinstance(value, str): # Duck-test the format string; raise ValueError on fail - '{0:{1}}'.format(1.23, value) + "{0:{1}}".format(1.23, value) self._floatformat = value else: - raise TypeError('Floating point format code must be a string.') + raise TypeError("Floating point format code must be a string.") # Logical representation # NOTE: This presumes that bools and ints are identical as dict keys @@ -168,8 +166,10 @@ def logical_repr(self, value): """Set the namelist representations of logical values.""" if not any(isinstance(value, t) for t in (list, tuple)): - raise TypeError("Logical representation must be a tuple with " - "a valid true and false value.") + raise TypeError( + "Logical representation must be a tuple with " + "a valid true and false value." + ) if not len(value) == 2: raise ValueError("List must contain two values.") @@ -185,14 +185,17 @@ def true_repr(self): def true_repr(self, value): """Validate and set the logical true representation.""" if isinstance(value, str): - if not (value.lower().startswith('t') or - value.lower().startswith('.t')): - raise ValueError("Logical true representation must start with " - "'T' or '.T'.") + if not ( + value.lower().startswith("t") or value.lower().startswith(".t") + ): + raise ValueError( + "Logical true representation must start with " + "'T' or '.T'." + ) else: self._logical_repr[1] = value else: - raise TypeError('Logical true representation must be a string.') + raise TypeError("Logical true representation must be a string.") @property def false_repr(self): @@ -203,14 +206,17 @@ def false_repr(self): def false_repr(self, value): """Validate and set the logical false representation.""" if isinstance(value, str): - if not (value.lower().startswith('f') or - value.lower().startswith('.f')): - raise ValueError("Logical false representation must start " - "with 'F' or '.F'.") + if not ( + value.lower().startswith("f") or value.lower().startswith(".f") + ): + raise ValueError( + "Logical false representation must start " + "with 'F' or '.F'." + ) else: self._logical_repr[0] = value else: - raise TypeError('Logical false representation must be a string.') + raise TypeError("Logical false representation must be a string.") # File output @@ -218,9 +224,9 @@ def write(self, nml_path, force=False): """Output dict to a Fortran 90 namelist file.""" if not force and os.path.isfile(nml_path): - raise IOError('File {0} already exists.'.format(nml_path)) + raise IOError("File {0} already exists.".format(nml_path)) - with open(nml_path, 'w') as nml_file: + with open(nml_path, "w") as nml_file: for grp_name, grp_vars in list(self.items()): # Check for repeated namelist records (saved as lists) if isinstance(grp_vars, list): @@ -230,7 +236,7 @@ def write(self, nml_path, force=False): self.write_nmlgrp(grp_name, grp_vars, nml_file) if list(self.items()): - with open(nml_path, 'rb+') as nml_file: + with open(nml_path, "rb+") as nml_file: nml_file.seek(-1, os.SEEK_END) nml_file.truncate() @@ -240,15 +246,15 @@ def write_nmlgrp(self, grp_name, grp_vars, nml_file): if self.uppercase: grp_name = grp_name.upper() - print('&{0}'.format(grp_name), file=nml_file) + print("&{0}".format(grp_name), file=nml_file) for v_name, v_val in list(grp_vars.items()): for v_str in self.var_strings(v_name, v_val): - nml_line = self.indent + '{0}'.format(v_str) + nml_line = self.indent + "{0}".format(v_str) print(nml_line, file=nml_file) - print('/', file=nml_file) + print("/", file=nml_file) print(file=nml_file) def var_strings(self, v_name, v_values): @@ -262,21 +268,23 @@ def var_strings(self, v_name, v_values): # Parse derived type contents if isinstance(v_values, dict): for f_name, f_vals in list(v_values.items()): - v_title = '%'.join([v_name, f_name]) + v_title = "%".join([v_name, f_name]) v_strs = self.var_strings(v_title, f_vals) var_strs.extend(v_strs) # Parse an array of derived types - elif (isinstance(v_values, list) and - any(isinstance(v, dict) for v in v_values) and - all((isinstance(v, dict) or v is None) for v in v_values)): + elif ( + isinstance(v_values, list) + and any(isinstance(v, dict) for v in v_values) + and all((isinstance(v, dict) or v is None) for v in v_values) + ): for idx, val in enumerate(v_values, start=1): if val is None: continue - v_title = v_name + '({0})'.format(idx) + v_title = v_name + "({0})".format(idx) v_strs = self.var_strings(v_title, val) var_strs.extend(v_strs) @@ -288,31 +296,32 @@ def var_strings(self, v_name, v_values): # Split output across multiple lines (if necessary) val_strs = [] - val_line = '' + val_line = "" for v_val in v_values: - v_width = self.colwidth - len(self.indent + v_name + ' = ') + v_width = self.colwidth - len(self.indent + v_name + " = ") if len(val_line) < v_width: - val_line += self.f90repr(v_val) + ', ' + val_line += self.f90repr(v_val) + ", " if len(val_line) >= v_width: val_strs.append(val_line.rstrip()) - val_line = '' + val_line = "" # Append any remaining values if val_line: - if (self.end_comma or - (len(v_values) > 1 and v_values[-1] is None)): + if self.end_comma or ( + len(v_values) > 1 and v_values[-1] is None + ): val_strs.append(val_line) else: val_strs.append(val_line[:-2]) # Complete the set of values - var_strs.append('{0} = {1}'.format(v_name, val_strs[0]).strip()) + var_strs.append("{0} = {1}".format(v_name, val_strs[0]).strip()) for v_str in val_strs[1:]: - var_strs.append(' ' * (len(v_name + ' = ')) + v_str) + var_strs.append(" " * (len(v_name + " = ")) + v_str) return var_strs @@ -322,5 +331,7 @@ def f90repr(self, value): try: return self.f90str[type(value)](value) except KeyError: - raise ValueError('Type {0} of {1} cannot be converted to a ' - 'Fortran type.'.format(type(value), value)) + raise ValueError( + "Type {0} of {1} cannot be converted to a " + "Fortran type.".format(type(value), value) + ) diff --git a/util/f90nml/parser.py b/util/f90nml/parser.py index 466243e..05d44f3 100644 --- a/util/f90nml/parser.py +++ b/util/f90nml/parser.py @@ -1,12 +1,13 @@ """f90nml.parser - ============= +============= - Fortran namelist parser and tokenizer to convert contents into a hierarchy - of dicts containing intrinsic Python data types. +Fortran namelist parser and tokenizer to convert contents into a hierarchy +of dicts containing intrinsic Python data types. - :copyright: Copyright 2014 Marshall Ward, see AUTHORS for details. - :license: Apache License, Version 2.0, see LICENSE for details. +:copyright: Copyright 2014 Marshall Ward, see AUTHORS for details. +:license: Apache License, Version 2.0, see LICENSE for details. """ + from __future__ import absolute_import from builtins import next from builtins import range @@ -40,33 +41,35 @@ def read(self, nml_fname, nml_patch_in=None, patch_fname=None): >>> parser = Parser() >>> data_nml = parser.read('data.nml')""" - nml_file = open(nml_fname, 'r') + nml_file = open(nml_fname, "r") if nml_patch_in: if not isinstance(nml_patch_in, dict): nml_file.close() - raise ValueError('Input patch must be a dict or an NmlDict.') + raise ValueError("Input patch must be a dict or an NmlDict.") nml_patch = copy.deepcopy(NmlDict(nml_patch_in)) if not patch_fname: - patch_fname = nml_fname + '~' + patch_fname = nml_fname + "~" elif nml_fname == patch_fname: nml_file.close() - raise ValueError('f90nml: error: Patch filepath cannot be the ' - 'same as the original filepath.') - self.pfile = open(patch_fname, 'w') + raise ValueError( + "f90nml: error: Patch filepath cannot be the " + "same as the original filepath." + ) + self.pfile = open(patch_fname, "w") else: nml_patch = NmlDict() f90lex = shlex.shlex(nml_file) - f90lex.whitespace = '' - f90lex.wordchars += '.-+' # Include floating point tokens + f90lex.whitespace = "" + f90lex.wordchars += ".-+" # Include floating point tokens if nml_patch: - f90lex.commenters = '' + f90lex.commenters = "" else: - f90lex.commenters = '!' + f90lex.commenters = "!" self.tokens = iter(f90lex) @@ -77,11 +80,11 @@ def read(self, nml_fname, nml_patch_in=None, patch_fname=None): while True: try: # Check for classic group terminator - if self.token == 'end': + if self.token == "end": self.update_tokens() # Ignore tokens outside of namelist groups - while self.token not in ('&', '$'): + while self.token not in ("&", "$"): self.update_tokens() except StopIteration: @@ -99,15 +102,16 @@ def read(self, nml_fname, nml_patch_in=None, patch_fname=None): # Populate the namelist group while g_name: - if self.token not in ('=', '%', '('): + if self.token not in ("=", "%", "("): self.update_tokens() # Set the next active variable - if self.token in ('=', '(', '%'): + if self.token in ("=", "(", "%"): try: v_name, v_values = self.parse_variable( - g_vars, patch_nml=grp_patch) + g_vars, patch_nml=grp_patch + ) except ValueError: nml_file.close() if self.pfile: @@ -128,14 +132,14 @@ def read(self, nml_fname, nml_patch_in=None, patch_fname=None): v_values = [] # Finalise namelist group - if self.token in ('/', '&', '$'): + if self.token in ("/", "&", "$"): # Append any remaining patched variables for v_name, v_val in list(grp_patch.items()): g_vars[v_name] = v_val v_strs = nmls.var_strings(v_name, v_val) for v_str in v_strs: - self.pfile.write(' {0}\n'.format(v_str)) + self.pfile.write(" {0}\n".format(v_str)) # Append the grouplist to the namelist if g_name in nmls: @@ -179,7 +183,7 @@ def parse_variable(self, parent, patch_nml=None): patch_values = None write_token = v_name not in patch_nml - if self.token == '(': + if self.token == "(": v_indices = self.parse_index() @@ -195,7 +199,7 @@ def parse_variable(self, parent, patch_nml=None): else: v_idx = None - if self.token == '%': + if self.token == "%": # Resolve the derived type @@ -216,7 +220,7 @@ def parse_variable(self, parent, patch_nml=None): else: # Construct the variable array - assert self.token == '=' + assert self.token == "=" n_vals = None prior_ws_sep = ws_sep = False @@ -231,11 +235,13 @@ def parse_variable(self, parent, patch_nml=None): self.pfile.write(p_val) # Add variables until next variable trigger - while (self.token not in ('=', '(', '%') or - (self.prior_token, self.token) == ('=', '(')): + while self.token not in ("=", "(", "%") or ( + self.prior_token, + self.token, + ) == ("=", "("): # Check for repeated values - if self.token == '*': + if self.token == "*": n_vals = self.parse_value(write_token) assert isinstance(n_vals, int) self.update_tokens(write_token) @@ -243,19 +249,22 @@ def parse_variable(self, parent, patch_nml=None): n_vals = 1 # First check for implicit null values - if self.prior_token in ('=', '%', ','): - if (self.token in (',', '/', '&', '$') and - not (self.prior_token == ',' and - self.token in ('/', '&', '$'))): + if self.prior_token in ("=", "%", ","): + if self.token in (",", "/", "&", "$") and not ( + self.prior_token == "," + and self.token in ("/", "&", "$") + ): append_value(v_values, None, v_idx, n_vals) - elif self.prior_token == '*': + elif self.prior_token == "*": - if self.token not in ('/', '&', '$'): + if self.token not in ("/", "&", "$"): self.update_tokens(write_token) - if (self.token == '=' or (self.token in ('/', '&', '$') and - self.prior_token == '*')): + if self.token == "=" or ( + self.token in ("/", "&", "$") + and self.prior_token == "*" + ): next_value = None else: next_value = self.parse_value(write_token) @@ -269,17 +278,22 @@ def parse_variable(self, parent, patch_nml=None): write_token = True # Check for escaped strings - if (v_values and isinstance(v_values[-1], str) and - isinstance(next_value, str) and not prior_ws_sep): + if ( + v_values + and isinstance(v_values[-1], str) + and isinstance(next_value, str) + and not prior_ws_sep + ): quote_char = self.prior_token[0] - v_values[-1] = quote_char.join([v_values[-1], - next_value]) + v_values[-1] = quote_char.join( + [v_values[-1], next_value] + ) else: append_value(v_values, next_value, v_idx, n_vals) # Exit for end of nml group (/, &, $) or null broadcast (=) - if self.token in ('/', '&', '$', '='): + if self.token in ("/", "&", "$", "="): break else: prior_ws_sep = ws_sep @@ -306,49 +320,55 @@ def parse_index(self): i_start = int(self.token) self.update_tokens() except ValueError: - if self.token in (',', ')'): - raise ValueError('{0} index cannot be empty.'.format(v_name)) - elif not self.token == ':': + if self.token in (",", ")"): + raise ValueError("{0} index cannot be empty.".format(v_name)) + elif not self.token == ":": raise # End index - if self.token == ':': + if self.token == ":": self.update_tokens() try: i_end = 1 + int(self.token) self.update_tokens() except ValueError: - if self.token == ':': - raise ValueError('{0} end index cannot be implicit ' - 'when using stride.'.format(v_name)) - elif self.token not in (',', ')'): + if self.token == ":": + raise ValueError( + "{0} end index cannot be implicit " + "when using stride.".format(v_name) + ) + elif self.token not in (",", ")"): raise - elif self.token in (',', ')'): + elif self.token in (",", ")"): # Replace index with single-index range if i_start: i_end = 1 + i_start # Stride index - if self.token == ':': + if self.token == ":": self.update_tokens() try: i_stride = int(self.token) except ValueError: - if self.token == ')': - raise ValueError('{0} stride index cannot be ' - 'implicit.'.format(v_name)) + if self.token == ")": + raise ValueError( + "{0} stride index cannot be " + "implicit.".format(v_name) + ) else: raise if i_stride == 0: - raise ValueError('{0} stride index cannot be zero.' - ''.format(v_name)) + raise ValueError( + "{0} stride index cannot be zero." "".format(v_name) + ) self.update_tokens() - if self.token not in (',', ')'): - raise ValueError('{0} index did not terminate ' - 'correctly.'.format(v_name)) + if self.token not in (",", ")"): + raise ValueError( + "{0} index did not terminate " "correctly.".format(v_name) + ) idx_triplet = (i_start, i_end, i_stride) v_indices.append((idx_triplet)) @@ -361,20 +381,20 @@ def parse_value(self, write_token=True): v_str = self.prior_token # Construct the complex string - if v_str == '(': + if v_str == "(": v_re = self.token self.update_tokens(write_token) - assert self.token == ',' + assert self.token == "," self.update_tokens(write_token) v_im = self.token self.update_tokens(write_token) - assert self.token == ')' + assert self.token == ")" self.update_tokens(write_token) - v_str = '({0}, {1})'.format(v_re, v_im) + v_str = "({0}, {1})".format(v_re, v_im) recast_funcs = [int, pyfloat, pycomplex, pybool, pystr] @@ -395,14 +415,14 @@ def update_tokens(self, write_token=True): self.pfile.write(self.token) # Commas between values are interpreted as whitespace - if self.token == ',': + if self.token == ",": ws_sep = True - while next_token in tuple(whitespace + '!'): + while next_token in tuple(whitespace + "!"): if self.pfile: - if next_token == '!': - while not next_token == '\n': + if next_token == "!": + while not next_token == "\n": self.pfile.write(next_token) next_token = next(self.tokens) self.pfile.write(next_token) @@ -417,6 +437,7 @@ def update_tokens(self, write_token=True): # Support functions + def append_value(v_values, next_value, v_idx=None, n_vals=1): """Update a list of parsed values with a new value.""" diff --git a/util/imageMorphometricParms_v2.py b/util/imageMorphometricParms_v2.py index 8d43df2..8bb0380 100644 --- a/util/imageMorphometricParms_v2.py +++ b/util/imageMorphometricParms_v2.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -''' +""" Calculates morphometric parameters for an image based on prevailing wind direction. Specify a dem on a square grid to load and averaging dimension @@ -21,18 +21,24 @@ imp_point = used to communicate with QGIS -''' +""" + import numpy as np import scipy.ndimage.interpolation as sc + # import matplotlib as plt def imagemorphparam_v2(dsm, dem, scale, mid, dtheta, feedback, imp_point): - numPixels = len(dsm[np.where(dsm != -9999)]) # too deal with irregular grids + numPixels = len( + dsm[np.where(dsm != -9999)] + ) # too deal with irregular grids build = dsm - dem - build[(build < 3.)] = 0. # building should be higher than 2 meter. Changed to 3 meters #784 + build[(build < 3.0)] = ( + 0.0 # building should be higher than 2 meter. Changed to 3 meters #784 + ) # new part (when did i write this?) buildvec = build[np.where(build > 0)] @@ -40,21 +46,21 @@ def imagemorphparam_v2(dsm, dem, scale, mid, dtheta, feedback, imp_point): zH_all = buildvec.mean() zHmax_all = buildvec.max() zH_sd_all = buildvec.std() - pai_all = (buildvec.size * 1.0) / numPixels #(build.size * 1.0) + pai_all = (buildvec.size * 1.0) / numPixels # (build.size * 1.0) else: zH_all = 0.0 zHmax_all = 0.0 zH_sd_all = 0.0 pai_all = 0.0 - fai = np.zeros((int(360./dtheta), 1)) - zH = np.zeros((int(360./dtheta), 1)) - zHmax = np.zeros((int(360./dtheta), 1)) - zH_sd = np.zeros((int(360./dtheta), 1)) - pai = np.zeros((int(360./dtheta), 1)) - deg = np.zeros((int(360./dtheta), 1)) - test = np.zeros((int(360./dtheta), 1)) - #%subset and center (moved inside loop 20230208) + fai = np.zeros((int(360.0 / dtheta), 1)) + zH = np.zeros((int(360.0 / dtheta), 1)) + zHmax = np.zeros((int(360.0 / dtheta), 1)) + zH_sd = np.zeros((int(360.0 / dtheta), 1)) + pai = np.zeros((int(360.0 / dtheta), 1)) + deg = np.zeros((int(360.0 / dtheta), 1)) + test = np.zeros((int(360.0 / dtheta), 1)) + # %subset and center (moved inside loop 20230208) # n = dsm.shape[0] # imid = np.floor((n/2.)) # if mid == 1: @@ -72,46 +78,54 @@ def imagemorphparam_v2(dsm, dem, scale, mid, dtheta, feedback, imp_point): j = int(0) for angle in np.arange(0, 360, dtheta): if imp_point == 1: - feedback.setProgress(int(angle/3.6)) + feedback.setProgress(int(angle / 3.6)) # Rotating buildings # d = sc.rotate(build, angle, order=0, reshape=False, mode='nearest') #old - a = sc.rotate(build, angle, order=0, reshape=True, mode='constant', cval=-99) - - #% convolve leading edge filter with domain + a = sc.rotate( + build, angle, order=0, reshape=True, mode="constant", cval=-99 + ) + + # % convolve leading edge filter with domain c = a * 0.0 n = c.shape[1] - imid = np.floor((n/2.)) + imid = np.floor((n / 2.0)) # filter for fai. Moved inside loop since size change if grid is irregular - filt1 = np.ones((n, 1)) * -1. + filt1 = np.ones((n, 1)) * -1.0 filt2 = np.ones((n, 1)) filt = np.array(np.hstack((filt1, filt2))).conj().T buildZero = np.copy(a) - buildZero[buildZero == -99] = 0 # remove -99 to avoid one 99 meter tall building wall + buildZero[buildZero == -99] = ( + 0 # remove -99 to avoid one 99 meter tall building wall + ) for i in np.arange(1, c.shape[0]): - c[int(i)-1, :] = np.sum((filt*buildZero[int(i)-1:i+1, :]), 0) + c[int(i) - 1, :] = np.sum( + (filt * buildZero[int(i) - 1 : i + 1, :]), 0 + ) - if mid == 1: # from center point + if mid == 1: # from center point ny = a.shape[0] - imidy = np.floor((ny/2.)) # the mid (NtoS) line of the grid - lineMid = a[0:int(imidy),int(imid)] - walltemp = c[0:int(imidy),int(imid)] - else: #whole grid - lineMid = a[:,int(imid)] # whole center line - walltemp = c[:,int(imid)] + imidy = np.floor((ny / 2.0)) # the mid (NtoS) line of the grid + lineMid = a[0 : int(imidy), int(imid)] + walltemp = c[0 : int(imidy), int(imid)] + else: # whole grid + lineMid = a[:, int(imid)] # whole center line + walltemp = c[:, int(imid)] bld = lineMid[np.where(lineMid > -99)] wall = walltemp[np.where(lineMid > -99)] - ly = bld.shape[0] #number of pixels to consider in NtoS - lx = 1 #!TODO should this consider full length (EtoW) of grid and if so, how? - + ly = bld.shape[0] # number of pixels to consider in NtoS + lx = 1 #!TODO should this consider full length (EtoW) of grid and if so, how? + wall = wall[np.where(wall > 2)] # wall vector - fai[j] = np.sum(wall)/((lx*ly)/scale) - bld = bld[np.where(bld > 2)] # building vector: change from 0 to 2 : 20150906 - pai[j] = np.float32(bld.shape[0]) / (lx*ly) + fai[j] = np.sum(wall) / ((lx * ly) / scale) + bld = bld[ + np.where(bld > 2) + ] # building vector: change from 0 to 2 : 20150906 + pai[j] = np.float32(bld.shape[0]) / (lx * ly) deg[j] = angle if np.float32(bld.shape[0]) == 0: zH[j] = 0 @@ -123,15 +137,27 @@ def imagemorphparam_v2(dsm, dem, scale, mid, dtheta, feedback, imp_point): zH_sd[j] = bld.std() # if angle == 0: - # test = wall + # test = wall test[j] = ly j += 1 fai_all = np.mean(fai) - immorphresult = {'fai': fai, 'pai': pai, 'zH': zH, 'deg': deg, 'zHmax': zHmax,'zH_sd': zH_sd, 'pai_all': pai_all, - 'zH_all': zH_all, 'zHmax_all': zHmax_all, 'zH_sd_all': zH_sd_all, 'fai_all': fai_all,'test': test} + immorphresult = { + "fai": fai, + "pai": pai, + "zH": zH, + "deg": deg, + "zHmax": zHmax, + "zH_sd": zH_sd, + "pai_all": pai_all, + "zH_all": zH_all, + "zHmax_all": zHmax_all, + "zH_sd_all": zH_sd_all, + "fai_all": fai_all, + "test": test, + } return immorphresult @@ -153,7 +179,7 @@ def imagemorphparam_v2(dsm, dem, scale, mid, dtheta, feedback, imp_point): # zH_all = 0 # zHmax_all = 0 # zH_sd_all = 0 -# pai_all = 0 +# pai_all = 0 # fai = np.zeros((int(360./dtheta), 1)) # zH = np.zeros((int(360./dtheta), 1)) diff --git a/util/landCoverFractions_v2.py b/util/landCoverFractions_v2.py index f41132c..9c9c85f 100644 --- a/util/landCoverFractions_v2.py +++ b/util/landCoverFractions_v2.py @@ -1,17 +1,17 @@ # -*- coding: utf-8 -*- -#%calculates morphometric parameters for an image based on prevailing wind -#%direction. Specify a dem on a square grid to load and averaging dimension -#% -#%Date: 26 February 2004 -#%Author: -#% Offerle, B. -#% Geovetarcentrum -#% Goteborg University, Sweden -#% Modified by Fredrik Lindberg 2010-01-09, fredrik.lindberg@kcl.ac.uk (fredrikl@gvc.gu.se) -#% Translated to Python 20150108 +# %calculates morphometric parameters for an image based on prevailing wind +# %direction. Specify a dem on a square grid to load and averaging dimension +# % +# %Date: 26 February 2004 +# %Author: +# % Offerle, B. +# % Geovetarcentrum +# % Goteborg University, Sweden +# % Modified by Fredrik Lindberg 2010-01-09, fredrik.lindberg@kcl.ac.uk (fredrikl@gvc.gu.se) +# % Translated to Python 20150108 # Extended to be albe to calculate on irregular grids, Fredrik 20230207 # Added iter to caclulate ión either 7 or 9 lc-classes -#%-------------------------------------------------------------------------- +# %-------------------------------------------------------------------------- import numpy as np import scipy.ndimage.interpolation as sc @@ -25,55 +25,63 @@ def landcover_v2(lc_grid, mid, dtheta, feedback, imp_point, iter): lc_gridvec = lc_grid[np.where(lc_grid == i + 1)] if lc_gridvec.size > 0: # lc_frac_all[0, i] = round((lc_gridvec.size * 1.0) / (lc_grid.size * 1.0), 3) - lc_frac_all[0, i] = round((lc_gridvec.size * 1.0) / (lc_grid.size - (lc_grid == 0).sum()),3) # ignoring NoData (0) pixels - + lc_frac_all[0, i] = round( + (lc_gridvec.size * 1.0) + / (lc_grid.size - (lc_grid == 0).sum()), + 3, + ) # ignoring NoData (0) pixels # Anisotropic (Adjusted for irregular grids) - lc_frac = np.zeros((int(360./dtheta), iter)) - deg = np.zeros((int(360./dtheta), 1)) + lc_frac = np.zeros((int(360.0 / dtheta), iter)) + deg = np.zeros((int(360.0 / dtheta), 1)) - #n = lc_grid.shape[0] - #imid = np.floor((n/2.)) + # n = lc_grid.shape[0] + # imid = np.floor((n/2.)) # if mid == 1: - # dY = np.int16(np.arange(np.dot(1, imid))) # the half length of the grid (y) - #else: #moved inside loop as it varies on an irregular grid - #dY = np.int16(np.arange(np.dot(1, n))) # the whole length of the grid (y) + # dY = np.int16(np.arange(np.dot(1, imid))) # the half length of the grid (y) + # else: #moved inside loop as it varies on an irregular grid + # dY = np.int16(np.arange(np.dot(1, n))) # the whole length of the grid (y) - # dX = np.int16(np.arange(imid, imid+1)) - #lx = dX.shape[0] - #ly = dY.shape[0] + # dX = np.int16(np.arange(imid, imid+1)) + # lx = dX.shape[0] + # ly = dY.shape[0] j = int(0) for angle in np.arange(0, 360, dtheta): if imp_point == 1: - feedback.setProgress(int(angle/3.6)) + feedback.setProgress(int(angle / 3.6)) - #d = sc.rotate(lc_grid, angle, order=0, reshape=False, mode='nearest') #old - d = sc.rotate(lc_grid, angle, order=0, reshape=True, mode='constant', cval=-99) + # d = sc.rotate(lc_grid, angle, order=0, reshape=False, mode='nearest') #old + d = sc.rotate( + lc_grid, angle, order=0, reshape=True, mode="constant", cval=-99 + ) n = d.shape[1] - imid = np.floor((n/2.)) # the mid (NtoS) line of the grid - if mid == 1: # from center point + imid = np.floor((n / 2.0)) # the mid (NtoS) line of the grid + if mid == 1: # from center point ny = d.shape[0] - imidy = np.floor((ny/2.)) # the mid (NtoS) line of the grid - lineMid = d[0:int(imidy),int(imid)] - else: #whole grid - lineMid = d[:,int(imid)] # whole center line - bld = lineMid[np.where(lineMid > 0)] # line within grid only + imidy = np.floor((ny / 2.0)) # the mid (NtoS) line of the grid + lineMid = d[0 : int(imidy), int(imid)] + else: # whole grid + lineMid = d[:, int(imid)] # whole center line + bld = lineMid[np.where(lineMid > 0)] # line within grid only - #b = np.round(((lc_grid.max()-lc_grid.min())/d.max())*d+lc_grid.min(), 0) #not needed anymore - #bld = b[dY, dX] # lc array - ly = bld.shape[0] #number of pixels to consider in NtoS - lx = 1 #!TODO should this consider full length (EtoW) of grid and if so, how? + # b = np.round(((lc_grid.max()-lc_grid.min())/d.max())*d+lc_grid.min(), 0) #not needed anymore + # bld = b[dY, dX] # lc array + ly = bld.shape[0] # number of pixels to consider in NtoS + lx = 1 #!TODO should this consider full length (EtoW) of grid and if so, how? for i in range(0, iter): bldtemp = bld[np.where(bld == i + 1)] # lc vector - lc_frac[j, i] = np.float32(bldtemp.shape[0]) / (lx*ly) + lc_frac[j, i] = np.float32(bldtemp.shape[0]) / (lx * ly) deg[j] = angle j += 1 - landcoverresult = {'lc_frac_all': lc_frac_all, 'lc_frac': lc_frac, 'deg': deg} + landcoverresult = { + "lc_frac_all": lc_frac_all, + "lc_frac": lc_frac, + "deg": deg, + } return landcoverresult - diff --git a/util/misc.py b/util/misc.py index 930fe75..6d9783b 100644 --- a/util/misc.py +++ b/util/misc.py @@ -1,29 +1,30 @@ -__author__ = 'xlinfr' +__author__ = "xlinfr" import numpy as np from osgeo import gdal, osr from osgeo.gdalconst import GDT_Float32 from pandas import read_csv, to_datetime -import os +import os import re + # Slope and aspect used in SEBE and Wall aspect def get_ders(dsm, scale): # dem,_,_=read_dem_grid(dem_file) - dx = 1/scale + dx = 1 / scale # dx=0.5 fy, fx = np.gradient(dsm, dx, dx) - asp, grad = cart2pol(fy, fx, 'rad') + asp, grad = cart2pol(fy, fx, "rad") slope = np.arctan(grad) asp = asp * -1 asp = asp + (asp < 0) * (np.pi * 2) return slope, asp -def cart2pol(x, y, units='deg'): +def cart2pol(x, y, units="deg"): radius = np.sqrt(x**2 + y**2) theta = np.arctan2(y, x) - if units in ['deg', 'degs']: + if units in ["deg", "degs"]: theta = theta * 180 / np.pi return theta, radius @@ -32,7 +33,9 @@ def saveraster(gdal_data, filename, raster): rows = gdal_data.RasterYSize cols = gdal_data.RasterXSize - outDs = gdal.GetDriverByName("GTiff").Create(filename, cols, rows, int(1), GDT_Float32) + outDs = gdal.GetDriverByName("GTiff").Create( + filename, cols, rows, int(1), GDT_Float32 + ) outBand = outDs.GetRasterBand(1) # write the data @@ -45,11 +48,14 @@ def saveraster(gdal_data, filename, raster): outDs.SetGeoTransform(gdal_data.GetGeoTransform()) outDs.SetProjection(gdal_data.GetProjection()) + def saverasternd(gdal_data, filename, raster): rows = gdal_data.RasterYSize cols = gdal_data.RasterXSize - outDs = gdal.GetDriverByName("GTiff").Create(filename, cols, rows, int(1), GDT_Float32) + outDs = gdal.GetDriverByName("GTiff").Create( + filename, cols, rows, int(1), GDT_Float32 + ) outBand = outDs.GetRasterBand(1) # write the data @@ -62,6 +68,7 @@ def saverasternd(gdal_data, filename, raster): outDs.SetGeoTransform(gdal_data.GetGeoTransform()) outDs.SetProjection(gdal_data.GetProjection()) + def xy2latlon(crsWtkIn, x, y): old_cs = osr.SpatialReference() old_cs.ImportFromWkt(crsWtkIn) @@ -86,12 +93,12 @@ def xy2latlon(crsWtkIn, x, y): lonlat = transform.TransformPoint(x, y) gdalver = float(gdal.__version__[0]) - if gdalver >= 3.: - lon = lonlat[1] #changed to gdal 3 - lat = lonlat[0] #changed to gdal 3 + if gdalver >= 3.0: + lon = lonlat[1] # changed to gdal 3 + lat = lonlat[0] # changed to gdal 3 else: - lon = lonlat[0] #changed to gdal 2 - lat = lonlat[1] #changed to gdal 2 + lon = lonlat[0] # changed to gdal 2 + lat = lonlat[1] # changed to gdal 2 return lat, lon @@ -118,7 +125,7 @@ def createTSlist(): total_minutes = int(offset.total_seconds() // 60) hours, minutes = divmod(abs(total_minutes), 60) - sign = '+' if total_minutes >= 0 else '-' + sign = "+" if total_minutes >= 0 else "-" offset_str = f"UTC{sign}{hours:02d}:{minutes:02d}" offset_hours = total_minutes / 60 @@ -126,7 +133,7 @@ def createTSlist(): if offset_str not in timezones_by_offset: timezones_by_offset[offset_str] = { "utc_offset": offset_hours, - "timezones": [] + "timezones": [], } # Add up to 3 example zones per offset @@ -141,7 +148,7 @@ def createTSlist(): { "utc_offset_str": offset, "utc_offset": data["utc_offset"], - "timezones": data["timezones"] + "timezones": data["timezones"], } for offset, data in timezones_by_offset.items() ] @@ -158,7 +165,7 @@ def createTSlist(): # # Get the current time in naive UTC # #now = datetime.utcnow() # now = datetime.now(timezone.utc) - + # # Dictionary to store timezones grouped by UTC offset # timezones_by_offset = {} @@ -198,9 +205,10 @@ def createTSlist(): # return sorted_timezones, timezones_by_offset + def get_resolution_from_file(folder_path): - suews_pattern = re.compile(r'.*_(\d{4})_SUEWS') + suews_pattern = re.compile(r".*_(\d{4})_SUEWS") # Step 1: Find the first matching SUEWS file suews_file = None @@ -213,11 +221,13 @@ def get_resolution_from_file(folder_path): df_suews = read_csv(suews_file, delim_whitespace=True) # Combine Year, DOY, Hour, Min into a datetime index - df_suews['Datetime'] = to_datetime( - df_suews[['Year', 'DOY', 'Hour', 'Min']].astype(str).agg('-'.join, axis=1), - format='%Y-%j-%H-%M' + df_suews["Datetime"] = to_datetime( + df_suews[["Year", "DOY", "Hour", "Min"]] + .astype(str) + .agg("-".join, axis=1), + format="%Y-%j-%H-%M", ) - df_suews.set_index('Datetime', inplace=True) + df_suews.set_index("Datetime", inplace=True) # Step 3: Compute resolution (in seconds) time1 = df_suews.index[1] @@ -226,23 +236,36 @@ def get_resolution_from_file(folder_path): return input_resolution -def SUEWS_txt_to_df( suews_output_path): - df_output_suews = read_csv(suews_output_path, delim_whitespace = True) - df_output_suews['Datetime'] = to_datetime(df_output_suews[['Year', 'DOY', 'Hour', 'Min']].astype(str).agg('-'.join, axis=1), format='%Y-%j-%H-%M') - df_output_suews.set_index('Datetime', inplace=True) + +def SUEWS_txt_to_df(suews_output_path): + df_output_suews = read_csv(suews_output_path, delim_whitespace=True) + df_output_suews["Datetime"] = to_datetime( + df_output_suews[["Year", "DOY", "Hour", "Min"]] + .astype(str) + .agg("-".join, axis=1), + format="%Y-%j-%H-%M", + ) + df_output_suews.set_index("Datetime", inplace=True) return df_output_suews + def SUEWS_met_txt_to_df(suews_met_path): - df_met_forcing = read_csv(suews_met_path, delim_whitespace = True) - df_met_forcing['Datetime'] = to_datetime(df_met_forcing[['iy', 'id', 'it', 'imin']].astype(str).agg('-'.join, axis=1), format='%Y-%j-%H-%M') - df_met_forcing.set_index('Datetime', inplace=True) - + df_met_forcing = read_csv(suews_met_path, delim_whitespace=True) + df_met_forcing["Datetime"] = to_datetime( + df_met_forcing[["iy", "id", "it", "imin"]] + .astype(str) + .agg("-".join, axis=1), + format="%Y-%j-%H-%M", + ) + df_met_forcing.set_index("Datetime", inplace=True) + return df_met_forcing + def extract_suews_years(folder_path): # Match: anything + underscore + 4-digit year + _SUEWS - suews_pattern = re.compile(r'.*_(\d{4})_SUEWS') + suews_pattern = re.compile(r".*_(\d{4})_SUEWS") years = set() for filename in os.listdir(folder_path): @@ -251,13 +274,14 @@ def extract_suews_years(folder_path): years.add(match.group(1)) return sorted(years) - + + def xy2latlon_fromraster(crsWtkIn, gdal_dsm): # Get latlon from grid coordinate system old_cs = osr.SpatialReference() # dsm_ref = dsmlayer.crs().toWkt() old_cs.ImportFromWkt(crsWtkIn) - + wgs84_wkt = """ GEOGCS["WGS 84", DATUM["WGS_1984", @@ -269,25 +293,27 @@ def xy2latlon_fromraster(crsWtkIn, gdal_dsm): UNIT["degree",0.01745329251994328, AUTHORITY["EPSG","9122"]], AUTHORITY["EPSG","4326"]]""" - + new_cs = osr.SpatialReference() new_cs.ImportFromWkt(wgs84_wkt) - + transform = osr.CoordinateTransformation(old_cs, new_cs) widthx = gdal_dsm.RasterXSize heightx = gdal_dsm.RasterYSize geotransform = gdal_dsm.GetGeoTransform() minx = geotransform[0] - miny = geotransform[3] + widthx * geotransform[4] + heightx * geotransform[5] - + miny = ( + geotransform[3] + widthx * geotransform[4] + heightx * geotransform[5] + ) + lonlat = transform.TransformPoint(minx, miny) gdalver = float(gdal.__version__[0]) - if gdalver == 3.: - lon = lonlat[1] #changed to gdal 3 - lat = lonlat[0] #changed to gdal 3 + if gdalver == 3.0: + lon = lonlat[1] # changed to gdal 3 + lat = lonlat[0] # changed to gdal 3 else: - lon = lonlat[0] #changed to gdal 2 - lat = lonlat[1] #changed to gdal 2 + lon = lonlat[0] # changed to gdal 2 + lat = lonlat[1] # changed to gdal 2 scale = 1 / geotransform[1] - - return lat, lon, scale, minx, miny \ No newline at end of file + + return lat, lon, scale, minx, miny diff --git a/util/ncWMSConnector.py b/util/ncWMSConnector.py index 99cd421..3ebfffd 100644 --- a/util/ncWMSConnector.py +++ b/util/ncWMSConnector.py @@ -5,6 +5,7 @@ from datetime import timedelta as td import tempfile import shutil + try: import pandas as pd import numpy as np @@ -16,86 +17,146 @@ from collections import OrderedDict import os + + class InvalidRelativeBbox(ValueError): pass + + class InvalidTimeWindow(ValueError): pass + class NCWMS_Connector(object): def __init__(self): - self.vars = ['Tair', 'Wind', 'LWdown', 'PSurf', 'Qair', 'Rainf', 'Snowf', 'SWdown'] - self.start_date = dt(1979, 0o1, 0o1, 00, 00, 00) # The first data point available in the dataset on the server - self.end_date = dt(2015, 12, 31, 21, 00, 00) # The final data point available in the dataset on the server + self.vars = [ + "Tair", + "Wind", + "LWdown", + "PSurf", + "Qair", + "Rainf", + "Snowf", + "SWdown", + ] + self.start_date = dt( + 1979, 0o1, 0o1, 00, 00, 00 + ) # The first data point available in the dataset on the server + self.end_date = dt( + 2015, 12, 31, 21, 00, 00 + ) # The final data point available in the dataset on the server self.time_res = 3600 * 3 # Time resolution of data in seconds self.request_length = 200 # Number of days of data to request at a time from server (helps manage load on server and produce a progress bar) self.request_params = {} # Holds a dict of request parameters - self.results = OrderedDict() # Stores a list of downloaded netCDF files for different time subsets, with start datetime as key + self.results = ( + OrderedDict() + ) # Stores a list of downloaded netCDF files for different time subsets, with start datetime as key self.killed = False # Aborts execution if needed def kill(self): self.killed = True - def check_bbox(self,lower_left_lat, lower_left_lon, upper_right_lat, upper_right_lon): - ''' + def check_bbox( + self, lower_left_lat, lower_left_lon, upper_right_lat, upper_right_lon + ): + """ Check WGS84 bounding box is valid :param lower_left_lat: :param lower_left_lon: :param upper_right_lat: :param upper_right_lon: :return: True or exception - ''' + """ if lower_left_lat > upper_right_lat: - raise InvalidRelativeBbox('Lower left latitude of bounding box cannot be greater than upper right latitude') + raise InvalidRelativeBbox( + "Lower left latitude of bounding box cannot be greater than upper right latitude" + ) if lower_left_lon > upper_right_lon: - raise InvalidRelativeBbox('Lower left longitude of bounding box cannot be greater than upper right longitude') + raise InvalidRelativeBbox( + "Lower left longitude of bounding box cannot be greater than upper right longitude" + ) return True - def check_times(self,start_date, end_date): - ''' + def check_times(self, start_date, end_date): + """ Check requested start and end dates are valid :param start_date: :param end_date: :return: - ''' + """ if type(start_date) is not dt: - raise TypeError('Start time is of type %s, but must be datetime.datetime' % (type(start_date),)) + raise TypeError( + "Start time is of type %s, but must be datetime.datetime" + % (type(start_date),) + ) if type(end_date) is not dt: - raise TypeError('End time is of type %s, but must be datetime.datetime' % (type(start_date),)) + raise TypeError( + "End time is of type %s, but must be datetime.datetime" + % (type(start_date),) + ) if start_date > end_date: - raise InvalidTimeWindow('Requested end date (%s) must be greater than or equal to start date (%s)' % (end_date.strftime('%Y-%m-%d %H:%M:%S'), start_date.strftime('%Y-%m-%d %H:%M:%S'))) + raise InvalidTimeWindow( + "Requested end date (%s) must be greater than or equal to start date (%s)" + % ( + end_date.strftime("%Y-%m-%d %H:%M:%S"), + start_date.strftime("%Y-%m-%d %H:%M:%S"), + ) + ) # Check this is allowed given scope of data if (start_date < self.start_date) or (start_date > self.end_date): - raise InvalidTimeWindow('Requested start date (%s) must be between %s and %s'%(end_date.strftime('%Y-%m-%d %H:%M:%S'), - self.start_date.strftime('%Y-%m-%d %H:%M:%S'), - self.end_date.strftime('%Y-%m-%d %H:%M:%S'))) - - if (end_date > self.end_date) or ( end_date < self.start_date): - raise InvalidTimeWindow('Requested end date (%s) must be between %s and %s'%(end_date.strftime('%Y-%m-%d %H:%M:%S'), - self.start_date.strftime('%Y-%m-%d %H:%M:%S'), - self.end_date.strftime('%Y-%m-%d %H:%M:%S'))) + raise InvalidTimeWindow( + "Requested start date (%s) must be between %s and %s" + % ( + end_date.strftime("%Y-%m-%d %H:%M:%S"), + self.start_date.strftime("%Y-%m-%d %H:%M:%S"), + self.end_date.strftime("%Y-%m-%d %H:%M:%S"), + ) + ) + + if (end_date > self.end_date) or (end_date < self.start_date): + raise InvalidTimeWindow( + "Requested end date (%s) must be between %s and %s" + % ( + end_date.strftime("%Y-%m-%d %H:%M:%S"), + self.start_date.strftime("%Y-%m-%d %H:%M:%S"), + self.end_date.strftime("%Y-%m-%d %H:%M:%S"), + ) + ) return True def check_vars(self, var_list): - ''' + """ Ensure requested variable names are valid :param var_list: :return: - ''' + """ for v in var_list: if v not in self.vars: - raise ValueError('Variable %s is not one of those available in data'%(v, )) + raise ValueError( + "Variable %s is not one of those available in data" % (v,) + ) return True - def get_data(self, start_date, end_date, variables, lowerleft_lat, lowerleft_lon, upperright_lat, upperright_lon, update=None): - ''' + def get_data( + self, + start_date, + end_date, + variables, + lowerleft_lat, + lowerleft_lon, + upperright_lat, + upperright_lon, + update=None, + ): + """ Retrieve data from ncWMS :param start_date: datetime.datetime: Earliest requested date :param end_date: datetime.datetime: Latest requested date @@ -106,144 +167,204 @@ def get_data(self, start_date, end_date, variables, lowerleft_lat, lowerleft_lon :param upperright_lon: Longitude (WGS84) degrees E, upper right of bounding box :param update: A QT progressBar object (optional) :return: - ''' + """ # Validate input params - self.check_bbox(lower_left_lat = lowerleft_lat, lower_left_lon = lowerleft_lon, upper_right_lat=upperright_lat, upper_right_lon=upperright_lon) + self.check_bbox( + lower_left_lat=lowerleft_lat, + lower_left_lon=lowerleft_lon, + upper_right_lat=upperright_lat, + upper_right_lon=upperright_lon, + ) self.check_vars(variables) self.check_times(start_date, end_date) - self.request_params = {'vars':variables, 'start_date':start_date, 'end_date':end_date, 'bbox': [lowerleft_lat, lowerleft_lon, upperright_lat, upperright_lon]} + self.request_params = { + "vars": variables, + "start_date": start_date, + "end_date": end_date, + "bbox": [ + lowerleft_lat, + lowerleft_lon, + upperright_lat, + upperright_lon, + ], + } # start_dates = pd.date_range(start_date, end_date, freq='%dD' % (self.request_length,)).to_datetime() # to_datetime() not needed... - start_dates = pd.date_range(start_date, end_date, freq='%dD' % (self.request_length,)) + start_dates = pd.date_range( + start_date, end_date, freq="%dD" % (self.request_length,) + ) # Create queue of retrievals, and safeguard against over-running dataset end date for s in range(0, len(start_dates)): if self.killed: break - if s == len(start_dates)-1: - end_date_candidate = end_date + td(seconds = 3600 * 24 -1) + if s == len(start_dates) - 1: + end_date_candidate = end_date + td(seconds=3600 * 24 - 1) final_date = True else: - end_date_candidate = start_dates[s+1]- td(seconds=self.time_res) + end_date_candidate = start_dates[s + 1] - td( + seconds=self.time_res + ) final_date = False if end_date_candidate > self.end_date: final_date = True end_date_candidate = self.end_date - self.results[start_dates[s]] = self.retrieve(start_dates[s], end_date_candidate) # Get data from start date to next start date minus time resolution + self.results[start_dates[s]] = self.retrieve( + start_dates[s], end_date_candidate + ) # Get data from start date to next start date minus time resolution if update is not None: - update.emit({'progress':100*float(s)/float(len(start_dates)), 'message':' (%s to %s)'%(start_dates[s].strftime('%Y-%m-%d'), end_date_candidate.strftime('%Y-%m-%d'))}) + update.emit( + { + "progress": 100 * float(s) / float(len(start_dates)), + "message": ( + " (%s to %s)" + % ( + start_dates[s].strftime("%Y-%m-%d"), + end_date_candidate.strftime("%Y-%m-%d"), + ) + ), + } + ) if final_date: break self.convert_to_nc3() - update.emit({'progress':100, 'message':' Cleaning up...'}) + update.emit({"progress": 100, "message": " Cleaning up..."}) def convert_to_nc3(self): - ''' + """ Process dict of downloaded netCDF files into a single averaged time series (also netCDF) :return: - ''' + """ # Convert each file to netcdf4_classic so it can be used with MFDataset for file_date in list(self.results.keys()): - tmp = tempfile.mkstemp(suffix='.nc') - new_data = nc4.Dataset(tmp, 'w', clobber=True, format='NETCDF3_CLASSIC') + tmp = tempfile.mkstemp(suffix=".nc") + new_data = nc4.Dataset( + tmp, "w", clobber=True, format="NETCDF3_CLASSIC" + ) extant = nc4.Dataset(self.results[file_date]) # from https://gist.github.com/guziy/8543562 for dname, the_dim in extant.dimensions.items(): - new_data.createDimension(dname, len(the_dim) if not the_dim.isunlimited() else None) + new_data.createDimension( + dname, len(the_dim) if not the_dim.isunlimited() else None + ) # Copy variables from first file for v_name, varin in extant.variables.items(): - if varin.datatype == 'int64': - dtype = 'f' # Use a float instead of a long integer because netcdf3 classic doesn't allow + if varin.datatype == "int64": + dtype = "f" # Use a float instead of a long integer because netcdf3 classic doesn't allow else: dtype = varin.datatype - outVar = new_data.createVariable(v_name, dtype, varin.dimensions) + outVar = new_data.createVariable( + v_name, dtype, varin.dimensions + ) # Copy variable attributes - outVar.setncatts({k: varin.getncattr(k) for k in varin.ncattrs()}) + outVar.setncatts( + {k: varin.getncattr(k) for k in varin.ncattrs()} + ) outVar[:] = varin[:] new_data.close() extant.close() - os.remove(self.results[file_date]) # Delete original NC file - self.results[file_date] = tmp # Update dict with newer one - + os.remove(self.results[file_date]) # Delete original NC file + self.results[file_date] = tmp # Update dict with newer one def resample_by_method(self, resampled, method): - ''' + """ Use a specific method to calculate aggregate statistics on a resampled pandas dataframe :param resampled: output of ps.series.resample() :param method: String naming method to use :return: - ''' - if method == 'mean': + """ + if method == "mean": return resampled.mean() - if method == 'median': + if method == "median": return resampled.median() - if method == 'sum': + if method == "sum": return resampled.sum() - if method == 'min': + if method == "min": return resampled.min() - if method == 'max': + if method == "max": return resampled.max() - raise ValueError('Resampling statistic %s not recognised.'%(method,)) + raise ValueError("Resampling statistic %s not recognised." % (method,)) def average_data(self, period, method): - ''' + """ Produces a NetCDF file with temporally averaged data for all variables, preserving the spatial aspects :param period: Desired averaging period in seconds, or None to do no averaging :param method: How the mean should be found, either 'mean', 'sum', 'median', 'min' or 'max' :return: - ''' + """ - combined_data = nc4.MFDataset(list(self.results.values()), aggdim='time') + combined_data = nc4.MFDataset( + list(self.results.values()), aggdim="time" + ) # Create new netCDF file that'll contain averaged/combined data and delete the individual files # from https://gist.github.com/guziy/8543562 # Go round the variables and average them - tmp = tempfile.mkstemp(suffix='.nc') - new_data = nc4.Dataset(tmp, 'w', clobber=True, format='NETCDF3_CLASSIC') - times = combined_data.variables['time'] + tmp = tempfile.mkstemp(suffix=".nc") + new_data = nc4.Dataset( + tmp, "w", clobber=True, format="NETCDF3_CLASSIC" + ) + times = combined_data.variables["time"] time_bins = nc4.num2date(times[:], units=times.units) # Workaround: The server currently has a slightly wobble in time bins, which we know to be precise # re-do them. The first one tends to be correct, and drift sets in later. - time_bins = pd.date_range(time_bins[0], periods=len(time_bins), freq=str(self.time_res)+'s') - dim = combined_data.variables[self.vars[0]][:].shape # Assume all requested variables have the same shape + time_bins = pd.date_range( + time_bins[0], periods=len(time_bins), freq=str(self.time_res) + "s" + ) + dim = combined_data.variables[self.vars[0]][ + : + ].shape # Assume all requested variables have the same shape if period is None: new_time_bins = time_bins else: - new_time_bins = self.resample_by_method(pd.Series(index=time_bins, data=0).resample('%ds'%(period,), label='right'), method).index + new_time_bins = self.resample_by_method( + pd.Series(index=time_bins, data=0).resample( + "%ds" % (period,), label="right" + ), + method, + ).index new_dim = [len(new_time_bins), dim[1], dim[2]] # Duplicate all non-time dimensions from original file, and add new time dimension for dname, the_dim in combined_data.dimensions.items(): - if dname == 'time': + if dname == "time": t = new_data.createDimension(dname, len(new_time_bins)) else: - new_data.createDimension(dname, len(the_dim) if not the_dim.isunlimited() else None) + new_data.createDimension( + dname, len(the_dim) if not the_dim.isunlimited() else None + ) # Copy variables from first file for v_name, varin in combined_data.variables.items(): - outVar = new_data.createVariable(v_name, varin.dtype, varin.dimensions) + outVar = new_data.createVariable( + v_name, varin.dtype, varin.dimensions + ) try: - if v_name == 'time': - outVar.units = varin.units.replace('seconds', 'hours') # Replace seconds with hours (if seconds) to save data + if v_name == "time": + outVar.units = varin.units.replace( + "seconds", "hours" + ) # Replace seconds with hours (if seconds) to save data else: outVar.units = varin.units except: pass try: - outVar.setncatts({k: varin.getncattr(k) for k in varin.ncattrs()}) + outVar.setncatts( + {k: varin.getncattr(k) for k in varin.ncattrs()} + ) except: pass @@ -252,50 +373,91 @@ def average_data(self, period, method): if v_name in self.vars: # TODO: Make this efficient for i in range(0, combined_data.variables[v_name][:].shape[1]): - for j in range(0, combined_data.variables[v_name][:].shape[2]): - p = pd.Series(index=time_bins, data=combined_data.variables[v_name][:,i,j]) + for j in range( + 0, combined_data.variables[v_name][:].shape[2] + ): + p = pd.Series( + index=time_bins, + data=combined_data.variables[v_name][:, i, j], + ) if period is None: rs = p else: - rs = self.resample_by_method(p.resample('%ds'%(period,), label='right'), method) + rs = self.resample_by_method( + p.resample("%ds" % (period,), label="right"), + method, + ) if (i == 0) and (j == 0): new_array = np.zeros(new_dim) - new_array[:,i,j] = rs.values + new_array[:, i, j] = rs.values outVar[:] = new_array else: - if v_name == 'time': + if v_name == "time": # Populate with new time bins - timestamps = nc4.date2num(list(new_time_bins), varin.units.replace('seconds', 'hours')) # Replace seconds with hours to save data + timestamps = nc4.date2num( + list(new_time_bins), + varin.units.replace("seconds", "hours"), + ) # Replace seconds with hours to save data outVar[:] = timestamps - if v_name in ['lat', 'lon']: + if v_name in ["lat", "lon"]: outVar[:] = combined_data.variables[v_name][:] new_data.close() combined_data.close() - [os.remove(v) for v in list(self.results.values())] # Remove individual files as no longer needed - return tmp # Return path to combined file + [ + os.remove(v) for v in list(self.results.values()) + ] # Remove individual files as no longer needed + return tmp # Return path to combined file def retrieve(self, start_period, end_period): - ''' + """ Performs retrieval of NetCDF file from server in chunks defined by start_period and end_period :return: - ''' - baseURL = 'http://data.urban-climate.net:8080/umep/download' - parms = {'BBOX':'%f,%f,%f,%f'%(float(self.request_params['bbox'][1]), float(self.request_params['bbox'][0]), float(self.request_params['bbox'][3]), float(self.request_params['bbox'][2])), - 'DATASET': 'watch', - 'VARIABLES':','.join(self.request_params['vars']), - 'TIME':'%s/%s'%(start_period.strftime('%Y-%m-%dT%H:%M:%S'),end_period.strftime('%Y-%m-%dT%H:%M:%S'))} + """ + baseURL = "http://data.urban-climate.net:8080/umep/download" + parms = { + "BBOX": ( + "%f,%f,%f,%f" + % ( + float(self.request_params["bbox"][1]), + float(self.request_params["bbox"][0]), + float(self.request_params["bbox"][3]), + float(self.request_params["bbox"][2]), + ) + ), + "DATASET": "watch", + "VARIABLES": ",".join(self.request_params["vars"]), + "TIME": ( + "%s/%s" + % ( + start_period.strftime("%Y-%m-%dT%H:%M:%S"), + end_period.strftime("%Y-%m-%dT%H:%M:%S"), + ) + ), + } try: - dataOut = tempfile.mkstemp('.nc') + dataOut = tempfile.mkstemp(".nc") except Exception as e: os.remove(dataOut) - raise Exception('Problem creating temporary file to store raster data: '+ str(e)) + raise Exception( + "Problem creating temporary file to store raster data: " + + str(e) + ) # TODO: Work out if the response is an XML error - resp = requests.get(baseURL, params=parms, auth=HTTPDigestAuth("umep-user", "pUEmw5BbVdzfu3dz"), stream=True, timeout=15) + resp = requests.get( + baseURL, + params=parms, + auth=HTTPDigestAuth("umep-user", "pUEmw5BbVdzfu3dz"), + stream=True, + timeout=15, + ) if resp.status_code != 200: - raise Exception('Error connecting to server. Got HTTP response code %d'%(resp.status_code,)) - with open(dataOut, 'wb') as out: + raise Exception( + "Error connecting to server. Got HTTP response code %d" + % (resp.status_code,) + ) + with open(dataOut, "wb") as out: shutil.copyfileobj(resp.raw, out) del resp return dataOut diff --git a/util/shadowingfunctions.py b/util/shadowingfunctions.py index e501c9d..dde117a 100644 --- a/util/shadowingfunctions.py +++ b/util/shadowingfunctions.py @@ -3,86 +3,109 @@ import numpy as np from math import radians import matplotlib.pylab as plt + # from numba import jit -def shadowingfunctionglobalradiation(a, azimuth, altitude, scale, feedback, forsvf): - #%This m.file calculates shadows on a DEM - #% conversion - degrees = np.pi/180. +def shadowingfunctionglobalradiation( + a, azimuth, altitude, scale, feedback, forsvf +): + + # %This m.file calculates shadows on a DEM + # % conversion + degrees = np.pi / 180.0 # if azimuth == 0.0: - # azimuth = 0.000000000001 + # azimuth = 0.000000000001 azimuth = np.dot(azimuth, degrees) altitude = np.dot(altitude, degrees) - #% measure the size of the image + # % measure the size of the image sizex = a.shape[0] sizey = a.shape[1] if forsvf == 0: barstep = np.max([sizex, sizey]) - total = 100. / barstep #dlg.progressBar.setRange(0, barstep) - #% initialise parameters + total = 100.0 / barstep # dlg.progressBar.setRange(0, barstep) + # % initialise parameters f = a - dx = 0. - dy = 0. - dz = 0. + dx = 0.0 + dy = 0.0 + dz = 0.0 temp = np.zeros((sizex, sizey)) - index = 1. - #% other loop parameters + index = 1.0 + # % other loop parameters amaxvalue = a.max() - pibyfour = np.pi/4. - threetimespibyfour = 3.*pibyfour - fivetimespibyfour = 5.*pibyfour - seventimespibyfour = 7.*pibyfour + pibyfour = np.pi / 4.0 + threetimespibyfour = 3.0 * pibyfour + fivetimespibyfour = 5.0 * pibyfour + seventimespibyfour = 7.0 * pibyfour sinazimuth = np.sin(azimuth) cosazimuth = np.cos(azimuth) tanazimuth = np.tan(azimuth) signsinazimuth = np.sign(sinazimuth) signcosazimuth = np.sign(cosazimuth) - dssin = np.abs((1./sinazimuth)) - dscos = np.abs((1./cosazimuth)) + dssin = np.abs((1.0 / sinazimuth)) + dscos = np.abs((1.0 / cosazimuth)) tanaltitudebyscale = np.tan(altitude) / scale - #% main loop - while (amaxvalue >= dz and np.abs(dx) < sizex and np.abs(dy) < sizey): + # % main loop + while amaxvalue >= dz and np.abs(dx) < sizex and np.abs(dy) < sizey: if forsvf == 0: feedback.setProgress(int(index * total)) # dlg.progressBar.setValue(index) - #while np.logical_and(np.logical_and(amaxvalue >= dz, np.abs(dx) <= sizex), np.abs(dy) <= sizey):(np.logical_and(amaxvalue >= dz, np.abs(dx) <= sizex), np.abs(dy) <= sizey): - #if np.logical_or(np.logical_and(pibyfour <= azimuth, azimuth < threetimespibyfour), np.logical_and(fivetimespibyfour <= azimuth, azimuth < seventimespibyfour)): - if (pibyfour <= azimuth and azimuth < threetimespibyfour or fivetimespibyfour <= azimuth and azimuth < seventimespibyfour): + # while np.logical_and(np.logical_and(amaxvalue >= dz, np.abs(dx) <= sizex), np.abs(dy) <= sizey):(np.logical_and(amaxvalue >= dz, np.abs(dx) <= sizex), np.abs(dy) <= sizey): + # if np.logical_or(np.logical_and(pibyfour <= azimuth, azimuth < threetimespibyfour), np.logical_and(fivetimespibyfour <= azimuth, azimuth < seventimespibyfour)): + if ( + pibyfour <= azimuth + and azimuth < threetimespibyfour + or fivetimespibyfour <= azimuth + and azimuth < seventimespibyfour + ): dy = signsinazimuth * index - dx = -1. * signcosazimuth * np.abs(np.round(index / tanazimuth)) + dx = -1.0 * signcosazimuth * np.abs(np.round(index / tanazimuth)) ds = dssin else: dy = signsinazimuth * np.abs(np.round(index * tanazimuth)) - dx = -1. * signcosazimuth * index + dx = -1.0 * signcosazimuth * index ds = dscos - #% note: dx and dy represent absolute values while ds is an incremental value - dz = ds *index * tanaltitudebyscale - temp[0:sizex, 0:sizey] = 0. + # % note: dx and dy represent absolute values while ds is an incremental value + dz = ds * index * tanaltitudebyscale + temp[0:sizex, 0:sizey] = 0.0 absdx = np.abs(dx) absdy = np.abs(dy) - xc1 = (dx+absdx)/2.+1. - xc2 = sizex+(dx-absdx)/2. - yc1 = (dy+absdy)/2.+1. - yc2 = sizey+(dy-absdy)/2. - xp1 = -((dx-absdx)/2.)+1. - xp2 = sizex-(dx+absdx)/2. - yp1 = -((dy-absdy)/2.)+1. - yp2 = sizey-(dy+absdy)/2. - temp[int(xp1)-1:int(xp2), int(yp1)-1:int(yp2)] = a[int(xc1)-1:int(xc2), int(yc1)-1:int(yc2)]-dz + xc1 = (dx + absdx) / 2.0 + 1.0 + xc2 = sizex + (dx - absdx) / 2.0 + yc1 = (dy + absdy) / 2.0 + 1.0 + yc2 = sizey + (dy - absdy) / 2.0 + xp1 = -((dx - absdx) / 2.0) + 1.0 + xp2 = sizex - (dx + absdx) / 2.0 + yp1 = -((dy - absdy) / 2.0) + 1.0 + yp2 = sizey - (dy + absdy) / 2.0 + temp[int(xp1) - 1 : int(xp2), int(yp1) - 1 : int(yp2)] = ( + a[int(xc1) - 1 : int(xc2), int(yc1) - 1 : int(yc2)] - dz + ) # f = np.maximum(f, temp) # bad performance in python3. Replaced with fmax f = np.fmax(f, temp) - index += 1. + index += 1.0 - f = f-a + f = f - a f = np.logical_not(f) sh = np.double(f) return sh + # @jit(nopython=True) -def shadowingfunction_20(a, vegdem, vegdem2, azimuth, altitude, scale, amaxvalue, bush, feedback, forsvf): +def shadowingfunction_20( + a, + vegdem, + vegdem2, + azimuth, + altitude, + scale, + amaxvalue, + bush, + feedback, + forsvf, +): # plt.ion() # fig = plt.figure(figsize=(24, 7)) @@ -104,48 +127,50 @@ def shadowingfunction_20(a, vegdem, vegdem2, azimuth, altitude, scale, amaxvalue # New capability to deal with pergolas 20210827 # conversion - degrees = np.pi/180. + degrees = np.pi / 180.0 azimuth = azimuth * degrees altitude = altitude * degrees - + # measure the size of grid sizex = a.shape[0] sizey = a.shape[1] - + # progressbar for svf plugin if forsvf == 0: barstep = np.max([sizex, sizey]) - total = 100. / barstep + total = 100.0 / barstep feedback.setProgress(0) # dlg.progressBar.setRange(0, barstep) # dlg.progressBar.setValue(0) # initialise parameters - dx = 0. - dy = 0. - dz = 0. + dx = 0.0 + dy = 0.0 + dz = 0.0 temp = np.zeros((sizex, sizey)) tempvegdem = np.zeros((sizex, sizey)) tempvegdem2 = np.zeros((sizex, sizey)) templastfabovea = np.zeros((sizex, sizey)) templastgabovea = np.zeros((sizex, sizey)) - bushplant = bush > 1. - sh = np.zeros((sizex, sizey)) #shadows from buildings - vbshvegsh = np.zeros((sizex, sizey)) #vegetation blocking buildings - vegsh = np.add(np.zeros((sizex, sizey)), bushplant, dtype=float) #vegetation shadow + bushplant = bush > 1.0 + sh = np.zeros((sizex, sizey)) # shadows from buildings + vbshvegsh = np.zeros((sizex, sizey)) # vegetation blocking buildings + vegsh = np.add( + np.zeros((sizex, sizey)), bushplant, dtype=float + ) # vegetation shadow f = a - pibyfour = np.pi / 4. - threetimespibyfour = 3. * pibyfour - fivetimespibyfour = 5.* pibyfour - seventimespibyfour = 7. * pibyfour + pibyfour = np.pi / 4.0 + threetimespibyfour = 3.0 * pibyfour + fivetimespibyfour = 5.0 * pibyfour + seventimespibyfour = 7.0 * pibyfour sinazimuth = np.sin(azimuth) cosazimuth = np.cos(azimuth) tanazimuth = np.tan(azimuth) signsinazimuth = np.sign(sinazimuth) signcosazimuth = np.sign(cosazimuth) - dssin = np.abs((1./sinazimuth)) - dscos = np.abs((1./cosazimuth)) + dssin = np.abs((1.0 / sinazimuth)) + dscos = np.abs((1.0 / cosazimuth)) tanaltitudebyscale = np.tan(altitude) / scale # index = 1 index = 0 @@ -156,57 +181,70 @@ def shadowingfunction_20(a, vegdem, vegdem2, azimuth, altitude, scale, amaxvalue # main loop while (amaxvalue >= dz) and (np.abs(dx) < sizex) and (np.abs(dy) < sizey): if forsvf == 0: - feedback.setProgress(int(index * total)) #dlg.progressBar.setValue(index) - if ((pibyfour <= azimuth) and (azimuth < threetimespibyfour) or (fivetimespibyfour <= azimuth) and (azimuth < seventimespibyfour)): + feedback.setProgress( + int(index * total) + ) # dlg.progressBar.setValue(index) + if ( + (pibyfour <= azimuth) + and (azimuth < threetimespibyfour) + or (fivetimespibyfour <= azimuth) + and (azimuth < seventimespibyfour) + ): dy = signsinazimuth * index - dx = -1. * signcosazimuth * np.abs(np.round(index / tanazimuth)) + dx = -1.0 * signcosazimuth * np.abs(np.round(index / tanazimuth)) ds = dssin else: dy = signsinazimuth * np.abs(np.round(index * tanazimuth)) - dx = -1. * signcosazimuth * index + dx = -1.0 * signcosazimuth * index ds = dscos # note: dx and dy represent absolute values while ds is an incremental value dz = (ds * index) * tanaltitudebyscale - tempvegdem[0:sizex, 0:sizey] = 0. - tempvegdem2[0:sizex, 0:sizey] = 0. - temp[0:sizex, 0:sizey] = 0. - templastfabovea[0:sizex, 0:sizey] = 0. - templastgabovea[0:sizex, 0:sizey] = 0. + tempvegdem[0:sizex, 0:sizey] = 0.0 + tempvegdem2[0:sizex, 0:sizey] = 0.0 + temp[0:sizex, 0:sizey] = 0.0 + templastfabovea[0:sizex, 0:sizey] = 0.0 + templastgabovea[0:sizex, 0:sizey] = 0.0 absdx = np.abs(dx) absdy = np.abs(dy) - xc1 = int((dx+absdx)/2.) - xc2 = int(sizex+(dx-absdx)/2.) - yc1 = int((dy+absdy)/2.) - yc2 = int(sizey+(dy-absdy)/2.) - xp1 = int(-((dx-absdx)/2.)) - xp2 = int(sizex-(dx+absdx)/2.) - yp1 = int(-((dy-absdy)/2.)) - yp2 = int(sizey-(dy+absdy)/2.) + xc1 = int((dx + absdx) / 2.0) + xc2 = int(sizex + (dx - absdx) / 2.0) + yc1 = int((dy + absdy) / 2.0) + yc2 = int(sizey + (dy - absdy) / 2.0) + xp1 = int(-((dx - absdx) / 2.0)) + xp2 = int(sizex - (dx + absdx) / 2.0) + yp1 = int(-((dy - absdy) / 2.0)) + yp2 = int(sizey - (dy + absdy) / 2.0) tempvegdem[xp1:xp2, yp1:yp2] = vegdem[xc1:xc2, yc1:yc2] - dz tempvegdem2[xp1:xp2, yp1:yp2] = vegdem2[xc1:xc2, yc1:yc2] - dz - temp[xp1:xp2, yp1:yp2] = a[xc1:xc2, yc1:yc2]-dz - - f = np.fmax(f, temp) #Moving building shadow - sh[(f > a)] = 1. - sh[(f <= a)] = 0. - fabovea = tempvegdem > a #vegdem above DEM - gabovea = tempvegdem2 > a #vegdem2 above DEM - - #new pergola condition - templastfabovea[xp1:xp2, yp1:yp2] = vegdem[xc1:xc2, yc1:yc2]-dzprev - templastgabovea[xp1:xp2, yp1:yp2] = vegdem2[xc1:xc2, yc1:yc2]-dzprev + temp[xp1:xp2, yp1:yp2] = a[xc1:xc2, yc1:yc2] - dz + + f = np.fmax(f, temp) # Moving building shadow + sh[(f > a)] = 1.0 + sh[(f <= a)] = 0.0 + fabovea = tempvegdem > a # vegdem above DEM + gabovea = tempvegdem2 > a # vegdem2 above DEM + + # new pergola condition + templastfabovea[xp1:xp2, yp1:yp2] = vegdem[xc1:xc2, yc1:yc2] - dzprev + templastgabovea[xp1:xp2, yp1:yp2] = vegdem2[xc1:xc2, yc1:yc2] - dzprev lastfabovea = templastfabovea > a lastgabovea = templastgabovea > a dzprev = dz - vegsh2 = np.add(np.add(np.add(fabovea, gabovea, dtype=float),lastfabovea, dtype=float),lastgabovea, dtype=float) - vegsh2[vegsh2 == 4] = 0. + vegsh2 = np.add( + np.add( + np.add(fabovea, gabovea, dtype=float), lastfabovea, dtype=float + ), + lastgabovea, + dtype=float, + ) + vegsh2[vegsh2 == 4] = 0.0 # vegsh2[vegsh2 == 1] = 0. # This one is the ultimate question... - vegsh2[vegsh2 > 0] = 1. + vegsh2[vegsh2 > 0] = 1.0 vegsh = np.fmax(vegsh, vegsh2) - vegsh[(vegsh * sh > 0.)] = 0. - vbshvegsh = vegsh + vbshvegsh # removing shadows 'behind' buildings + vegsh[(vegsh * sh > 0.0)] = 0.0 + vbshvegsh = vegsh + vbshvegsh # removing shadows 'behind' buildings # im1 = ax1.imshow(fabovea) # im2 = ax2.imshow(gabovea) @@ -223,13 +261,13 @@ def shadowingfunction_20(a, vegdem, vegdem2, azimuth, altitude, scale, amaxvalue # plt.show() # plt.pause(0.05) - index += 1. + index += 1.0 - sh = 1.-sh - vbshvegsh[(vbshvegsh > 0.)] = 1. - vbshvegsh = vbshvegsh-vegsh - vegsh = 1.-vegsh - vbshvegsh = 1.-vbshvegsh + sh = 1.0 - sh + vbshvegsh[(vbshvegsh > 0.0)] = 1.0 + vbshvegsh = vbshvegsh - vegsh + vegsh = 1.0 - vegsh + vbshvegsh = 1.0 - vbshvegsh # plt.close() # plt.ion() @@ -252,32 +290,34 @@ def shadowingfunction_20(a, vegdem, vegdem2, azimuth, altitude, scale, amaxvalue # plt.show() # plt.pause(0.05) - shadowresult = {'sh': sh, 'vegsh': vegsh, 'vbshvegsh': vbshvegsh} + shadowresult = {"sh": sh, "vegsh": vegsh, "vbshvegsh": vbshvegsh} return shadowresult -def shadowingfunction_20_old(a, vegdem, vegdem2, azimuth, altitude, scale, amaxvalue, bush, dlg, forsvf): +def shadowingfunction_20_old( + a, vegdem, vegdem2, azimuth, altitude, scale, amaxvalue, bush, dlg, forsvf +): - #% This function casts shadows on buildings and vegetation units - #% conversion - degrees = np.pi/180. + # % This function casts shadows on buildings and vegetation units + # % conversion + degrees = np.pi / 180.0 if azimuth == 0.0: azimuth = 0.000000000001 azimuth = np.dot(azimuth, degrees) altitude = np.dot(altitude, degrees) - #% measure the size of the image + # % measure the size of the image sizex = a.shape[0] sizey = a.shape[1] - #% initialise parameters + # % initialise parameters if forsvf == 0: barstep = np.max([sizex, sizey]) dlg.progressBar.setRange(0, barstep) dlg.progressBar.setValue(0) - dx = 0. - dy = 0. - dz = 0. + dx = 0.0 + dy = 0.0 + dz = 0.0 temp = np.zeros((sizex, sizey)) tempvegdem = np.zeros((sizex, sizey)) tempvegdem2 = np.zeros((sizex, sizey)) @@ -287,126 +327,154 @@ def shadowingfunction_20_old(a, vegdem, vegdem2, azimuth, altitude, scale, amaxv tempbush = np.zeros((sizex, sizey)) f = a g = np.zeros((sizex, sizey)) - bushplant = bush > 1. - pibyfour = np.pi/4. - threetimespibyfour = 3.*pibyfour - fivetimespibyfour = 5.*pibyfour - seventimespibyfour = 7.*pibyfour + bushplant = bush > 1.0 + pibyfour = np.pi / 4.0 + threetimespibyfour = 3.0 * pibyfour + fivetimespibyfour = 5.0 * pibyfour + seventimespibyfour = 7.0 * pibyfour sinazimuth = np.sin(azimuth) cosazimuth = np.cos(azimuth) tanazimuth = np.tan(azimuth) signsinazimuth = np.sign(sinazimuth) signcosazimuth = np.sign(cosazimuth) - dssin = np.abs((1./sinazimuth)) - dscos = np.abs((1./cosazimuth)) + dssin = np.abs((1.0 / sinazimuth)) + dscos = np.abs((1.0 / cosazimuth)) tanaltitudebyscale = np.tan(altitude) / scale index = 1 - #% main loop - while (amaxvalue >= dz and np.abs(dx) < sizex and np.abs(dy) < sizey): + # % main loop + while amaxvalue >= dz and np.abs(dx) < sizex and np.abs(dy) < sizey: if forsvf == 0: dlg.progressBar.setValue(index) - if (pibyfour <= azimuth and azimuth < threetimespibyfour or fivetimespibyfour <= azimuth and azimuth < seventimespibyfour): + if ( + pibyfour <= azimuth + and azimuth < threetimespibyfour + or fivetimespibyfour <= azimuth + and azimuth < seventimespibyfour + ): dy = signsinazimuth * index - dx = -1. * signcosazimuth * np.abs(np.round(index / tanazimuth)) + dx = -1.0 * signcosazimuth * np.abs(np.round(index / tanazimuth)) ds = dssin else: dy = signsinazimuth * np.abs(np.round(index * tanazimuth)) - dx = -1. * signcosazimuth * index + dx = -1.0 * signcosazimuth * index ds = dscos - #% note: dx and dy represent absolute values while ds is an incremental value + # % note: dx and dy represent absolute values while ds is an incremental value dz = np.dot(np.dot(ds, index), tanaltitudebyscale) - tempvegdem[0:sizex, 0:sizey] = 0. - tempvegdem2[0:sizex, 0:sizey] = 0. - temp[0:sizex, 0:sizey] = 0. + tempvegdem[0:sizex, 0:sizey] = 0.0 + tempvegdem2[0:sizex, 0:sizey] = 0.0 + temp[0:sizex, 0:sizey] = 0.0 absdx = np.abs(dx) absdy = np.abs(dy) - xc1 = (dx+absdx)/2.+1. - xc2 = sizex+(dx-absdx)/2. - yc1 = (dy+absdy)/2.+1. - yc2 = sizey+(dy-absdy)/2. - xp1 = -((dx-absdx)/2.)+1. - xp2 = sizex-(dx+absdx)/2. - yp1 = -((dy-absdy)/2.)+1. - yp2 = sizey-(dy+absdy)/2. - tempvegdem[int(xp1)-1:int(xp2), int(yp1)-1:int(yp2)] = vegdem[int(xc1)-1:int(xc2), int(yc1)-1:int(yc2)]-dz - tempvegdem2[int(xp1)-1:int(xp2), int(yp1)-1:int(yp2)] = vegdem2[int(xc1)-1:int(xc2), int(yc1)-1:int(yc2)]-dz - temp[int(xp1)-1:int(xp2), int(yp1)-1:int(yp2)] = a[int(xc1)-1:int(xc2), int(yc1)-1:int(yc2)]-dz + xc1 = (dx + absdx) / 2.0 + 1.0 + xc2 = sizex + (dx - absdx) / 2.0 + yc1 = (dy + absdy) / 2.0 + 1.0 + yc2 = sizey + (dy - absdy) / 2.0 + xp1 = -((dx - absdx) / 2.0) + 1.0 + xp2 = sizex - (dx + absdx) / 2.0 + yp1 = -((dy - absdy) / 2.0) + 1.0 + yp2 = sizey - (dy + absdy) / 2.0 + tempvegdem[int(xp1) - 1 : int(xp2), int(yp1) - 1 : int(yp2)] = ( + vegdem[int(xc1) - 1 : int(xc2), int(yc1) - 1 : int(yc2)] - dz + ) + tempvegdem2[int(xp1) - 1 : int(xp2), int(yp1) - 1 : int(yp2)] = ( + vegdem2[int(xc1) - 1 : int(xc2), int(yc1) - 1 : int(yc2)] - dz + ) + temp[int(xp1) - 1 : int(xp2), int(yp1) - 1 : int(yp2)] = ( + a[int(xc1) - 1 : int(xc2), int(yc1) - 1 : int(yc2)] - dz + ) # f = np.maximum(f, temp) # bad performance in python3. Replaced with fmax f = np.fmax(f, temp) - sh[(f > a)] = 1. - sh[(f <= a)] = 0. - #%Moving building shadow + sh[(f > a)] = 1.0 + sh[(f <= a)] = 0.0 + # %Moving building shadow fabovea = tempvegdem > a - #%vegdem above DEM + # %vegdem above DEM gabovea = tempvegdem2 > a - #%vegdem2 above DEM + # %vegdem2 above DEM # vegsh2 = np.float(fabovea)-np.float(gabovea) vegsh2 = np.subtract(fabovea, gabovea, dtype=float) # vegsh = np.maximum(vegsh, vegsh2) # bad performance in python3. Replaced with fmax vegsh = np.fmax(vegsh, vegsh2) - vegsh[(vegsh*sh > 0.)] = 0. - #% removing shadows 'behind' buildings - vbshvegsh = vegsh+vbshvegsh - #% vegsh at high sun altitudes - if index == 1.: - firstvegdem = tempvegdem-temp - firstvegdem[(firstvegdem <= 0.)] = 1000. - vegsh[(firstvegdem < dz)] = 1. - vegsh = vegsh*(vegdem2 > a) + vegsh[(vegsh * sh > 0.0)] = 0.0 + # % removing shadows 'behind' buildings + vbshvegsh = vegsh + vbshvegsh + # % vegsh at high sun altitudes + if index == 1.0: + firstvegdem = tempvegdem - temp + firstvegdem[(firstvegdem <= 0.0)] = 1000.0 + vegsh[(firstvegdem < dz)] = 1.0 + vegsh = vegsh * (vegdem2 > a) vbshvegsh = np.zeros((sizex, sizey)) - #% Bush shadow on bush plant - if np.logical_and(bush.max() > 0., np.max((fabovea*bush)) > 0.): - tempbush[0:sizex, 0:sizey] = 0. - tempbush[int(xp1)-1:int(xp2), int(yp1)-1:int(yp2)] = bush[int(xc1)-1:int(xc2),int(yc1)-1:int(yc2)]-dz + # % Bush shadow on bush plant + if np.logical_and(bush.max() > 0.0, np.max((fabovea * bush)) > 0.0): + tempbush[0:sizex, 0:sizey] = 0.0 + tempbush[int(xp1) - 1 : int(xp2), int(yp1) - 1 : int(yp2)] = ( + bush[int(xc1) - 1 : int(xc2), int(yc1) - 1 : int(yc2)] - dz + ) # g = np.maximum(g, tempbush) # bad performance in python3. Replaced with fmax g = np.fmax(g, tempbush) g *= bushplant - index += 1. + index += 1.0 - sh = 1.-sh - vbshvegsh[(vbshvegsh > 0.)] = 1. - vbshvegsh = vbshvegsh-vegsh + sh = 1.0 - sh + vbshvegsh[(vbshvegsh > 0.0)] = 1.0 + vbshvegsh = vbshvegsh - vegsh - if bush.max() > 0.: - g = g-bush - g[(g > 0.)] = 1. - g[(g < 0.)] = 0. - vegsh = vegsh-bushplant+g - vegsh[(vegsh<0.)] = 0. + if bush.max() > 0.0: + g = g - bush + g[(g > 0.0)] = 1.0 + g[(g < 0.0)] = 0.0 + vegsh = vegsh - bushplant + g + vegsh[(vegsh < 0.0)] = 0.0 - vegsh[(vegsh > 0.)] = 1. - vegsh = 1.-vegsh - vbshvegsh = 1.-vbshvegsh + vegsh[(vegsh > 0.0)] = 1.0 + vegsh = 1.0 - vegsh + vbshvegsh = 1.0 - vbshvegsh - shadowresult = {'sh': sh, 'vegsh': vegsh, 'vbshvegsh': vbshvegsh} + shadowresult = {"sh": sh, "vegsh": vegsh, "vbshvegsh": vbshvegsh} return shadowresult -def shadowingfunction_findwallID(dsm, azimuth, altitude, scale, walls, uniqueWallIDs, dem, wall2d_id, voxel_height, voxelId_list, facesh, wall_dict, sh): + +def shadowingfunction_findwallID( + dsm, + azimuth, + altitude, + scale, + walls, + uniqueWallIDs, + dem, + wall2d_id, + voxel_height, + voxelId_list, + facesh, + wall_dict, + sh, +): """ This function identifies what wall id and voxel height that is seen from a ground pixel - + INPUTS: dsm = Digital surface model azimuth and altitude = sun position in degrees scale= scale of DSM (1 meter pixels=1, 2 meter pixels=0.5) uniqueWallIDs = pixel row 'outside' buildings. will be calculated if empty walls = height of walls - dem = Digital elevation model. (Should be excluded in future to incorporate ground elevation) - + dem = Digital elevation model. (Should be excluded in future to incorporate ground elevation) + OUTPUT: buildIDSeen = ID seen from ground pixel - voxelHeight = Wall height shadow volume + voxelHeight = Wall height shadow volume Fredrik Lindberg 2023-02-16 fredrikl@gvc.gu.se - + """ # Remove ground heights - dsm = dsm-dem + dsm = dsm - dem # buildings = 1 - ((dsm) > 0) dsm[dsm < 0.5] = 0 @@ -427,16 +495,16 @@ def shadowingfunction_findwallID(dsm, azimuth, altitude, scale, walls, uniqueWal dy = 0 dz = 0 temp = np.zeros((rows, cols)) - temp2 = np.zeros((rows, cols)) # walls + temp2 = np.zeros((rows, cols)) # walls tempwallID = np.zeros((rows, cols)) uniqueWallIDsOrig = np.copy(uniqueWallIDs) - + voxelHeight = np.zeros((rows, cols)) temp3 = np.ones((rows, cols)) - + # other loop parameters amaxvalue = np.max(dsm) - pibyfour = np.pi/4 + pibyfour = np.pi / 4 threetimespibyfour = 3 * pibyfour fivetimespibyfour = 5 * pibyfour seventimespibyfour = 7 * pibyfour @@ -445,17 +513,18 @@ def shadowingfunction_findwallID(dsm, azimuth, altitude, scale, walls, uniqueWal tanazimuth = np.tan(azimuth) signsinazimuth = np.sign(sinazimuth) signcosazimuth = np.sign(cosazimuth) - dssin = np.abs(1/sinazimuth) - dscos = np.abs(1/cosazimuth) - tanaltitudebyscale = np.tan(altitude)/scale - + dssin = np.abs(1 / sinazimuth) + dscos = np.abs(1 / cosazimuth) + tanaltitudebyscale = np.tan(altitude) / scale + index = 1 # main loop while (amaxvalue >= dz) and (np.abs(dx) < rows) and (np.abs(dy) < cols): - if (pibyfour <= azimuth and azimuth < threetimespibyfour) or \ - (fivetimespibyfour <= azimuth and azimuth < seventimespibyfour): + if (pibyfour <= azimuth and azimuth < threetimespibyfour) or ( + fivetimespibyfour <= azimuth and azimuth < seventimespibyfour + ): dy = signsinazimuth * index dx = -1 * signcosazimuth * np.abs(np.round(index / tanazimuth)) ds = dssin @@ -472,21 +541,21 @@ def shadowingfunction_findwallID(dsm, azimuth, altitude, scale, walls, uniqueWal absdx = np.abs(dx) absdy = np.abs(dy) - xc1 = int((dx+absdx)/2) - xc2 = int(rows+(dx-absdx)/2) - yc1 = int((dy+absdy)/2) - yc2 = int(cols+(dy-absdy)/2) + xc1 = int((dx + absdx) / 2) + xc2 = int(rows + (dx - absdx) / 2) + yc1 = int((dy + absdy) / 2) + yc2 = int(cols + (dy - absdy) / 2) - xp1 = int(-((dx-absdx)/2)) - xp2 = int(rows-(dx+absdx)/2) - yp1 = int(-((dy-absdy)/2)) - yp2 = int(cols-(dy+absdy)/2) + xp1 = int(-((dx - absdx) / 2)) + xp2 = int(rows - (dx + absdx) / 2) + yp1 = int(-((dy - absdy) / 2)) + yp2 = int(cols - (dy + absdy) / 2) wallSeen = facesh uniqueWallIDs = uniqueWallIDs * wallSeen # uniqueWallIDs = ((uniqueWallIDs - firstMove) < 0) * uniqueWallIDsOrig + uniqueWallIDs # adding missing corner # wallSeenHeight = walls * wallSeen - + # temp2[xp1:xp2, yp1:yp2] = wallSeenHeight[xc1:xc2, yc1:yc2] - dz # Moving wall shadow # Moving wall id tempwallID[xp1:xp2, yp1:yp2] = uniqueWallIDs[xc1:xc2, yc1:yc2] @@ -497,19 +566,21 @@ def shadowingfunction_findwallID(dsm, azimuth, altitude, scale, walls, uniqueWal # Descending wall, how much of the wall that is still above ground level temp2 = temp_wallHeight - dz - # buildIDSeen = Wall pixels/voxels seen, i.e. only voxels that are positive (above ground level) (temp2 > 0). + # buildIDSeen = Wall pixels/voxels seen, i.e. only voxels that are positive (above ground level) (temp2 > 0). # temp3 indicates those pixels that the walls have not progressed into yet (saved in previous iteration). buildIDSeen = (temp2 > 0) * temp3 * tempwallID + buildIDSeen - + # voxelHeight = (temp2 > 0) * temp3 * temp2 + voxelHeight # seen wall heights - - # voxelHeight = the elevation on a wall that is seen from a pixel with the given altitude and azimuth (only above ground leve, i.e. (temp2 > 0)). + + # voxelHeight = the elevation on a wall that is seen from a pixel with the given altitude and azimuth (only above ground leve, i.e. (temp2 > 0)). # voxelHeight = wall height - descending wall, i.e. temp_wallHeight - temp2. Only applicable to pixels where there is no value from previous iterations (temp3). - voxelHeight = (temp2 > 0) * temp3 * (temp_wallHeight - temp2) + voxelHeight + voxelHeight = (temp2 > 0) * temp3 * ( + temp_wallHeight - temp2 + ) + voxelHeight # voxelHeight = (temp2 > 0) * temp3 * (temp_wallHeight - (temp2 * (temp2 > 0))) + voxelHeight # seen wall heights - + # Remember pixels previous iteration that walls have not progressed into yet. - temp3 = np.copy(temp2 <= 0) * (buildIDSeen == 0) + temp3 = np.copy(temp2 <= 0) * (buildIDSeen == 0) index += 1 @@ -546,15 +617,23 @@ def shadowingfunction_findwallID(dsm, azimuth, altitude, scale, walls, uniqueWal # Fill voxelId matrix with unique voxel IDs for temp_id, temp_height in d: # print(str(temp_id) + ' ' + str(temp_height)) - temp_fill_id = voxelId_list[((wall2d_id == temp_id) & (voxel_height == temp_height))] + temp_fill_id = voxelId_list[ + ((wall2d_id == temp_id) & (voxel_height == temp_height)) + ] if temp_fill_id.__len__() > 0: # print('temp_fill_id = ' + str(temp_fill_id)) - voxelId[(buildIDSeen == temp_id) & (voxelHeight_ceil == temp_height)] = temp_fill_id + voxelId[ + (buildIDSeen == temp_id) & (voxelHeight_ceil == temp_height) + ] = temp_fill_id in_list += 1 else: not_in_list += 1 - buildIDSeen[(buildIDSeen == temp_id) & (voxelHeight_ceil == temp_height)] = 0 - voxelHeight_ceil[(buildIDSeen == temp_id) & (voxelHeight_ceil == temp_height)] = 0 + buildIDSeen[ + (buildIDSeen == temp_id) & (voxelHeight_ceil == temp_height) + ] = 0 + voxelHeight_ceil[ + (buildIDSeen == temp_id) & (voxelHeight_ceil == temp_height) + ] = 0 # if ((np.any(buildIDSeen == temp_id) & ~np.all(voxelHeight_ceil == temp_height)) or (~np.all(buildIDSeen == temp_id) & np.any(voxelHeight_ceil == temp_height))): # print('temp_id = ' + str(temp_id)) @@ -575,12 +654,11 @@ def shadowingfunction_findwallID(dsm, azimuth, altitude, scale, walls, uniqueWal return buildIDSeen, voxelHeight, voxelId - # temp[xp1:xp2, yp1:yp2] = dsm[xc1:xc2, yc1:yc2] - dz # f = np.fmax(f, temp) #Moving building shadow # if index == 1: #Remove walls on "wrong" side of buildings during first iteration - + # firstMove = (f-dsm) > 0 # if (pibyfour <= azimuth and azimuth < threetimespibyfour) or \ # (fivetimespibyfour <= azimuth and azimuth < seventimespibyfour): @@ -605,6 +683,6 @@ def shadowingfunction_findwallID(dsm, azimuth, altitude, scale, walls, uniqueWal # temp[xp1b:xp2b, yp1b:yp2b] = dsm[xc1b:xc2b, yc1b:yc2b] - # wallSeen = temp-dsm - # wallSeen[wallSeen > 0] = 1 - # wallSeen[wallSeen < 0] = 0 \ No newline at end of file +# wallSeen = temp-dsm +# wallSeen[wallSeen > 0] = 1 +# wallSeen[wallSeen < 0] = 0 diff --git a/util/ssParms.py b/util/ssParms.py index ee98ca2..4de863c 100644 --- a/util/ssParms.py +++ b/util/ssParms.py @@ -1,44 +1,51 @@ # -*- coding: utf-8 -*- -''' +""" Calcualte input for spartacus and write info to Gridlayout namelist Fredrik Lindberg 2023-07-06 -''' +""" + import numpy as np from ..functions import wallalgorithms as wa + # from umep_suewsss_export_component import write_GridLayout_file, create_GridLayout_dict -from ..util.umep_suewsss_export_component import write_GridLayout_file, create_GridLayout_dict +from ..util.umep_suewsss_export_component import ( + write_GridLayout_file, + create_GridLayout_dict, +) # import matplotlib as plt def ss_calc(build, cdsm, walls, numPixels, feedback): - walllimit = 0.3 # 30 centimeters height variation identifies a vegetation edge pixel - total = 100. / (int(build.shape[0] * build.shape[1])) + walllimit = 0.3 # 30 centimeters height variation identifies a vegetation edge pixel + total = 100.0 / (int(build.shape[0] * build.shape[1])) if cdsm.max() > 0: vegEdges = wa.findwalls(cdsm, walllimit, feedback, total) - + buildvec = build[np.where(build > 0)] - if buildvec.size > 0: #TODO: What if vegetation is higher that building OR only vegetation? - #zH_all = buildvec.mean() + if ( + buildvec.size > 0 + ): # TODO: What if vegetation is higher that building OR only vegetation? + # zH_all = buildvec.mean() zHmax_all = buildvec.max() - #zH_sd_all = buildvec.std() - #pai_ground = (buildvec.size * 1.0) / numPixels + # zH_sd_all = buildvec.std() + # pai_ground = (buildvec.size * 1.0) / numPixels iterHeights = int(np.ceil(zHmax_all)) else: - #zH_all = 0 + # zH_all = 0 zHmax_all = 0 - #zH_sd_all = 0 - #pai_all = 0 + # zH_sd_all = 0 + # pai_all = 0 iterHeights = int(0) z = np.zeros((iterHeights + 1, 1)) paiZ_b = np.zeros((iterHeights + 1, 1)) - bScale = np.zeros((iterHeights + 1, 1)) # building scale + bScale = np.zeros((iterHeights + 1, 1)) # building scale paiZ_v = np.zeros((iterHeights + 1, 1)) - vScale = np.zeros((iterHeights + 1, 1)) # vegetation scale + vScale = np.zeros((iterHeights + 1, 1)) # vegetation scale for i in np.arange(0, iterHeights + 1): z[i] = i @@ -49,7 +56,7 @@ def ss_calc(build, cdsm, walls, numPixels, feedback): if waiZ_b == 0: bScale[i] = 0 else: - bScale[i] = (4*paiZ_b[i]) / waiZ_b + bScale[i] = (4 * paiZ_b[i]) / waiZ_b # feedback.setProgressText('z = ' + str(z[i])) # feedback.setProgressText('numPixels = ' + str(numPixels)) # feedback.setProgressText('paipixels = ' + str(np.where(buildZ > 0)[0].shape[0])) @@ -68,76 +75,101 @@ def ss_calc(build, cdsm, walls, numPixels, feedback): if waiZ_v == 0: vScale[i] = 0 else: - vScale[i] = (4*paiZ_v[i]) / waiZ_v + vScale[i] = (4 * paiZ_v[i]) / waiZ_v else: paiZ_v[i] = 0 vScale[i] = 0 - ssResult = {'z': z, 'paiZ_b': paiZ_b, 'bScale': bScale,'paiZ_v': paiZ_v, 'vScale': vScale} + ssResult = { + "z": z, + "paiZ_b": paiZ_b, + "bScale": bScale, + "paiZ_v": paiZ_v, + "vScale": vScale, + } return ssResult def writeGridLayout(ssVect, heightMethod, vertHeights, nlayer, skew): - ''' + """ Input: ssVect: array from xx_IMPGrid_SS_x.txt heightMethod: Method used to set vertical layers vertheights: heights of intermediate layers (bottom is 0 and top is maxzH) [option 1] nlayers: no of vertical layers [option 2] skew: 1 is equal interval between heights and 2 is exponential [option 2 and 3] - ''' + """ - #heightMethod = 2 # input from gui - #vertHeights = [0, 15, 40] - #nlayer = 3 # input from gui - #skew = 2 #TODO input from gui. linear or shewed shewed = 1 or 2 + # heightMethod = 2 # input from gui + # vertHeights = [0, 15, 40] + # nlayer = 3 # input from gui + # skew = 2 #TODO input from gui. linear or shewed shewed = 1 or 2 ssDict = create_GridLayout_dict() - if heightMethod == 1: # static levels (taken from interface). Last value > max height - ssDict['height'] = vertHeights.append(ssVect[:,0].max()) - ssDict['nlayer'] = len(ssDict['height']) - 1 - elif heightMethod == 2: # always nlayers layer based on percentiles - ssDict['nlayer'] = nlayer - elif heightMethod == 3: # vary number of layers based on height variation. Lowest no of nlayers always 3 + if ( + heightMethod == 1 + ): # static levels (taken from interface). Last value > max height + ssDict["height"] = vertHeights.append(ssVect[:, 0].max()) + ssDict["nlayer"] = len(ssDict["height"]) - 1 + elif heightMethod == 2: # always nlayers layer based on percentiles + ssDict["nlayer"] = nlayer + elif ( + heightMethod == 3 + ): # vary number of layers based on height variation. Lowest no of nlayers always 3 nlayer = 3 - if ssVect[:,0].max() > 40: nlayer = 4 - if ssVect[:,0].max() > 60: nlayer = 5 - if ssVect[:,0].max() > 80: nlayer = 6 - if ssVect[:,0].max() > 120: nlayer = 7 - ssDict['nlayer'] = nlayer - - intervals = np.ceil(ssVect[:,0].max() / nlayer) - ssDict['height'] = [] - ssDict['height'].append(.0) + if ssVect[:, 0].max() > 40: + nlayer = 4 + if ssVect[:, 0].max() > 60: + nlayer = 5 + if ssVect[:, 0].max() > 80: + nlayer = 6 + if ssVect[:, 0].max() > 120: + nlayer = 7 + ssDict["nlayer"] = nlayer + + intervals = np.ceil(ssVect[:, 0].max() / nlayer) + ssDict["height"] = [] + ssDict["height"].append(0.0) for i in range(1, nlayer): - ssDict['height'].append(float(round((intervals * i) / skew))) - ssDict['height'].append(float(ssVect[:,0].max())) - - ssDict['building_frac'] = [] - ssDict['veg_frac'] = [] - ssDict['building_scale'] = [] - ssDict['veg_scale'] = [] + ssDict["height"].append(float(round((intervals * i) / skew))) + ssDict["height"].append(float(ssVect[:, 0].max())) + ssDict["building_frac"] = [] + ssDict["veg_frac"] = [] + ssDict["building_scale"] = [] + ssDict["veg_scale"] = [] index = int(0) - for i in range(1,len(ssDict['height'])):#TODO this loop need to be confirmed by Reading + for i in range( + 1, len(ssDict["height"]) + ): # TODO this loop need to be confirmed by Reading index += 1 - startH = int(ssDict['height'][index-1]) - endH = int(ssDict['height'][index]) + startH = int(ssDict["height"][index - 1]) + endH = int(ssDict["height"][index]) if index == 1: - ssDict['building_frac'].append(ssVect[0,1]) # first is plan area index of buildings - ssDict['veg_frac'].append(ssVect[0,3]) # first is plan area index of trees + ssDict["building_frac"].append( + ssVect[0, 1] + ) # first is plan area index of buildings + ssDict["veg_frac"].append( + ssVect[0, 3] + ) # first is plan area index of trees else: - ssDict['building_frac'].append(np.round(np.mean(ssVect[startH:endH, 1]),3)) # intergrated pai_build mean in ith vertical layer - ssDict['veg_frac'].append(np.round(np.mean(ssVect[startH:endH, 3]),3)) # intergrated pai_veg mean in ith vertical layer - - ssDict['building_scale'].append(np.round(np.mean(ssVect[startH:endH, 2]),3)) # intergrated bscale mean in ith vertical layer - ssDict['veg_scale'].append(np.round(np.mean(ssVect[startH:endH, 4]),3)) # intergrated vscale mean in ith vertical layer - - #TODO here we need to add other parameters based on typology - - write_GridLayout_file(ssDict, outputDir + '/', _name) - - + ssDict["building_frac"].append( + np.round(np.mean(ssVect[startH:endH, 1]), 3) + ) # intergrated pai_build mean in ith vertical layer + ssDict["veg_frac"].append( + np.round(np.mean(ssVect[startH:endH, 3]), 3) + ) # intergrated pai_veg mean in ith vertical layer + + ssDict["building_scale"].append( + np.round(np.mean(ssVect[startH:endH, 2]), 3) + ) # intergrated bscale mean in ith vertical layer + ssDict["veg_scale"].append( + np.round(np.mean(ssVect[startH:endH, 4]), 3) + ) # intergrated vscale mean in ith vertical layer + + # TODO here we need to add other parameters based on typology + + write_GridLayout_file(ssDict, outputDir + "/", _name) diff --git a/util/umep_installer.py b/util/umep_installer.py index f3998d1..1d2e4bc 100644 --- a/util/umep_installer.py +++ b/util/umep_installer.py @@ -45,7 +45,9 @@ def locate_py(): if candidate_path.exists(): return candidate_path - raise RuntimeError("UMEP cannot locate the Python interpreter used by QGIS!") + raise RuntimeError( + "UMEP cannot locate the Python interpreter used by QGIS!" + ) # check if supy is installed @@ -53,11 +55,15 @@ def check_supy_version(): try: path_pybin = locate_py() list_cmd = f"{str(path_pybin)} -m pip show supy".split() - list_info = subprocess.check_output(list_cmd, encoding="UTF8").split("\n") + list_info = subprocess.check_output(list_cmd, encoding="UTF8").split( + "\n" + ) str_ver = list_info[1].split(":")[1].strip() return str_ver except Exception: - raise RuntimeError("UMEP cannot identify a supy installation!") from Exception + raise RuntimeError( + "UMEP cannot identify a supy installation!" + ) from Exception # install supy @@ -87,7 +93,7 @@ def install_umep_python(ver=None): else "" ) # Select correct supy version via extras (numpy 1 vs 2) - numpy_major = np.__version__.split('.')[0] + numpy_major = np.__version__.split(".")[0] numpy_extra = f"[numpy{numpy_major}]" list_cmd = f"{str(path_pybin)} -m pip install umep-reqs{numpy_extra}{str_ver} -U --user --prefer-binary {str_use_feature}".split() @@ -105,12 +111,16 @@ def install_umep_python(ver=None): str_info = ( str_info - if "Successfully installed UMEP dependent Python packages" in str_info + if "Successfully installed UMEP dependent Python packages" + in str_info else f"UMEP dependent Python packages has already been installed!" ) return str_info except subprocess.CalledProcessError as exc: - QgsMessageLog.logMessage(f"Error running {exc.args}:\n{exc.stdout}", level=Qgis.MessageLevel.Warning) + QgsMessageLog.logMessage( + f"Error running {exc.args}:\n{exc.stdout}", + level=Qgis.MessageLevel.Warning, + ) raise @@ -120,12 +130,16 @@ def uninstall_umep_python(): try: path_pybin = locate_py() list_cmd = f"{str(path_pybin)} -m pip uninstall umep-reqs -y".split() - list_info = subprocess.check_output(list_cmd, encoding="UTF8").split("\n") + list_info = subprocess.check_output(list_cmd, encoding="UTF8").split( + "\n" + ) str_info = list_info[-2].strip() return str_info except Exception: - raise RuntimeError(f"UMEP couldn't uninstall umep-reqs!") from Exception + raise RuntimeError( + f"UMEP couldn't uninstall umep-reqs!" + ) from Exception # set up umep @@ -137,6 +151,7 @@ def setup_umep_python(ver=None, debug=False): try: # check if supy and others have been installed import supy as sp + sp.show_version() import numba import jaydebeapi diff --git a/util/umep_solweig_export_component.py b/util/umep_solweig_export_component.py index a1ff60a..cf79b19 100644 --- a/util/umep_solweig_export_component.py +++ b/util/umep_solweig_export_component.py @@ -1,20 +1,25 @@ import os from configparser import ConfigParser + def read_solweig_config(config_file_path): if not os.path.exists(config_file_path): - raise ValueError("The config file '{}' could not be found.".format(config_file_path)) + raise ValueError( + "The config file '{}' could not be found.".format(config_file_path) + ) config = ConfigParser() config.read(config_file_path) + return config.defaults() -''' +""" input: TargetDict: dictonary with all relevant inputs refdir = outputfolder -''' +""" + def write_solweig_config(configDict, refdir): f = open(refdir, "w") @@ -24,89 +29,111 @@ def write_solweig_config(configDict, refdir): f.write("\n") f.write("### INPUTS ###\n") f.write("# output path\n") - f.write("output_dir={}\n".format(configDict['output_dir'])) + f.write("output_dir={}\n".format(configDict["output_dir"])) f.write("# working path\n") - f.write("working_dir={}\n".format(configDict['working_dir'])) + f.write("working_dir={}\n".format(configDict["working_dir"])) f.write("# parameters json file\n") - f.write("para_json_path={}\n".format(configDict['para_json_path'])) + f.write("para_json_path={}\n".format(configDict["para_json_path"])) f.write("# Input ground and building dsm\n") - f.write("filepath_dsm={}\n".format(configDict['filepath_dsm'])) + f.write("filepath_dsm={}\n".format(configDict["filepath_dsm"])) f.write("# Input vegetation dsm\n") - f.write("filepath_cdsm={}\n".format(configDict['filepath_cdsm'])) + f.write("filepath_cdsm={}\n".format(configDict["filepath_cdsm"])) f.write("# Input trunkzone vegetation dsm\n") - f.write("filepath_tdsm={}\n".format(configDict['filepath_tdsm'])) + f.write("filepath_tdsm={}\n".format(configDict["filepath_tdsm"])) f.write("# Input Digital Elevation Model\n") - f.write("filepath_dem={}\n".format(configDict['filepath_dem'])) + f.write("filepath_dem={}\n".format(configDict["filepath_dem"])) f.write("# Input Land cover dataset\n") - f.write("filepath_lc={}\n".format(configDict['filepath_lc'])) + f.write("filepath_lc={}\n".format(configDict["filepath_lc"])) f.write("# Input wall height raster\n") - f.write("filepath_wh={}\n".format(configDict['filepath_wh'])) + f.write("filepath_wh={}\n".format(configDict["filepath_wh"])) f.write("# Input wall aspect raster\n") - f.write("filepath_wa={}\n".format(configDict['filepath_wa'])) + f.write("filepath_wa={}\n".format(configDict["filepath_wa"])) f.write("# Skyview factor files\n") - f.write("input_svf={}\n".format(configDict['input_svf'])) + f.write("input_svf={}\n".format(configDict["input_svf"])) f.write("# Input file for anisotrophic sky\n") - f.write("input_aniso={}\n".format(configDict['input_aniso'])) + f.write("input_aniso={}\n".format(configDict["input_aniso"])) f.write("# Point of Interest file for ground\n") - f.write("poi_file={}\n".format(configDict['poi_file'])) - f.write("poi_field={}\n".format(configDict['poi_field'])) - f.write("# Input file for wall temperture scheme (Wallenberg et al. 2025)\n") - f.write("input_wall={}\n".format(configDict['input_wall'])) + f.write("poi_file={}\n".format(configDict["poi_file"])) + f.write("poi_field={}\n".format(configDict["poi_field"])) + f.write( + "# Input file for wall temperture scheme (Wallenberg et al. 2025)\n" + ) + f.write("input_wall={}\n".format(configDict["input_wall"])) + f.write("# Input file for surface temperature data\n") + f.write("input_surf={}\n".format(configDict.get("input_surf", ""))) f.write("# Point of Interest file for walls\n") - f.write("woi_file={}\n".format(configDict['woi_file'])) - f.write("woi_field={}\n".format(configDict['woi_field'])) + f.write("woi_file={}\n".format(configDict["woi_file"])) + f.write("woi_field={}\n".format(configDict["woi_field"])) f.write("# input meteorolgical file (i.e. forcing file)\n") - f.write("input_met={}\n".format(configDict['input_met'])) + f.write("input_met={}\n".format(configDict["input_met"])) f.write("\n") f.write("## input settings ##\n") f.write("# dates\n") f.write("# option to execute solweig outside of osgeo/qgis environment\n") - f.write("standalone={}\n".format(configDict['standalone'])) - f.write("# estimate diffuse and global shortwve radiation from global radiation (1)\n") - f.write("onlyglobal={}\n".format(configDict['onlyglobal'])) + f.write("standalone={}\n".format(configDict["standalone"])) + f.write( + "# estimate diffuse and global shortwve radiation from global radiation (1)\n" + ) + f.write("onlyglobal={}\n".format(configDict["onlyglobal"])) f.write("# use vegetation scheme (lindberg and grimmond, 2011, TAAC)\n") - f.write("usevegdem={}\n".format(configDict['usevegdem'])) + f.write("usevegdem={}\n".format(configDict["usevegdem"])) f.write("# consider leaf on trees full year (1)\n") - f.write("conifer_bool={}\n".format(configDict['conifer_bool'])) + f.write("conifer_bool={}\n".format(configDict["conifer_bool"])) f.write("# consider person as a cylinder (1) or a box (0)\n") - f.write("cyl={}\n".format(configDict['cyl'])) + f.write("cyl={}\n".format(configDict["cyl"])) f.write("# Location of model domain\n") - f.write("lat={}\n".format(configDict['lat'])) - f.write("lon={}\n".format(configDict['lon'])) + f.write("lat={}\n".format(configDict["lat"])) + f.write("lon={}\n".format(configDict["lon"])) f.write("#utc time given in meteorological forcing file\n") - f.write("utc={}\n".format(configDict['utc'])) - f.write("# scale between horisontal and vertical resolution (1 = 1 meter pixel reolution, 0.5 = 2)\n") - f.write("scale={}\n".format(configDict['scale'])) - f.write("# Set to 1 if an EPW file is used as meteorological forcing data\n") - f.write("useepwfile={}\n".format(configDict['useepwfile'])) + f.write("utc={}\n".format(configDict["utc"])) + f.write( + "# scale between horisontal and vertical resolution (1 = 1 meter pixel reolution, 0.5 = 2)\n" + ) + f.write("scale={}\n".format(configDict["scale"])) + f.write( + "# Set to 1 if an EPW file is used as meteorological forcing data\n" + ) + f.write("useepwfile={}\n".format(configDict["useepwfile"])) f.write("# land cover scheme activated (1) (Lindberg et al. 2016 UC)\n") - f.write("landcover={}\n".format(configDict['landcover'])) - f.write("# use dem and dsm to identify building pixels (0) else, land cover will be used (0)\n") - f.write("demforbuild={}\n".format(configDict['demforbuild'])) - f.write("# use anisotrphic sky (Wallenberg et al. XXXX and Wallenberg et al. XXXX)\n") - f.write("aniso={}\n".format(configDict['aniso'])) - f.write("# use wall surface temperature scheme (Wallenberg et al. 2025, GMD)\n") - f.write("wallscheme={}\n".format(configDict['wallscheme'])) - f.write("# If building materials is not included in lc, then this is used for all buildings (Wood, Brick or Concrete)\n") - f.write("walltype={}\n".format(configDict['walltype'])) + f.write("landcover={}\n".format(configDict["landcover"])) + f.write( + "# use dem and dsm to identify building pixels (0) else, land cover will be used (0)\n" + ) + f.write("demforbuild={}\n".format(configDict["demforbuild"])) + f.write( + "# use anisotrphic sky (Wallenberg et al. XXXX and Wallenberg et al. XXXX)\n" + ) + f.write("aniso={}\n".format(configDict["aniso"])) + f.write( + "# use wall surface temperature scheme (Wallenberg et al. 2025, GMD)\n" + ) + f.write("wallscheme={}\n".format(configDict["wallscheme"])) + f.write( + "# If building materials is not included in lc, then this is used for all buildings (Wood, Brick or Concrete)\n" + ) + f.write("walltype={}\n".format(configDict["walltype"])) + f.write("# use OHM for ground surface temperature modeling\n") + f.write("groundmodel={}\n".format(configDict["groundmodel"])) f.write("# output settings\n") - f.write("outputtmrt={}\n".format(configDict['outputtmrt'])) - f.write("outputkup={}\n".format(configDict['outputkup'])) - f.write("outputkdown={}\n".format(configDict['outputkdown'])) - f.write("outputlup={}\n".format(configDict['outputlup'])) - f.write("outputldown={}\n".format(configDict['outputldown'])) - f.write("outputsh={}\n".format(configDict['outputsh'])) - f.write("savebuild={}\n".format(configDict['savebuild'])) - f.write("outputkdiff={}\n".format(configDict['outputkdiff'])) - f.write("outputtreeplanter={}\n".format(configDict['outputtreeplanter'])) - f.write("wallnetcdf={}\n".format(configDict['wallnetcdf'])) + f.write("outputtmrt={}\n".format(configDict["outputtmrt"])) + f.write("outputkup={}\n".format(configDict["outputkup"])) + f.write("outputkdown={}\n".format(configDict["outputkdown"])) + f.write("outputlup={}\n".format(configDict["outputlup"])) + f.write("outputldown={}\n".format(configDict["outputldown"])) + f.write("outputsh={}\n".format(configDict["outputsh"])) + f.write("savebuild={}\n".format(configDict["savebuild"])) + f.write("outputkdiff={}\n".format(configDict["outputkdiff"])) + f.write("outputtreeplanter={}\n".format(configDict["outputtreeplanter"])) + f.write("wallnetcdf={}\n".format(configDict["wallnetcdf"])) f.write("#-------------------------------------------------------\n") - f.write("# dates - used if an EPW-file is used\n") + f.write("# dates - used if an EPW-file is used\n") f.write("#-------------------------------------------------------\n") - f.write("# year,month,day,hour ## the start date/time for period of interest\n") - f.write("date1={}\n".format(configDict['date1'])) + f.write( + "# year,month,day,hour ## the start date/time for period of interest\n" + ) + f.write("date1={}\n".format(configDict["date1"])) f.write("# year,month,day,hour # end date/time\n") - f.write("date2={}\n".format(configDict['date2'])) + f.write("date2={}\n".format(configDict["date2"])) f.write("\n") f.close() diff --git a/util/umep_suewsss_export_component.py b/util/umep_suewsss_export_component.py index 6d48eee..343e487 100644 --- a/util/umep_suewsss_export_component.py +++ b/util/umep_suewsss_export_component.py @@ -1,204 +1,368 @@ - -''' +""" This module is used to transform UMEP pre-processing data to fit SuewsSS -input: +input: ss_object: dictonary with all relevant inputs refdir = outputfolder -fname = filename -''' +fname = filename +""" import os + def create_GridLayout_dict(): ssDict = {} # dim - ssDict['nlayer'] = 3 # number of vertical layers + ssDict["nlayer"] = 3 # number of vertical layers # geom - ssDict['height'] = [0., 11., 15., 22.] #TODO height of top of each layer (start with 0 i.e. one more than nlayers) - ssDict['building_frac'] = [0.43, 0.38, .2] #TODO fraction of building coverage of each layer; the first one is plan area index of buildings - ssDict['veg_frac'] = [0.01, 0.02, .01] #TODO fraction of vegetation coverage of each layer - ssDict['building_scale'] =[50., 50., 50] #TODO building scale of each layer [m] - ssDict['veg_scale'] = [10., 10., 10] #TODO vegetation scale of each layer [m] + ssDict["height"] = [ + 0.0, + 11.0, + 15.0, + 22.0, + ] # TODO height of top of each layer (start with 0 i.e. one more than nlayers) + ssDict["building_frac"] = [ + 0.43, + 0.38, + 0.2, + ] # TODO fraction of building coverage of each layer; the first one is plan area index of buildings + ssDict["veg_frac"] = [ + 0.01, + 0.02, + 0.01, + ] # TODO fraction of vegetation coverage of each layer + ssDict["building_scale"] = [ + 50.0, + 50.0, + 50, + ] # TODO building scale of each layer [m] + ssDict["veg_scale"] = [ + 10.0, + 10.0, + 10, + ] # TODO vegetation scale of each layer [m] # roof - ssDict['sfr_roof'] = [.3, .3, .4] #TODO how? fraction of roofs of each layer (sum should be 1) - ssDict['tin_roof'] = [5, 5, 6] #TODO? how? initial temperatures of roofs [degC] - ssDict['alb_roof'] = [.5, .5, .2] #TODO albedo of roofs - ssDict['emis_roof'] = [.95, .95, .95] #TODO emissivity of roofs - ssDict['state_roof'] = [.0, .0, .0] # initial surface water depth state of roofs [mm] - ssDict['statelimit_roof'] = [5, 5, 5] # surface water depth state limit of roofs [mm] - ssDict['wetthresh_roof'] = [5, 5, 5] # surface water depth threshold of roofs (used in latent heat flux calculation) [mm] - ssDict['soilstore_roof'] = [20, 20, 20] # soil water store of roofs [mm] - ssDict['soilstorecap_roof'] = [120, 120, 120] # soil water store capacity of roofs [mm] - ssDict['roof_albedo_dir_mult_fact'] = [1.,1.,1.] # initial surface water depth state of roofs [mm] + ssDict["sfr_roof"] = [ + 0.3, + 0.3, + 0.4, + ] # TODO how? fraction of roofs of each layer (sum should be 1) + ssDict["tin_roof"] = [ + 5, + 5, + 6, + ] # TODO? how? initial temperatures of roofs [degC] + ssDict["alb_roof"] = [0.5, 0.5, 0.2] # TODO albedo of roofs + ssDict["emis_roof"] = [0.95, 0.95, 0.95] # TODO emissivity of roofs + ssDict["state_roof"] = [ + 0.0, + 0.0, + 0.0, + ] # initial surface water depth state of roofs [mm] + ssDict["statelimit_roof"] = [ + 5, + 5, + 5, + ] # surface water depth state limit of roofs [mm] + ssDict["wetthresh_roof"] = [ + 5, + 5, + 5, + ] # surface water depth threshold of roofs (used in latent heat flux calculation) [mm] + ssDict["soilstore_roof"] = [20, 20, 20] # soil water store of roofs [mm] + ssDict["soilstorecap_roof"] = [ + 120, + 120, + 120, + ] # soil water store capacity of roofs [mm] + ssDict["roof_albedo_dir_mult_fact"] = [ + 1.0, + 1.0, + 1.0, + ] # initial surface water depth state of roofs [mm] # The following parameters are used to calculate the heat flux from the roof # first roof facet - ssDict['dz_roof1'] = [.2, .1, .1, .01, .01] #TODO thickness of each layer (strictly five lyaers in total) [m] - ssDict['k_roof1'] = [1.2, 1.2, 1.2, 1.2, 1.2] #TODO thermal conductivity of each layer [W/m/K] - ssDict['cp_roof1'] = [2e6, 2e6, 2e6, 2e6, 2e6] #TODO specific heat capacity of each layer [J/kg/K] - - ssDict['dz_roof2'] = [.2, .1, .1, .01, .01] #TODO - ssDict['k_roof2'] = [2.2, 1.2, 1.2, 1.2, 1.2] #TODO - ssDict['cp_roof2'] = [2e6, 2e6, 2e6, 2e6, 2e6] #TODO - - ssDict['dz_roof3'] = [.2, .1, .1, .01, .01] #TODO - ssDict['k_roof3'] = [2.2, 1.2, 1.2, 1.2, 1.2] #TODO - ssDict['cp_roof3'] = [2e6, 2e6, 2e6, 2e6, 2e6] #TODO + ssDict["dz_roof1"] = [ + 0.2, + 0.1, + 0.1, + 0.01, + 0.01, + ] # TODO thickness of each layer (strictly five lyaers in total) [m] + ssDict["k_roof1"] = [ + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + ] # TODO thermal conductivity of each layer [W/m/K] + ssDict["cp_roof1"] = [ + 2e6, + 2e6, + 2e6, + 2e6, + 2e6, + ] # TODO specific heat capacity of each layer [J/kg/K] + + ssDict["dz_roof2"] = [0.2, 0.1, 0.1, 0.01, 0.01] # TODO + ssDict["k_roof2"] = [2.2, 1.2, 1.2, 1.2, 1.2] # TODO + ssDict["cp_roof2"] = [2e6, 2e6, 2e6, 2e6, 2e6] # TODO + + ssDict["dz_roof3"] = [0.2, 0.1, 0.1, 0.01, 0.01] # TODO + ssDict["k_roof3"] = [2.2, 1.2, 1.2, 1.2, 1.2] # TODO + ssDict["cp_roof3"] = [2e6, 2e6, 2e6, 2e6, 2e6] # TODO # wall # similarly to roof parameters but for walls - ssDict['sfr_wall'] = [.3, .3, .4] #TODO # (sum should be 1) - ssDict['tin_wall'] = [5, 5, 5] - ssDict['alb_wall'] = [.5, .5, .5]#TODO - ssDict['emis_wall'] = [.95, .95, .95]#TODO - ssDict['state_wall'] = [.0, .0, .0] - ssDict['statelimit_wall'] = [5, 5, 5] - ssDict['wetthresh_wall'] = [5, 5, 5] - ssDict['soilstore_wall'] = [20, 20, 20] - ssDict['soilstorecap_wall'] = [120, 120, 120] - ssDict['wall_specular_frac'] = [0.,0.,0.] - - ssDict['dz_wall1'] = [.2, .1, .1, .01, .01]#TODO - ssDict['k_wall1'] = [1.2, 1.2, 1.2, 1.2, 1.2]#TODO - ssDict['cp_wall1'] = [3e6, 2e6, 2e6, 2e6, 2e6]#TODO - - ssDict['dz_wall2'] = [.2, .1, .1, .01, .01]#TODO - ssDict['k_wall2'] = [2.2, 1.2, 1.2, 1.2, 1.2]#TODO - ssDict['cp_wall2'] = [2e6, 3e6, 2e6, 2e6, 2e6]#TODO - - ssDict['dz_wall3'] = [.2, .1, .1, .01, .01]#TODO - ssDict['k_wall3'] = [2.2, 1.2, 1.2, 1.2, 1.2]#TODO - ssDict['cp_wall3'] = [2e6, 3e6, 2e6, 2e6, 2e6]#TODO + ssDict["sfr_wall"] = [0.3, 0.3, 0.4] # TODO # (sum should be 1) + ssDict["tin_wall"] = [5, 5, 5] + ssDict["alb_wall"] = [0.5, 0.5, 0.5] # TODO + ssDict["emis_wall"] = [0.95, 0.95, 0.95] # TODO + ssDict["state_wall"] = [0.0, 0.0, 0.0] + ssDict["statelimit_wall"] = [5, 5, 5] + ssDict["wetthresh_wall"] = [5, 5, 5] + ssDict["soilstore_wall"] = [20, 20, 20] + ssDict["soilstorecap_wall"] = [120, 120, 120] + ssDict["wall_specular_frac"] = [0.0, 0.0, 0.0] + + ssDict["dz_wall1"] = [0.2, 0.1, 0.1, 0.01, 0.01] # TODO + ssDict["k_wall1"] = [1.2, 1.2, 1.2, 1.2, 1.2] # TODO + ssDict["cp_wall1"] = [3e6, 2e6, 2e6, 2e6, 2e6] # TODO + + ssDict["dz_wall2"] = [0.2, 0.1, 0.1, 0.01, 0.01] # TODO + ssDict["k_wall2"] = [2.2, 1.2, 1.2, 1.2, 1.2] # TODO + ssDict["cp_wall2"] = [2e6, 3e6, 2e6, 2e6, 2e6] # TODO + + ssDict["dz_wall3"] = [0.2, 0.1, 0.1, 0.01, 0.01] # TODO + ssDict["k_wall3"] = [2.2, 1.2, 1.2, 1.2, 1.2] # TODO + ssDict["cp_wall3"] = [2e6, 3e6, 2e6, 2e6, 2e6] # TODO # surf # for generic SUEWS surfaces, used in storage heat flux calculations - ssDict['tin_surf'] = [2, 2, 2, 2, 2, 2, 2] # intitial temperature + ssDict["tin_surf"] = [2, 2, 2, 2, 2, 2, 2] # intitial temperature - ssDict['dz_surf_paved'] = [.2, .15, .01, .01, .01] - ssDict['k_surf_paved'] = [1.1, 1.1, 1.1, 1.1, 1.1] - ssDict['cp_surf_paved'] = [2.2e6, 2.2e6, 2.2e6, 2.2e6, 2.6e6] + ssDict["dz_surf_paved"] = [0.2, 0.15, 0.01, 0.01, 0.01] + ssDict["k_surf_paved"] = [1.1, 1.1, 1.1, 1.1, 1.1] + ssDict["cp_surf_paved"] = [2.2e6, 2.2e6, 2.2e6, 2.2e6, 2.6e6] - ssDict['dz_surf_buildings'] = [.2, .1, .1, .5, 1.6] - ssDict['k_surf_buildings'] = [1.2, 1.1, 1.1, 1.5, 1.6] - ssDict['cp_surf_buildings'] = [1.2e6, 1.1e6, 1.1e6, 1.5e6, 1.6e6] + ssDict["dz_surf_buildings"] = [0.2, 0.1, 0.1, 0.5, 1.6] + ssDict["k_surf_buildings"] = [1.2, 1.1, 1.1, 1.5, 1.6] + ssDict["cp_surf_buildings"] = [1.2e6, 1.1e6, 1.1e6, 1.5e6, 1.6e6] - ssDict['dz_surf_evergreen'] = [.2, .15, .01, .01, .01] - ssDict['k_surf_evergreen'] = [1.1, 1.1, 1.1, 1.1, 1.1] - ssDict['cp_surf_evergreen'] = [3.2e6, 1.1e6, 1.1e6, 1.5e6, 1.6e6] + ssDict["dz_surf_evergreen"] = [0.2, 0.15, 0.01, 0.01, 0.01] + ssDict["k_surf_evergreen"] = [1.1, 1.1, 1.1, 1.1, 1.1] + ssDict["cp_surf_evergreen"] = [3.2e6, 1.1e6, 1.1e6, 1.5e6, 1.6e6] - ssDict['dz_surf_decid'] = [.2, .1, .1, .1, 2.2] - ssDict['k_surf_decid'] = [1.2, 1.1, 1.1, 1.5, 1.6] - ssDict['cp_surf_decid'] = [3.2e6, 1.1e6, 1.1e6, 1.5e6, 1.6e6] + ssDict["dz_surf_decid"] = [0.2, 0.1, 0.1, 0.1, 2.2] + ssDict["k_surf_decid"] = [1.2, 1.1, 1.1, 1.5, 1.6] + ssDict["cp_surf_decid"] = [3.2e6, 1.1e6, 1.1e6, 1.5e6, 1.6e6] - ssDict['dz_surf_grass'] = [.2, .05, .1, .1, 2.2] - ssDict['k_surf_grass'] = [1.2, 1.1, 1.1, 1.5, 1.6] - ssDict['cp_surf_grass'] = [1.6e6, 1.1e6, 1.1e6, 1.5e6, 1.6e6] + ssDict["dz_surf_grass"] = [0.2, 0.05, 0.1, 0.1, 2.2] + ssDict["k_surf_grass"] = [1.2, 1.1, 1.1, 1.5, 1.6] + ssDict["cp_surf_grass"] = [1.6e6, 1.1e6, 1.1e6, 1.5e6, 1.6e6] - ssDict['dz_surf_baresoil'] = [.2, .05, .1, .1, 2.2] - ssDict['k_surf_baresoil'] = [1.2, 1.1, 1.1, 1.5, 1.6] - ssDict['cp_surf_baresoil'] = [1.9e6, 1.1e6, 1.1e6, 1.5e6, 1.6e6] + ssDict["dz_surf_baresoil"] = [0.2, 0.05, 0.1, 0.1, 2.2] + ssDict["k_surf_baresoil"] = [1.2, 1.1, 1.1, 1.5, 1.6] + ssDict["cp_surf_baresoil"] = [1.9e6, 1.1e6, 1.1e6, 1.5e6, 1.6e6] - ssDict['dz_surf_water'] = [.2, .05, .1, .1, 2.2] - ssDict['k_surf_water'] = [1.2, 1.1, 1.1, 1.5, 1.6] - ssDict['cp_surf_water'] = [1.9e6, 1.1e6, 1.1e6, 1.5e6, 1.6e6] + ssDict["dz_surf_water"] = [0.2, 0.05, 0.1, 0.1, 2.2] + ssDict["k_surf_water"] = [1.2, 1.1, 1.1, 1.5, 1.6] + ssDict["cp_surf_water"] = [1.9e6, 1.1e6, 1.1e6, 1.5e6, 1.6e6] return ssDict def write_GridLayout_file(ss_object, refdir, fname): - ss_file_path = os.path.join(refdir,fname+".nml") + ss_file_path = os.path.join(refdir, fname + ".nml") f = open(ss_file_path, "w") f.write("&dim\n") - f.write("nlayer = {}\n".format(ss_object['nlayer'])) + f.write("nlayer = {}\n".format(ss_object["nlayer"])) f.write("/\n") f.write("\n") f.write("&geom\n") - f.write("height = {}\n".format(str(ss_object['height'])[1:-1])) - f.write("building_frac = {}\n".format(str(ss_object['building_frac'])[1:-1])) - f.write("veg_frac = {}\n".format(str(ss_object['veg_frac'])[1:-1])) - f.write("building_scale = {}\n".format(str(ss_object['building_scale'])[1:-1])) - f.write("veg_scale = {}\n".format(str(ss_object['veg_scale'])[1:-1])) + f.write("height = {}\n".format(str(ss_object["height"])[1:-1])) + f.write( + "building_frac = {}\n".format(str(ss_object["building_frac"])[1:-1]) + ) + f.write("veg_frac = {}\n".format(str(ss_object["veg_frac"])[1:-1])) + f.write( + "building_scale = {}\n".format(str(ss_object["building_scale"])[1:-1]) + ) + f.write("veg_scale = {}\n".format(str(ss_object["veg_scale"])[1:-1])) f.write("/\n") f.write("\n") f.write("&roof\n") - f.write("sfr_roof = {}\n".format(str(ss_object['sfr_roof'])[1:-1])) - f.write("tin_roof = {}\n".format(str(ss_object['tin_roof'])[1:-1])) - f.write("alb_roof = {}\n".format(str(ss_object['alb_roof'])[1:-1])) - f.write("emis_roof = {}\n".format(str(ss_object['emis_roof'])[1:-1])) - f.write("state_roof = {}\n".format(str(ss_object['state_roof'])[1:-1])) - f.write("statelimit_roof = {}\n".format(str(ss_object['statelimit_roof'])[1:-1])) - f.write("wetthresh_roof = {}\n".format(str(ss_object['wetthresh_roof'])[1:-1])) - f.write("soilstore_roof = {}\n".format(str(ss_object['soilstore_roof'])[1:-1])) - f.write("soilstorecap_roof = {}\n".format(str(ss_object['soilstorecap_roof'])[1:-1])) - f.write("roof_albedo_dir_mult_fact(1,:) = {}\n".format(str(ss_object['roof_albedo_dir_mult_fact'])[1:-1])) - - + f.write("sfr_roof = {}\n".format(str(ss_object["sfr_roof"])[1:-1])) + f.write("tin_roof = {}\n".format(str(ss_object["tin_roof"])[1:-1])) + f.write("alb_roof = {}\n".format(str(ss_object["alb_roof"])[1:-1])) + f.write("emis_roof = {}\n".format(str(ss_object["emis_roof"])[1:-1])) + f.write("state_roof = {}\n".format(str(ss_object["state_roof"])[1:-1])) + f.write( + "statelimit_roof = {}\n".format( + str(ss_object["statelimit_roof"])[1:-1] + ) + ) + f.write( + "wetthresh_roof = {}\n".format(str(ss_object["wetthresh_roof"])[1:-1]) + ) + f.write( + "soilstore_roof = {}\n".format(str(ss_object["soilstore_roof"])[1:-1]) + ) + f.write( + "soilstorecap_roof = {}\n".format( + str(ss_object["soilstorecap_roof"])[1:-1] + ) + ) + f.write( + "roof_albedo_dir_mult_fact(1,:) = {}\n".format( + str(ss_object["roof_albedo_dir_mult_fact"])[1:-1] + ) + ) + # TODO make a nested loop based on nlayers - for j in range(1,ss_object['nlayer'] + 1): - f.write("dz_roof(" + str(j) + ",:) = {}\n".format(str(ss_object['dz_roof' + str(j)])[1:-1])) - f.write("k_roof(" + str(j) + ",:) = {}\n".format(str(ss_object['k_roof' + str(j)])[1:-1])) - f.write("cp_roof(" + str(j) + ",:) = {}\n".format(str(ss_object['cp_roof' + str(j)])[1:-1])) + for j in range(1, ss_object["nlayer"] + 1): + f.write( + "dz_roof(" + + str(j) + + ",:) = {}\n".format(str(ss_object["dz_roof" + str(j)])[1:-1]) + ) + f.write( + "k_roof(" + + str(j) + + ",:) = {}\n".format(str(ss_object["k_roof" + str(j)])[1:-1]) + ) + f.write( + "cp_roof(" + + str(j) + + ",:) = {}\n".format(str(ss_object["cp_roof" + str(j)])[1:-1]) + ) f.write("/\n") f.write("\n") - f.write("&wall\n") - f.write("sfr_wall = {}\n".format(str(ss_object['sfr_wall'])[1:-1])) - f.write("tin_wall = {}\n".format(str(ss_object['tin_wall'])[1:-1])) - f.write("alb_wall = {}\n".format(str(ss_object['alb_wall'])[1:-1])) - f.write("emis_wall = {}\n".format(str(ss_object['emis_wall'])[1:-1])) - f.write("state_wall = {}\n".format(str(ss_object['state_wall'])[1:-1])) - f.write("statelimit_wall = {}\n".format(str(ss_object['statelimit_wall'])[1:-1])) - f.write("wetthresh_wall = {}\n".format(str(ss_object['wetthresh_wall'])[1:-1])) - f.write("soilstore_wall = {}\n".format(str(ss_object['soilstore_wall'])[1:-1])) - f.write("soilstorecap_wall = {}\n".format(str(ss_object['soilstorecap_wall'])[1:-1])) - f.write("wall_specular_frac(1,:) = {}\n".format(str(ss_object['wall_specular_frac'])[1:-1])) - + f.write("&wall\n") + f.write("sfr_wall = {}\n".format(str(ss_object["sfr_wall"])[1:-1])) + f.write("tin_wall = {}\n".format(str(ss_object["tin_wall"])[1:-1])) + f.write("alb_wall = {}\n".format(str(ss_object["alb_wall"])[1:-1])) + f.write("emis_wall = {}\n".format(str(ss_object["emis_wall"])[1:-1])) + f.write("state_wall = {}\n".format(str(ss_object["state_wall"])[1:-1])) + f.write( + "statelimit_wall = {}\n".format( + str(ss_object["statelimit_wall"])[1:-1] + ) + ) + f.write( + "wetthresh_wall = {}\n".format(str(ss_object["wetthresh_wall"])[1:-1]) + ) + f.write( + "soilstore_wall = {}\n".format(str(ss_object["soilstore_wall"])[1:-1]) + ) + f.write( + "soilstorecap_wall = {}\n".format( + str(ss_object["soilstorecap_wall"])[1:-1] + ) + ) + f.write( + "wall_specular_frac(1,:) = {}\n".format( + str(ss_object["wall_specular_frac"])[1:-1] + ) + ) + # TODO make a nested loop based on nlayers - for j in range(1,ss_object['nlayer'] + 1): - f.write("dz_wall(" + str(j) + ",:) = {}\n".format(str(ss_object['dz_wall' + str(j)])[1:-1])) - f.write("k_wall(" + str(j) + ",:) = {}\n".format(str(ss_object['k_wall' + str(j)])[1:-1])) - f.write("cp_wall(" + str(j) + ",:) = {}\n".format(str(ss_object['cp_wall' + str(j)])[1:-1])) + for j in range(1, ss_object["nlayer"] + 1): + f.write( + "dz_wall(" + + str(j) + + ",:) = {}\n".format(str(ss_object["dz_wall" + str(j)])[1:-1]) + ) + f.write( + "k_wall(" + + str(j) + + ",:) = {}\n".format(str(ss_object["k_wall" + str(j)])[1:-1]) + ) + f.write( + "cp_wall(" + + str(j) + + ",:) = {}\n".format(str(ss_object["cp_wall" + str(j)])[1:-1]) + ) f.write("/\n") f.write("\n") - f.write("&surf\n") - f.write("tin_surf = {}\n".format(str(ss_object['tin_surf'])[1:-1])) - - f.write("dz_surf(1,:) = {}\n".format(str(ss_object['dz_surf_paved'])[1:-1])) - f.write("k_surf(1,:) = {}\n".format(str(ss_object['k_surf_paved'])[1:-1])) - f.write("cp_surf((1,:) = {}\n".format(str(ss_object['cp_surf_paved'])[1:-1])) - - f.write("dz_surf(2,:) = {}\n".format(str(ss_object['dz_surf_buildings'])[1:-1])) - f.write("k_surf(2,:) = {}\n".format(str(ss_object['k_surf_buildings'])[1:-1])) - f.write("cp_surf((2,:) = {}\n".format(str(ss_object['cp_surf_buildings'])[1:-1])) - - f.write("dz_surf(3,:) = {}\n".format(str(ss_object['dz_surf_evergreen'])[1:-1])) - f.write("k_surf(3,:) = {}\n".format(str(ss_object['k_surf_evergreen'])[1:-1])) - f.write("cp_surf((3,:) = {}\n".format(str(ss_object['cp_surf_evergreen'])[1:-1])) - - f.write("dz_surf(4,:) = {}\n".format(str(ss_object['dz_surf_decid'])[1:-1])) - f.write("k_surf(4,:) = {}\n".format(str(ss_object['k_surf_decid'])[1:-1])) - f.write("cp_surf((4,:) = {}\n".format(str(ss_object['cp_surf_decid'])[1:-1])) - - f.write("dz_surf(5,:) = {}\n".format(str(ss_object['dz_surf_grass'])[1:-1])) - f.write("k_surf(5,:) = {}\n".format(str(ss_object['k_surf_grass'])[1:-1])) - f.write("cp_surf((5,:) = {}\n".format(str(ss_object['cp_surf_grass'])[1:-1])) - - f.write("dz_surf(6,:) = {}\n".format(str(ss_object['dz_surf_baresoil'])[1:-1])) - f.write("k_surf(6,:) = {}\n".format(str(ss_object['k_surf_baresoil'])[1:-1])) - f.write("cp_surf((6,:) = {}\n".format(str(ss_object['cp_surf_baresoil'])[1:-1])) - - f.write("dz_surf(7,:) = {}\n".format(str(ss_object['dz_surf_water'])[1:-1])) - f.write("k_surf(7,:) = {}\n".format(str(ss_object['k_surf_water'])[1:-1])) - f.write("cp_surf((7,:) = {}\n".format(str(ss_object['cp_surf_water'])[1:-1])) + f.write("&surf\n") + f.write("tin_surf = {}\n".format(str(ss_object["tin_surf"])[1:-1])) + + f.write( + "dz_surf(1,:) = {}\n".format(str(ss_object["dz_surf_paved"])[1:-1]) + ) + f.write("k_surf(1,:) = {}\n".format(str(ss_object["k_surf_paved"])[1:-1])) + f.write( + "cp_surf((1,:) = {}\n".format(str(ss_object["cp_surf_paved"])[1:-1]) + ) + + f.write( + "dz_surf(2,:) = {}\n".format(str(ss_object["dz_surf_buildings"])[1:-1]) + ) + f.write( + "k_surf(2,:) = {}\n".format(str(ss_object["k_surf_buildings"])[1:-1]) + ) + f.write( + "cp_surf((2,:) = {}\n".format( + str(ss_object["cp_surf_buildings"])[1:-1] + ) + ) + + f.write( + "dz_surf(3,:) = {}\n".format(str(ss_object["dz_surf_evergreen"])[1:-1]) + ) + f.write( + "k_surf(3,:) = {}\n".format(str(ss_object["k_surf_evergreen"])[1:-1]) + ) + f.write( + "cp_surf((3,:) = {}\n".format( + str(ss_object["cp_surf_evergreen"])[1:-1] + ) + ) + + f.write( + "dz_surf(4,:) = {}\n".format(str(ss_object["dz_surf_decid"])[1:-1]) + ) + f.write("k_surf(4,:) = {}\n".format(str(ss_object["k_surf_decid"])[1:-1])) + f.write( + "cp_surf((4,:) = {}\n".format(str(ss_object["cp_surf_decid"])[1:-1]) + ) + + f.write( + "dz_surf(5,:) = {}\n".format(str(ss_object["dz_surf_grass"])[1:-1]) + ) + f.write("k_surf(5,:) = {}\n".format(str(ss_object["k_surf_grass"])[1:-1])) + f.write( + "cp_surf((5,:) = {}\n".format(str(ss_object["cp_surf_grass"])[1:-1]) + ) + + f.write( + "dz_surf(6,:) = {}\n".format(str(ss_object["dz_surf_baresoil"])[1:-1]) + ) + f.write( + "k_surf(6,:) = {}\n".format(str(ss_object["k_surf_baresoil"])[1:-1]) + ) + f.write( + "cp_surf((6,:) = {}\n".format(str(ss_object["cp_surf_baresoil"])[1:-1]) + ) + + f.write( + "dz_surf(7,:) = {}\n".format(str(ss_object["dz_surf_water"])[1:-1]) + ) + f.write("k_surf(7,:) = {}\n".format(str(ss_object["k_surf_water"])[1:-1])) + f.write( + "cp_surf((7,:) = {}\n".format(str(ss_object["cp_surf_water"])[1:-1]) + ) f.write("/\n") f.write("\n") @@ -206,6 +370,7 @@ def write_GridLayout_file(ss_object, refdir, fname): return ss_file_path + # def read_GridLayout_file(refdir, fname): # ss_file_path = os.path.join(refdir, fname + ".uwg") # # f = open(uwg_file_path, "r") @@ -228,7 +393,7 @@ def write_GridLayout_file(ss_object, refdir, fname): # skiptype = 2 # if skiptype == 0: # if line[0] == '#' or line == '\n': # empty line or comment -# test = 4 +# test = 4 # else: # regular input # a = line.find(',') # if line[0:a] == 'zone': @@ -258,7 +423,7 @@ def write_GridLayout_file(ss_object, refdir, fname): # l1.append(letter_list[0]) # l2.append(letter_list[1]) # l3.append(float(letter_list[2])) - + # # if skipcount < 3: # # for item in letter_list: # # if item == '\n': @@ -281,12 +446,5 @@ def write_GridLayout_file(ss_object, refdir, fname): # bldlist.append(l2) # bldlist.append(l3) # ssDict['bld'] = bldlist - -# return ssDict - - - - - - +# return ssDict diff --git a/util/umep_target_export_component.py b/util/umep_target_export_component.py index 5d62e54..3a47095 100644 --- a/util/umep_target_export_component.py +++ b/util/umep_target_export_component.py @@ -1,10 +1,11 @@ import os -''' +""" input: TargetDict: dictonary with all relevant inputs refdir = outputfolder -''' +""" + def write_config_file(targetDict, refdir): config_file_path = os.path.join(refdir, "config.ini") @@ -18,39 +19,43 @@ def write_config_file(targetDict, refdir): f.write("# =================================================\n") f.write("### INPUTS ###\n") f.write("# output path\n") - f.write("work_dir={}\n".format(targetDict['work_dir'])) + f.write("work_dir={}\n".format(targetDict["work_dir"])) f.write("# parameters json file\n") - f.write("para_json_path={}\n".format(targetDict['para_json_path'])) + f.write("para_json_path={}\n".format(targetDict["para_json_path"])) f.write("# site name (string)\n") - f.write("site_name={}\n".format(targetDict['site_name'])) + f.write("site_name={}\n".format(targetDict["site_name"])) f.write("# run name (string)\n") - f.write("run_name={}\n".format(targetDict['run_name'])) + f.write("run_name={}\n".format(targetDict["run_name"])) f.write("# input meteorolgical file (i.e. forcing file)\n") - f.write("inpt_met_file={}\n".format(targetDict['inpt_met_file'])) + f.write("inpt_met_file={}\n".format(targetDict["inpt_met_file"])) f.write("# input land cover data file\n") - f.write("inpt_lc_file={}\n".format(targetDict['inpt_lc_file'])) + f.write("inpt_lc_file={}\n".format(targetDict["inpt_lc_file"])) f.write("# format of datetime in input met files\n") - f.write("date_fmt={}\n".format(targetDict['date_fmt'])) + f.write("date_fmt={}\n".format(targetDict["date_fmt"])) f.write("# time step (minutes)\n") - f.write("timestep={}\n".format(targetDict['timestep'])) + f.write("timestep={}\n".format(targetDict["timestep"])) f.write("\n") - f.write("include roofs={}\n".format(targetDict['include roofs'])) - f.write("mod_ldwn={}\n".format(targetDict['mod_ldwn'])) - f.write("domainDim={}\n".format(targetDict['domaindim'])) - f.write("latEdge={}\n".format(targetDict['latedge'])) - f.write("lonEdge={}\n".format(targetDict['lonedge'])) - f.write("latResolution={}\n".format(targetDict['latresolution'])) - f.write("lonResolution={}\n".format(targetDict['lonresolution'])) + f.write("include roofs={}\n".format(targetDict["include roofs"])) + f.write("mod_ldwn={}\n".format(targetDict["mod_ldwn"])) + f.write("domainDim={}\n".format(targetDict["domaindim"])) + f.write("latEdge={}\n".format(targetDict["latedge"])) + f.write("lonEdge={}\n".format(targetDict["lonedge"])) + f.write("latResolution={}\n".format(targetDict["latresolution"])) + f.write("lonResolution={}\n".format(targetDict["lonresolution"])) f.write("\n") f.write("#----------------------------------\n") f.write("# dates\n") f.write("#----------------------------------\n") - f.write("# year,month,day,hour # start date for simulation (should be a minimum of 24 hours prior to date1)\n") - f.write("date1a={}\n".format(targetDict['date1a'])) - f.write("# year,month,day,hour # the date/time for period of interest (i.e. before this will not be saved)\n") - f.write("date1={}\n".format(targetDict['date1'])) - f.write("# year,month,day,hour # end date for validation period\n") - f.write("date2={}\n".format(targetDict['date2'])) + f.write( + "# year,month,day,hour # start date for simulation (should be a minimum of 24 hours prior to date1)\n" + ) + f.write("date1a={}\n".format(targetDict["date1a"])) + f.write( + "# year,month,day,hour # the date/time for period of interest (i.e. before this will not be saved)\n" + ) + f.write("date1={}\n".format(targetDict["date1"])) + f.write("# year,month,day,hour # end date for validation period\n") + f.write("date2={}\n".format(targetDict["date2"])) f.write("\n") f.close() diff --git a/util/umep_uwg_export_component.py b/util/umep_uwg_export_component.py index b32283d..3596459 100644 --- a/util/umep_uwg_export_component.py +++ b/util/umep_uwg_export_component.py @@ -1,7 +1,7 @@ # import cPickle import os -''' +""" This module is adjusted from dragonfly_uwg_export_component to transform UMEP pre-processing data to fit uwg @@ -9,41 +9,139 @@ uwg_object: dictonary with all relevant inputs refdir = outputfolder fname = filename -''' +""" + def create_uwgdict(): - + uwgDict = {} # Urban characteristics - uwgDict['bldHeight'] = 10 # average building height (m) - uwgDict['bldDensity'] = 0.5 # urban area building plan density (0-1) - uwgDict['verToHor'] = 0.8 # urban area vertical to horizontal ratio - uwgDict['h_mix'] = 1 # fraction of building HVAC waste heat set to the street canyon [as opposed to the roof] - uwgDict['charLength'] = 1000 # dimension of a square that encompasses the whole neighborhood [aka. characteristic length] (m) - uwgDict['albRoad'] = 0.1 # road albedo (0 - 1) - uwgDict['dRoad'] = 0.5 # road pavement thickness (m) - uwgDict['kRoad'] = 1 # road pavement conductivity (W/m K) - uwgDict['cRoad'] = 1600000 # road volumetric heat capacity (J/m^3 K) - uwgDict['sensAnth'] = 20 # non-building sensible heat at street level [aka. heat from cars, pedestrians, street cooking, etc. ] (W/m^2) + uwgDict["bldHeight"] = 10 # average building height (m) + uwgDict["bldDensity"] = 0.5 # urban area building plan density (0-1) + uwgDict["verToHor"] = 0.8 # urban area vertical to horizontal ratio + uwgDict["h_mix"] = ( + 1 # fraction of building HVAC waste heat set to the street canyon [as opposed to the roof] + ) + uwgDict["charLength"] = ( + 1000 # dimension of a square that encompasses the whole neighborhood [aka. characteristic length] (m) + ) + uwgDict["albRoad"] = 0.1 # road albedo (0 - 1) + uwgDict["dRoad"] = 0.5 # road pavement thickness (m) + uwgDict["kRoad"] = 1 # road pavement conductivity (W/m K) + uwgDict["cRoad"] = 1600000 # road volumetric heat capacity (J/m^3 K) + uwgDict["sensAnth"] = ( + 20 # non-building sensible heat at street level [aka. heat from cars, pedestrians, street cooking, etc. ] (W/m^2) + ) # Climate Zone (Eg. City) - uwgDict['zone'] = '1A' + uwgDict["zone"] = "1A" # Vegetation parameters - uwgDict['grasscover'] = 0.1 # Fraction of the urban ground covered in grass/shrubs only (0-1) - uwgDict['treeCover'] = 0.1 # Fraction of the urban ground covered in trees (0-1) - uwgDict['vegStart'] = 4 # The month in which vegetation starts to evapotranspire (leaves are out) - uwgDict['vegEnd'] = 10 # The month in which vegetation stops evapotranspiring (leaves fall) - uwgDict['albVeg'] = 0.25 # Vegetation albedo - uwgDict['latGrss'] = 0.4 # Fraction of the heat absorbed by grass that is latent (goes to evaporating water) - uwgDict['latTree'] = 0.6 # Fraction of the heat absorbed by trees that is latent (goes to evaporating water) - uwgDict['rurVegCover'] = 0.9 # Fraction of the rural ground covered by vegetation + uwgDict["grasscover"] = ( + 0.1 # Fraction of the urban ground covered in grass/shrubs only (0-1) + ) + uwgDict["treeCover"] = ( + 0.1 # Fraction of the urban ground covered in trees (0-1) + ) + uwgDict["vegStart"] = ( + 4 # The month in which vegetation starts to evapotranspire (leaves are out) + ) + uwgDict["vegEnd"] = ( + 10 # The month in which vegetation stops evapotranspiring (leaves fall) + ) + uwgDict["albVeg"] = 0.25 # Vegetation albedo + uwgDict["latGrss"] = ( + 0.4 # Fraction of the heat absorbed by grass that is latent (goes to evaporating water) + ) + uwgDict["latTree"] = ( + 0.6 # Fraction of the heat absorbed by trees that is latent (goes to evaporating water) + ) + uwgDict["rurVegCover"] = ( + 0.9 # Fraction of the rural ground covered by vegetation + ) # Traffic schedule [1 to 24 hour],# Weekday# Saturday# Sunday - uwgDict['SchTraffic'] = [[0.2,0.2,0.2,0.2,0.2,0.4,0.7,0.9,0.9,0.6,0.6,0.6,0.6,0.6,0.7,0.8,0.9,0.9,0.8,0.8,0.7,0.3,0.2,0.2], - [0.2,0.2,0.2,0.2,0.2,0.3,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.6,0.7,0.7,0.7,0.7,0.5,0.4,0.3,0.2,0.2], - [0.2,0.2,0.2,0.2,0.2,0.3,0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.3,0.3,0.2,0.2]] + uwgDict["SchTraffic"] = [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.4, + 0.7, + 0.9, + 0.9, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.7, + 0.8, + 0.9, + 0.9, + 0.8, + 0.8, + 0.7, + 0.3, + 0.2, + 0.2, + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.3, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.6, + 0.7, + 0.7, + 0.7, + 0.7, + 0.5, + 0.4, + 0.3, + 0.2, + 0.2, + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.3, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.3, + 0.3, + 0.2, + 0.2, + ], + ] # Fraction of building stock for each DOE Building type (pre-80's build, 80's-present build, new) # Note that sum(bld) must be equal to 1 @@ -53,56 +151,106 @@ def create_uwgdict(): # 'Pst80' # 'New' - uwgDict['bld'] = [['FullServiceRestaurant','Hospital','LargeHotel','LargeOffice','MedOffice','MidRiseApartment','OutPatient','PrimarySchool','QuickServiceRestaurant','SecondarySchool','SmallHotel','SmallOffice','StandAloneRetail','StripMall','SuperMarket','Warehouse'], - ['Pst80','Pst80','Pst80','Pst80','Pst80','Pst80','Pst80','Pst80','Pst80','Pst80','Pst80','Pst80','Pst80','Pst80','Pst80','Pst80'], - [0,0,0,0.4,0,0.6,0,0,0,0,0,0,0,0,0,0]] + uwgDict["bld"] = [ + [ + "FullServiceRestaurant", + "Hospital", + "LargeHotel", + "LargeOffice", + "MedOffice", + "MidRiseApartment", + "OutPatient", + "PrimarySchool", + "QuickServiceRestaurant", + "SecondarySchool", + "SmallHotel", + "SmallOffice", + "StandAloneRetail", + "StripMall", + "SuperMarket", + "Warehouse", + ], + [ + "Pst80", + "Pst80", + "Pst80", + "Pst80", + "Pst80", + "Pst80", + "Pst80", + "Pst80", + "Pst80", + "Pst80", + "Pst80", + "Pst80", + "Pst80", + "Pst80", + "Pst80", + "Pst80", + ], + [0, 0, 0, 0.4, 0, 0.6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ] # ================================================= # OPTIONAL URBAN PARAMETERS # ================================================= # If not provided, optional parameters are taken from corresponding DOE Reference building - uwgDict['albRoof'] = None # roof albedo (0 - 1) - uwgDict['vegRoof'] = None # Fraction of the roofs covered in grass/shrubs (0 - 1) - uwgDict['glzR'] = None # Glazing Ratio (0 - 1) - uwgDict['SHGC'] = None # Solar Heat Gain Coefficient (0 - 1) - uwgDict['albWall'] = None # wall albedo (0 - 1) - uwgDict['flr_h'] = None # average building floor height + uwgDict["albRoof"] = None # roof albedo (0 - 1) + uwgDict["vegRoof"] = ( + None # Fraction of the roofs covered in grass/shrubs (0 - 1) + ) + uwgDict["glzR"] = None # Glazing Ratio (0 - 1) + uwgDict["SHGC"] = None # Solar Heat Gain Coefficient (0 - 1) + uwgDict["albWall"] = None # wall albedo (0 - 1) + uwgDict["flr_h"] = None # average building floor height # =================================================, # OPTIONAL PARAMETERS FOR SIMULATION CONTROL, # =================================================, # Simulation parameters, - uwgDict['Month'] = 10 # starting month (1-12) - uwgDict['Day'] = 1 # starting day (1-31) - uwgDict['nDay'] = 10 # number of days to run simultion - uwgDict['dtSim'] = 300 # simulation time step (s) - uwgDict['dtWeather'] = 3600 # weather time step (s) + uwgDict["Month"] = 10 # starting month (1-12) + uwgDict["Day"] = 1 # starting day (1-31) + uwgDict["nDay"] = 10 # number of days to run simultion + uwgDict["dtSim"] = 300 # simulation time step (s) + uwgDict["dtWeather"] = 3600 # weather time step (s) - uwgDict['autosize'] = 0 # autosize HVAC (1 for yes; 0 for no) - uwgDict['sensOcc'] = 100 # Sensible heat per occupant (W) - uwgDict['LatFOcc'] = 0.3 # Latent heat fraction from occupant (normally 0.3) - uwgDict['RadFOcc'] = 0.2 # Radiant heat fraction from occupant (normally 0.2) - uwgDict['RadFEquip'] = 0.5 # Radiant heat fraction from equipment (normally 0.5) - uwgDict['RadFLight'] = 0.7 # Radiant heat fraction from light (normally 0.7) + uwgDict["autosize"] = 0 # autosize HVAC (1 for yes; 0 for no) + uwgDict["sensOcc"] = 100 # Sensible heat per occupant (W) + uwgDict["LatFOcc"] = ( + 0.3 # Latent heat fraction from occupant (normally 0.3) + ) + uwgDict["RadFOcc"] = ( + 0.2 # Radiant heat fraction from occupant (normally 0.2) + ) + uwgDict["RadFEquip"] = ( + 0.5 # Radiant heat fraction from equipment (normally 0.5) + ) + uwgDict["RadFLight"] = ( + 0.7 # Radiant heat fraction from light (normally 0.7) + ) - #Urban climate parameters - uwgDict['h_ubl1'] = 1000 # ubl height - day (m) - uwgDict['h_ubl2'] = 80 # ubl height - night (m) - uwgDict['h_ref'] = 150 # inversion height (m) - uwgDict['h_temp'] = 2 # temperature height (m) - uwgDict['h_wind'] = 10 # wind height (m) - uwgDict['c_circ'] = 1.2 # circulation coefficient (default = 1.2 per Bruno (2012)) - uwgDict['c_exch'] = 1 # exchange coefficient (default = 1; ref Bruno (2014)) - uwgDict['maxDay'] = 150 # max day threshold (W/m^2) - uwgDict['maxNight'] = 20 # max night threshold (W/m^2) - uwgDict['windMin'] = 1 # min wind speed (m/s) - uwgDict['h_obs'] = 0.1 # rural average obstacle height (m) + # Urban climate parameters + uwgDict["h_ubl1"] = 1000 # ubl height - day (m) + uwgDict["h_ubl2"] = 80 # ubl height - night (m) + uwgDict["h_ref"] = 150 # inversion height (m) + uwgDict["h_temp"] = 2 # temperature height (m) + uwgDict["h_wind"] = 10 # wind height (m) + uwgDict["c_circ"] = ( + 1.2 # circulation coefficient (default = 1.2 per Bruno (2012)) + ) + uwgDict["c_exch"] = ( + 1 # exchange coefficient (default = 1; ref Bruno (2014)) + ) + uwgDict["maxDay"] = 150 # max day threshold (W/m^2) + uwgDict["maxNight"] = 20 # max night threshold (W/m^2) + uwgDict["windMin"] = 1 # min wind speed (m/s) + uwgDict["h_obs"] = 0.1 # rural average obstacle height (m) return uwgDict def get_uwg_file(uwg_object, refdir, fname): - uwg_file_path = os.path.join(refdir,fname+".uwg") + uwg_file_path = os.path.join(refdir, fname + ".uwg") f = open(uwg_file_path, "w") f.write("# =================================================\n") @@ -110,92 +258,179 @@ def get_uwg_file(uwg_object, refdir, fname): f.write("# =================================================\n") f.write("\n") f.write("# Urban characteristics\n") - f.write("bldHeight,{},\n".format(uwg_object['bldHeight'])) - f.write("bldDensity,{},\n".format(uwg_object['bldDensity'])) - f.write("verToHor,{},\n".format(uwg_object['verToHor'])) - f.write("h_mix,{},\n".format(uwg_object['h_mix'])) - f.write("charLength,{},\n".format(uwg_object['charLength'])) # dimension of a square that encompasses the whole neighborhood [aka. characteristic length] (m) - f.write("albRoad,{},\n".format(uwg_object['albRoad'])) # road albedo (0 - 1) - f.write("dRoad,{},\n".format(uwg_object['dRoad'])) # road pavement thickness (m) - f.write("kRoad,{},\n".format(uwg_object['kRoad'])) # road pavement conductivity (W/m K) - f.write("cRoad,{},\n".format(uwg_object['cRoad'])) # road volumetric heat capacity (J/m^3 K) - f.write("sensAnth,{},\n".format(uwg_object['sensAnth'])) # non-building sensible heat at street level [aka. heat from cars, pedestrians, street cooking, etc. ] (W/m^2) + f.write("bldHeight,{},\n".format(uwg_object["bldHeight"])) + f.write("bldDensity,{},\n".format(uwg_object["bldDensity"])) + f.write("verToHor,{},\n".format(uwg_object["verToHor"])) + f.write("h_mix,{},\n".format(uwg_object["h_mix"])) + f.write( + "charLength,{},\n".format(uwg_object["charLength"]) + ) # dimension of a square that encompasses the whole neighborhood [aka. characteristic length] (m) + f.write( + "albRoad,{},\n".format(uwg_object["albRoad"]) + ) # road albedo (0 - 1) + f.write( + "dRoad,{},\n".format(uwg_object["dRoad"]) + ) # road pavement thickness (m) + f.write( + "kRoad,{},\n".format(uwg_object["kRoad"]) + ) # road pavement conductivity (W/m K) + f.write( + "cRoad,{},\n".format(uwg_object["cRoad"]) + ) # road volumetric heat capacity (J/m^3 K) + f.write( + "sensAnth,{},\n".format(uwg_object["sensAnth"]) + ) # non-building sensible heat at street level [aka. heat from cars, pedestrians, street cooking, etc. ] (W/m^2) # f.write("latAnth,{},\n".format(uwg_object['latAnth'])) # non-building latent heat (W/m^2) (currently not used) f.write("\n") - f.write("zone,{},\n".format(uwg_object['zone'])) + f.write("zone,{},\n".format(uwg_object["zone"])) f.write("\n") f.write("# Vegetation parameters\n") - f.write("grasscover,{},\n".format(uwg_object['grasscover'])) # Fraction of the urban ground covered in grass/shrubs only (0-1) - f.write("treeCover,{},\n".format(uwg_object['treeCover'])) # Fraction of the urban ground covered in trees (0-1) - f.write("vegStart,{},\n".format(uwg_object['vegStart'])) # The month in which vegetation starts to evapotranspire (leaves are out) - f.write("vegEnd,{},\n".format(uwg_object['vegEnd'])) # The month in which vegetation stops evapotranspiring (leaves fall) - f.write("albVeg,{},\n".format(uwg_object['albVeg'])) # Vegetation albedo - f.write("rurVegCover,{},\n".format(uwg_object['rurVegCover'])) # Fraction of the rural ground covered by vegetation - f.write("latGrss,{},\n".format(uwg_object['latGrss'])) # Fraction of the heat absorbed by grass that is latent. Used in UWG only to calculate sensible heat fraction. - f.write("latTree,{},\n".format(uwg_object['latTree'])) # Fraction of the heat absorbed by trees that is latent. Used in UWG only to calculate sensible heat fraction. + f.write( + "grasscover,{},\n".format(uwg_object["grasscover"]) + ) # Fraction of the urban ground covered in grass/shrubs only (0-1) + f.write( + "treeCover,{},\n".format(uwg_object["treeCover"]) + ) # Fraction of the urban ground covered in trees (0-1) + f.write( + "vegStart,{},\n".format(uwg_object["vegStart"]) + ) # The month in which vegetation starts to evapotranspire (leaves are out) + f.write( + "vegEnd,{},\n".format(uwg_object["vegEnd"]) + ) # The month in which vegetation stops evapotranspiring (leaves fall) + f.write("albVeg,{},\n".format(uwg_object["albVeg"])) # Vegetation albedo + f.write( + "rurVegCover,{},\n".format(uwg_object["rurVegCover"]) + ) # Fraction of the rural ground covered by vegetation + f.write( + "latGrss,{},\n".format(uwg_object["latGrss"]) + ) # Fraction of the heat absorbed by grass that is latent. Used in UWG only to calculate sensible heat fraction. + f.write( + "latTree,{},\n".format(uwg_object["latTree"]) + ) # Fraction of the heat absorbed by trees that is latent. Used in UWG only to calculate sensible heat fraction. f.write("\n") f.write("# Traffic schedule [1 to 24 hour],\n") f.write("SchTraffic,\n") for i in range(3): for j in range(24): - f.write("{},".format(uwg_object['SchTraffic'][i][j])) + f.write("{},".format(uwg_object["SchTraffic"][i][j])) f.write("\n") f.write("\n") - f.write("# Fraction of building stock for each DOE Building type (pre-80's build, 80's-present build, new)\n") + f.write( + "# Fraction of building stock for each DOE Building type (pre-80's build, 80's-present build, new)\n" + ) f.write("# Note that sum(bld) must be equal to 1\n") f.write("bld,\n") for i in range(16): for j in range(3): - f.write("{},".format(uwg_object['bld'][j][i])) + f.write("{},".format(uwg_object["bld"][j][i])) f.write("\n") f.write("\n") f.write("# =================================================\n") f.write("# OPTIONAL URBAN PARAMETERS\n") f.write("# =================================================\n") - f.write("# If not provided, optional parameters are taken from corresponding DOE Reference building\n") - f.write("albRoof,{},\n".format(uwg_object['albRoof'] if uwg_object['albRoof'] else "")) # roof albedo (0 - 1) - f.write("vegRoof,{},\n".format(uwg_object['vegRoof'] if uwg_object['vegRoof'] else "")) # Fraction of the roofs covered in grass/shrubs (0 - 1) - f.write("glzR,{},\n".format(uwg_object['glzR'] if uwg_object['glzR'] else "")) # Glazing Ratio (0 - 1) - f.write("SHGC,{},\n".format(uwg_object['SHGC'] if uwg_object['SHGC'] else "")) # Solar Heat Gain Coefficient (0 - 1) - f.write("albWall,{},\n".format(uwg_object['albWall'] if uwg_object['albWall'] else "")) # wall albedo (0 - 1) - f.write("flr_h,{},\n".format(uwg_object['flr_h'] if uwg_object['flr_h'] else "")) # average building floor height + f.write( + "# If not provided, optional parameters are taken from corresponding DOE Reference building\n" + ) + f.write( + "albRoof,{},\n".format( + uwg_object["albRoof"] if uwg_object["albRoof"] else "" + ) + ) # roof albedo (0 - 1) + f.write( + "vegRoof,{},\n".format( + uwg_object["vegRoof"] if uwg_object["vegRoof"] else "" + ) + ) # Fraction of the roofs covered in grass/shrubs (0 - 1) + f.write( + "glzR,{},\n".format(uwg_object["glzR"] if uwg_object["glzR"] else "") + ) # Glazing Ratio (0 - 1) + f.write( + "SHGC,{},\n".format(uwg_object["SHGC"] if uwg_object["SHGC"] else "") + ) # Solar Heat Gain Coefficient (0 - 1) + f.write( + "albWall,{},\n".format( + uwg_object["albWall"] if uwg_object["albWall"] else "" + ) + ) # wall albedo (0 - 1) + f.write( + "flr_h,{},\n".format( + uwg_object["flr_h"] if uwg_object["flr_h"] else "" + ) + ) # average building floor height f.write("\n") f.write("# =================================================\n") f.write("# OPTIONAL PARAMETERS FOR SIMULATION CONTROL,\n") f.write("# =================================================\n") f.write("\n") f.write("# Simulation parameters,\n") - f.write("Month,{},\n".format(uwg_object['Month'])) # starting month (1-12) - f.write("Day,{},\n".format(uwg_object['Day'])) # starting day (1-31) - f.write("nDay,{},\n".format(uwg_object['nDay'])) # number of days to run simultion - f.write("dtSim,{},\n".format(uwg_object['dtSim'])) # simulation time step (s) - f.write("dtWeather,{},\n".format(uwg_object['dtWeather'])), # weather time step (s) + f.write("Month,{},\n".format(uwg_object["Month"])) # starting month (1-12) + f.write("Day,{},\n".format(uwg_object["Day"])) # starting day (1-31) + f.write( + "nDay,{},\n".format(uwg_object["nDay"]) + ) # number of days to run simultion + f.write( + "dtSim,{},\n".format(uwg_object["dtSim"]) + ) # simulation time step (s) + f.write( + "dtWeather,{},\n".format(uwg_object["dtWeather"]) + ), # weather time step (s) f.write("\n") f.write("# HVAC system and internal loads\n") - f.write("autosize,{},\n".format(uwg_object['autosize'])) # autosize HVAC (1 for yes; 0 for no) - f.write("sensOcc,{},\n".format(uwg_object['sensOcc'])) # Sensible heat per occupant (W) - f.write("LatFOcc,{},\n".format(uwg_object['LatFOcc'])) # Latent heat fraction from occupant (normally 0.3) - f.write("RadFOcc,{},\n".format(uwg_object['RadFOcc'])) # Radiant heat fraction from occupant (normally 0.2) - f.write("RadFEquip,{},\n".format(uwg_object['RadFEquip'])) # Radiant heat fraction from equipment (normally 0.5) - f.write("RadFLight,{},\n".format(uwg_object['RadFLight'])) # Radiant heat fraction from light (normally 0.7) + f.write( + "autosize,{},\n".format(uwg_object["autosize"]) + ) # autosize HVAC (1 for yes; 0 for no) + f.write( + "sensOcc,{},\n".format(uwg_object["sensOcc"]) + ) # Sensible heat per occupant (W) + f.write( + "LatFOcc,{},\n".format(uwg_object["LatFOcc"]) + ) # Latent heat fraction from occupant (normally 0.3) + f.write( + "RadFOcc,{},\n".format(uwg_object["RadFOcc"]) + ) # Radiant heat fraction from occupant (normally 0.2) + f.write( + "RadFEquip,{},\n".format(uwg_object["RadFEquip"]) + ) # Radiant heat fraction from equipment (normally 0.5) + f.write( + "RadFLight,{},\n".format(uwg_object["RadFLight"]) + ) # Radiant heat fraction from light (normally 0.7) f.write("\n") f.write("#Urban climate parameters\n") - f.write("h_ubl1,{},\n".format(uwg_object['h_ubl1'])) # ubl height - day (m) - f.write("h_ubl2,{},\n".format(uwg_object['h_ubl2'])) # ubl height - night (m) - f.write("h_ref,{},\n".format(uwg_object['h_ref'])) # inversion height (m) - f.write("h_temp,{},\n".format(uwg_object['h_temp'])) # temperature height (m) - f.write("h_wind,{},\n".format(uwg_object['h_wind'])) # wind height (m) - f.write("c_circ,{},\n".format(uwg_object['c_circ'])) # circulation coefficient (default = 1.2 per Bruno (2012)) - f.write("c_exch,{},\n".format(uwg_object['c_exch'])) # exchange coefficient (default = 1; ref Bruno (2014)) - f.write("maxDay,{},\n".format(uwg_object['maxDay'])) # max day threshold (W/m^2) - f.write("maxNight,{},\n".format(uwg_object['maxNight'])) # max night threshold (W/m^2) - f.write("windMin,{},\n".format(uwg_object['windMin'])) # min wind speed (m/s) - f.write("h_obs,{},\n".format(uwg_object['h_obs'])) # rural average obstacle height (m) + f.write( + "h_ubl1,{},\n".format(uwg_object["h_ubl1"]) + ) # ubl height - day (m) + f.write( + "h_ubl2,{},\n".format(uwg_object["h_ubl2"]) + ) # ubl height - night (m) + f.write("h_ref,{},\n".format(uwg_object["h_ref"])) # inversion height (m) + f.write( + "h_temp,{},\n".format(uwg_object["h_temp"]) + ) # temperature height (m) + f.write("h_wind,{},\n".format(uwg_object["h_wind"])) # wind height (m) + f.write( + "c_circ,{},\n".format(uwg_object["c_circ"]) + ) # circulation coefficient (default = 1.2 per Bruno (2012)) + f.write( + "c_exch,{},\n".format(uwg_object["c_exch"]) + ) # exchange coefficient (default = 1; ref Bruno (2014)) + f.write( + "maxDay,{},\n".format(uwg_object["maxDay"]) + ) # max day threshold (W/m^2) + f.write( + "maxNight,{},\n".format(uwg_object["maxNight"]) + ) # max night threshold (W/m^2) + f.write( + "windMin,{},\n".format(uwg_object["windMin"]) + ) # min wind speed (m/s) + f.write( + "h_obs,{},\n".format(uwg_object["h_obs"]) + ) # rural average obstacle height (m) f.close() return uwg_file_path + def read_uwg_file(refdir, fname): uwg_file_path = os.path.join(refdir, fname + ".uwg") # f = open(uwg_file_path, "r") @@ -212,27 +447,27 @@ def read_uwg_file(refdir, fname): with open(uwg_file_path) as file: # next(file) for line in file: - if line[0:7] == 'SchTraf': + if line[0:7] == "SchTraf": skiptype = 1 - if line[0:4] == 'bld,': + if line[0:4] == "bld,": skiptype = 2 if skiptype == 0: - if line[0] == '#' or line == '\n': # empty line or comment - test = 4 - else: # regular input - a = line.find(',') - if line[0:a] == 'zone': - uwgdict[line[0:a]] = line[a +1: len(line) - 2] - elif line[-3:] == ',,\n': + if line[0] == "#" or line == "\n": # empty line or comment + test = 4 + else: # regular input + a = line.find(",") + if line[0:a] == "zone": + uwgdict[line[0:a]] = line[a + 1 : len(line) - 2] + elif line[-3:] == ",,\n": uwgdict[line[0:a]] = None else: - uwgdict[line[0:a]] = float(line[a +1: len(line) - 2]) - elif skiptype == 1: # Traffic + uwgdict[line[0:a]] = float(line[a + 1 : len(line) - 2]) + elif skiptype == 1: # Traffic if skipcount >= 1: letter_list = line.split(",") floats_list = [] for item in letter_list: - if item == '\n': + if item == "\n": test = 4 else: floats_list.append(float(item)) @@ -241,14 +476,14 @@ def read_uwg_file(refdir, fname): if skipcount == 4: skipcount = 0 skiptype = 0 - uwgdict['SchTraffic'] = trafficlist - elif skiptype == 2: #Buildings + uwgdict["SchTraffic"] = trafficlist + elif skiptype == 2: # Buildings if skipcount >= 1: letter_list = line.split(",") l1.append(letter_list[0]) l2.append(letter_list[1]) l3.append(float(letter_list[2])) - + # if skipcount < 3: # for item in letter_list: # if item == '\n': @@ -270,13 +505,6 @@ def read_uwg_file(refdir, fname): bldlist.append(l1) bldlist.append(l2) bldlist.append(l3) - uwgdict['bld'] = bldlist - - return uwgdict - - - - - - + uwgdict["bld"] = bldlist + return uwgdict From 92a7eaaa176742edfa25dcb3cdbe56b5fcb31c35 Mon Sep 17 00:00:00 2001 From: "Mael.PHILIPPE" Date: Mon, 18 May 2026 15:18:50 +0200 Subject: [PATCH 02/20] added gpu processing for SOLWEIG --- functions/SOLWEIGpython/Kside_veg_v2022a.py | 240 +++++++------ functions/SOLWEIGpython/Kup_veg_2015a.py | 12 +- functions/SOLWEIGpython/Kvikt_veg.py | 16 +- functions/SOLWEIGpython/Lside_veg.py | 148 +++++--- functions/SOLWEIGpython/Lside_veg_v2015a.py | 58 +-- functions/SOLWEIGpython/Lvikt_veg.py | 15 + .../Solweig_2026a_calc_forprocessing.py | 114 +++--- functions/SOLWEIGpython/Solweig_run.py | 332 ++++++++++-------- functions/SOLWEIGpython/Tgmaps_v1.py | 25 +- functions/SOLWEIGpython/cylindric_wedge.py | 69 ++-- functions/SOLWEIGpython/daylen.py | 18 +- functions/SOLWEIGpython/ground_surface.py | 237 +++++++------ processor/configsolweig.ini | 24 +- .../Solweig_v2015_metdata_noload.py | 44 +-- .../clearnessindex_2013b.py | 23 +- util/SEBESOLWEIGCommonFiles/create_patches.py | 1 - .../SEBESOLWEIGCommonFiles/diffusefraction.py | 13 +- .../shadowingfunction_wallheight_13.py | 1 + .../shadowingfunction_wallheight_23.py | 141 ++++---- util/SEBESOLWEIGCommonFiles/sun_distance.py | 14 +- util/SEBESOLWEIGCommonFiles/sun_position.py | 318 ++++++++--------- 21 files changed, 996 insertions(+), 867 deletions(-) diff --git a/functions/SOLWEIGpython/Kside_veg_v2022a.py b/functions/SOLWEIGpython/Kside_veg_v2022a.py index bc480a3..15ebcd0 100644 --- a/functions/SOLWEIGpython/Kside_veg_v2022a.py +++ b/functions/SOLWEIGpython/Kside_veg_v2022a.py @@ -2,6 +2,7 @@ import numpy as np from .Kvikt_veg import Kvikt_veg from . import sunlit_shaded_patches +import torch def Kside_veg_v2022a( @@ -38,6 +39,13 @@ def Kside_veg_v2022a( vegshmat, vbshvegshmat, ): + device = ( + altitude.device + if isinstance(altitude, torch.Tensor) + else azimuth.device + if isinstance(azimuth, torch.Tensor) + else torch.device("cuda" if torch.cuda.is_available() else "cpu") + ) # New reflection equation 2012-05-25 vikttot = 4.4897 @@ -45,40 +53,40 @@ def Kside_veg_v2022a( aziS = azimuth - 90 + t aziW = azimuth - 180 + t aziN = azimuth - 270 + t - deg2rad = np.pi / 180 - KsideD = np.zeros((rows, cols)) - Kref_sun = np.zeros((rows, cols)) - Kref_sh = np.zeros((rows, cols)) - Kref_veg = np.zeros((rows, cols)) - Kside = np.zeros((rows, cols)) - - Kref_veg_n = np.zeros((rows, cols)) - Kref_veg_s = np.zeros((rows, cols)) - Kref_veg_e = np.zeros((rows, cols)) - Kref_veg_w = np.zeros((rows, cols)) - - Kref_sh_n = np.zeros((rows, cols)) - Kref_sh_s = np.zeros((rows, cols)) - Kref_sh_e = np.zeros((rows, cols)) - Kref_sh_w = np.zeros((rows, cols)) - - Kref_sun_n = np.zeros((rows, cols)) - Kref_sun_s = np.zeros((rows, cols)) - Kref_sun_e = np.zeros((rows, cols)) - Kref_sun_w = np.zeros((rows, cols)) - - KeastRef = np.zeros((rows, cols)) - KwestRef = np.zeros((rows, cols)) - KnorthRef = np.zeros((rows, cols)) - KsouthRef = np.zeros((rows, cols)) - diffRadE = np.zeros((rows, cols)) - diffRadS = np.zeros((rows, cols)) - diffRadW = np.zeros((rows, cols)) - diffRadN = np.zeros((rows, cols)) + deg2rad = torch.pi / 180 + KsideD = torch.zeros((rows, cols), device=device) + Kref_sun = torch.zeros((rows, cols), device=device) + Kref_sh = torch.zeros((rows, cols), device=device) + Kref_veg = torch.zeros((rows, cols), device=device) + Kside = torch.zeros((rows, cols), device=device) + + Kref_veg_n = torch.zeros((rows, cols), device=device) + Kref_veg_s = torch.zeros((rows, cols), device=device) + Kref_veg_e = torch.zeros((rows, cols), device=device) + Kref_veg_w = torch.zeros((rows, cols), device=device) + + Kref_sh_n = torch.zeros((rows, cols), device=device) + Kref_sh_s = torch.zeros((rows, cols), device=device) + Kref_sh_e = torch.zeros((rows, cols), device=device) + Kref_sh_w = torch.zeros((rows, cols), device=device) + + Kref_sun_n = torch.zeros((rows, cols), device=device) + Kref_sun_s = torch.zeros((rows, cols), device=device) + Kref_sun_e = torch.zeros((rows, cols), device=device) + Kref_sun_w = torch.zeros((rows, cols), device=device) + + KeastRef = torch.zeros((rows, cols), device=device) + KwestRef = torch.zeros((rows, cols), device=device) + KnorthRef = torch.zeros((rows, cols), device=device) + KsouthRef = torch.zeros((rows, cols), device=device) + diffRadE = torch.zeros((rows, cols), device=device) + diffRadS = torch.zeros((rows, cols), device=device) + diffRadW = torch.zeros((rows, cols), device=device) + diffRadN = torch.zeros((rows, cols), device=device) ### Direct radiation ### if cyl == 1: ### Kside with cylinder ### - KsideI = shadow * radI * np.cos(altitude * deg2rad) + KsideI = shadow * radI * torch.cos(altitude * deg2rad) KeastI = 0 KsouthI = 0 KwestI = 0 @@ -88,8 +96,8 @@ def Kside_veg_v2022a( KeastI = ( radI * shadow - * np.cos(altitude * deg2rad) - * np.sin(aziE * deg2rad) + * torch.cos(altitude * deg2rad) + * torch.sin(aziE * deg2rad) ) else: KeastI = 0 @@ -97,8 +105,8 @@ def Kside_veg_v2022a( KsouthI = ( radI * shadow - * np.cos(altitude * deg2rad) - * np.sin(aziS * deg2rad) + * torch.cos(altitude * deg2rad) + * torch.sin(aziS * deg2rad) ) else: KsouthI = 0 @@ -106,8 +114,8 @@ def Kside_veg_v2022a( KwestI = ( radI * shadow - * np.cos(altitude * deg2rad) - * np.sin(aziW * deg2rad) + * torch.cos(altitude * deg2rad) + * torch.sin(aziW * deg2rad) ) else: KwestI = 0 @@ -115,8 +123,8 @@ def Kside_veg_v2022a( KnorthI = ( radI * shadow - * np.cos(altitude * deg2rad) - * np.sin(aziN * deg2rad) + * torch.cos(altitude * deg2rad) + * torch.sin(aziN * deg2rad) ) else: KnorthI = 0 @@ -146,32 +154,32 @@ def Kside_veg_v2022a( if anisotropic_sky: patch_luminance = lv[:, 2] else: - patch_luminance = np.zeros((patch_altitude.shape[0])) + patch_luminance = torch.zeros((patch_altitude.shape[0]), device=device) patch_luminance[:] = 1.0 / patch_luminance.shape[0] # Unique altitudes for patches - skyalt, skyalt_c = np.unique(patch_altitude, return_counts=True) + skyalt, skyalt_c = torch.unique(patch_altitude, return_counts=True) - radTot = np.zeros(1) + radTot = torch.zeros(1, device=device) # Calculation of steradian for each patch - steradian = np.zeros((patch_altitude.shape[0])) + steradian = torch.zeros((patch_altitude.shape[0]), device=device) for i in range(patch_altitude.shape[0]): # If there are more than one patch in a band if skyalt_c[skyalt == patch_altitude[i]] > 1: steradian[i] = ( (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad ) * ( - np.sin((patch_altitude[i] + patch_altitude[0]) * deg2rad) - - np.sin((patch_altitude[i] - patch_altitude[0]) * deg2rad) + torch.sin((patch_altitude[i] + patch_altitude[0]) * deg2rad) + - torch.sin((patch_altitude[i] - patch_altitude[0]) * deg2rad) ) # If there is only one patch in band, i.e. 90 degrees else: steradian[i] = ( (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad ) * ( - np.sin((patch_altitude[i]) * deg2rad) - - np.sin( + torch.sin((patch_altitude[i]) * deg2rad) + - torch.sin( (patch_altitude[i - 1] + patch_altitude[0]) * deg2rad ) ) @@ -179,7 +187,7 @@ def Kside_veg_v2022a( radTot += ( patch_luminance[i] * steradian[i] - * np.sin(patch_altitude[i] * deg2rad) + * torch.sin(patch_altitude[i] * deg2rad) ) # Radiance fraction normalization lumChi = ( @@ -188,23 +196,23 @@ def Kside_veg_v2022a( if cyl == 1: for idx in range(patch_azimuth.shape[0]): - # Angle of incidence, np.cos(0) because cylinder - always perpendicular - anglIncC = np.cos(patch_altitude[idx] * deg2rad) * np.cos( + # Angle of incidence, torch.cos(0) because cylinder - always perpendicular + anglIncC = torch.cos(patch_altitude[idx] * deg2rad) * torch.cos( 0 - ) # * np.sin(np.pi / 2) \ - # + np.sin(patch_altitude[idx] * deg2rad) * np.cos(np.pi / 2) + ) # * torch.sin(torch.pi / 2) \ + # + torch.sin(patch_altitude[idx] * deg2rad) * torch.cos(torch.pi / 2) # Diffuse vertical radiation KsideD += ( diffsh[:, :, idx] * lumChi[idx] * anglIncC * steradian[idx] ) # Shortwave reflected on sunlit surfaces - # sunlit_surface = ((albedo * radG) / np.pi) + # sunlit_surface = ((albedo * radG) / torch.pi) sunlit_surface = ( - albedo * (radI * np.cos(altitude * deg2rad)) + (radD * 0.5) - ) / np.pi + albedo * (radI * torch.cos(altitude * deg2rad)) + (radD * 0.5) + ) / torch.pi # Shortwave reflected on shaded surfaces and vegetation - shaded_surface = (albedo * radD * 0.5) / np.pi + shaded_surface = (albedo * radD * 0.5) / torch.pi # Shortwave radiation reflected on vegetation - based on diffuse shortwave radiation temp_vegsh = (vegshmat[:, :, idx] == 0) | ( @@ -214,7 +222,7 @@ def Kside_veg_v2022a( shaded_surface * temp_vegsh * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) ) # Shortwave radiation reflected on buildings (shaded and sunlit) - based on global and diffuse shortwave radiation @@ -235,14 +243,14 @@ def Kside_veg_v2022a( * sunlit_patches * temp_sh * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) ) Kref_sh += ( shaded_surface * shaded_patches * temp_sh * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) ) Kside = KsideI + KsideD + Kref_sun + Kref_sh + Kref_veg @@ -257,17 +265,17 @@ def Kside_veg_v2022a( # Kwest = (albedo * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + KupW) * 0.5 # Knorth = (albedo * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + KupN) * 0.5 else: # Box - diffRadE = np.zeros((rows, cols)) - diffRadS = np.zeros((rows, cols)) - diffRadW = np.zeros((rows, cols)) - diffRadN = np.zeros((rows, cols)) + diffRadE = torch.zeros((rows, cols), device=device) + diffRadS = torch.zeros((rows, cols), device=device) + diffRadW = torch.zeros((rows, cols), device=device) + diffRadN = torch.zeros((rows, cols), device=device) for idx in range(patch_azimuth.shape[0]): if (patch_azimuth[idx] > 360) or (patch_azimuth[idx] <= 180): - anglIncE = np.cos(patch_altitude[idx] * deg2rad) * np.cos( + anglIncE = torch.cos(patch_altitude[idx] * deg2rad) * torch.cos( (90 - patch_azimuth[idx] + t) * deg2rad - ) # * np.sin(np.pi / 2) \ - # + np.sin(patch_altitude[idx] * deg2rad) * np.cos(np.pi / 2) + ) # * torch.sin(torch.pi / 2) \ + # + torch.sin(patch_altitude[idx] * deg2rad) * torch.cos(torch.pi / 2) diffRadE += ( diffsh[:, :, idx] * lumChi[idx] @@ -276,10 +284,10 @@ def Kside_veg_v2022a( ) # * 0.5 if (patch_azimuth[idx] > 90) and (patch_azimuth[idx] <= 270): - anglIncS = np.cos(patch_altitude[idx] * deg2rad) * np.cos( + anglIncS = torch.cos(patch_altitude[idx] * deg2rad) * torch.cos( (180 - patch_azimuth[idx] + t) * deg2rad - ) # * np.sin(np.pi / 2) \ - # + np.sin(patch_altitude[idx] * deg2rad) * np.cos(np.pi / 2) + ) # * torch.sin(torch.pi / 2) \ + # + torch.sin(patch_altitude[idx] * deg2rad) * torch.cos(torch.pi / 2) diffRadS += ( diffsh[:, :, idx] * lumChi[idx] @@ -288,10 +296,10 @@ def Kside_veg_v2022a( ) # * 0.5 if (patch_azimuth[idx] > 180) and (patch_azimuth[idx] <= 360): - anglIncW = np.cos(patch_altitude[idx] * deg2rad) * np.cos( + anglIncW = torch.cos(patch_altitude[idx] * deg2rad) * torch.cos( (270 - patch_azimuth[idx] + t) * deg2rad - ) # * np.sin(np.pi / 2) \ - # + np.sin(patch_altitude[idx] * deg2rad) * np.cos(np.pi / 2) + ) # * torch.sin(torch.pi / 2) \ + # + torch.sin(patch_altitude[idx] * deg2rad) * torch.cos(torch.pi / 2) diffRadW += ( diffsh[:, :, idx] * lumChi[idx] @@ -300,10 +308,10 @@ def Kside_veg_v2022a( ) # * 0.5 if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] <= 90): - anglIncN = np.cos(patch_altitude[idx] * deg2rad) * np.cos( + anglIncN = torch.cos(patch_altitude[idx] * deg2rad) * torch.cos( (0 - patch_azimuth[idx] + t) * deg2rad - ) # * np.sin(np.pi / 2) \ - # + np.sin(patch_altitude[idx] * deg2rad) * np.cos(np.pi / 2) + ) # * torch.sin(torch.pi / 2) \ + # + torch.sin(patch_altitude[idx] * deg2rad) * torch.cos(torch.pi / 2) diffRadN += ( diffsh[:, :, idx] * lumChi[idx] @@ -312,12 +320,12 @@ def Kside_veg_v2022a( ) # * 0.5 # Shortwave reflected on sunlit surfaces - # sunlit_surface = ((albedo * radG) / np.pi) + # sunlit_surface = ((albedo * radG) / torch.pi) sunlit_surface = ( - albedo * (radI * np.cos(altitude * deg2rad)) + (radD * 0.5) - ) / np.pi + albedo * (radI * torch.cos(altitude * deg2rad)) + (radD * 0.5) + ) / torch.pi # Shortwave reflected on shaded surfaces and vegetation - shaded_surface = (albedo * radD * 0.5) / np.pi + shaded_surface = (albedo * radD * 0.5) / torch.pi # Shortwave radiation reflected on vegetation - based on diffuse shortwave radiation temp_vegsh = (vegshmat[:, :, idx] == 0) | ( @@ -327,46 +335,46 @@ def Kside_veg_v2022a( shaded_surface * temp_vegsh * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) ) if (patch_azimuth[idx] > 360) or (patch_azimuth[idx] < 180): Kref_veg_e += ( shaded_surface * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) * temp_vegsh - * np.cos((90 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos((90 - patch_azimuth[idx] + t) * deg2rad) ) if (patch_azimuth[idx] > 90) and (patch_azimuth[idx] < 270): Kref_veg_s += ( shaded_surface * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) * temp_vegsh - * np.cos((180 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos((180 - patch_azimuth[idx] + t) * deg2rad) ) if (patch_azimuth[idx] > 180) and (patch_azimuth[idx] < 360): Kref_veg_w += ( shaded_surface * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) * temp_vegsh - * np.cos((270 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos((270 - patch_azimuth[idx] + t) * deg2rad) ) if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] < 90): Kref_veg_n += ( shaded_surface * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) * temp_vegsh - * np.cos((0 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos((0 - patch_azimuth[idx] + t) * deg2rad) ) # Shortwave radiation reflected on buildings (shaded and sunlit) - based on global and diffuse shortwave radiation temp_vbsh = (1 - shmat[:, :, idx]) * vbshvegshmat[:, :, idx] temp_sh = temp_vbsh == 1 # & (vbshvegshmat[:,:,idx] == 1) - azimuth_difference = np.abs(azimuth - patch_azimuth[idx]) + azimuth_difference = torch.abs(azimuth - patch_azimuth[idx]) if (azimuth_difference > 90) and (azimuth_difference < 270): sunlit_patches, shaded_patches = ( @@ -383,14 +391,14 @@ def Kside_veg_v2022a( * sunlit_patches * temp_sh * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) ) Kref_sh += ( shaded_surface * shaded_patches * temp_sh * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) ) if (patch_azimuth[idx] > 360) or ( @@ -400,17 +408,17 @@ def Kside_veg_v2022a( sunlit_surface * sunlit_patches * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * np.cos((90 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos((90 - patch_azimuth[idx] + t) * deg2rad) ) Kref_sh_e += ( shaded_surface * shaded_patches * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * np.cos((90 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos((90 - patch_azimuth[idx] + t) * deg2rad) ) if (patch_azimuth[idx] > 90) and ( patch_azimuth[idx] < 270 @@ -419,17 +427,17 @@ def Kside_veg_v2022a( sunlit_surface * sunlit_patches * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * np.cos((180 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos((180 - patch_azimuth[idx] + t) * deg2rad) ) Kref_sh_s += ( shaded_surface * shaded_patches * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * np.cos((180 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos((180 - patch_azimuth[idx] + t) * deg2rad) ) if (patch_azimuth[idx] > 180) and ( patch_azimuth[idx] < 360 @@ -438,41 +446,41 @@ def Kside_veg_v2022a( sunlit_surface * sunlit_patches * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * np.cos((270 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos((270 - patch_azimuth[idx] + t) * deg2rad) ) Kref_sh_w += ( shaded_surface * shaded_patches * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * np.cos((270 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos((270 - patch_azimuth[idx] + t) * deg2rad) ) if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] < 90): Kref_sun_n += ( sunlit_surface * sunlit_patches * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * np.cos((0 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos((0 - patch_azimuth[idx] + t) * deg2rad) ) Kref_sh_n += ( shaded_surface * shaded_patches * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * np.cos((0 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos((0 - patch_azimuth[idx] + t) * deg2rad) ) else: Kref_sh += ( shaded_surface * temp_sh * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) ) if (patch_azimuth[idx] > 360) or ( @@ -481,9 +489,9 @@ def Kside_veg_v2022a( Kref_sh_e += ( shaded_surface * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * np.cos((90 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos((90 - patch_azimuth[idx] + t) * deg2rad) ) if (patch_azimuth[idx] > 90) and ( patch_azimuth[idx] < 270 @@ -491,9 +499,9 @@ def Kside_veg_v2022a( Kref_sh_s += ( shaded_surface * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * np.cos((180 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos((180 - patch_azimuth[idx] + t) * deg2rad) ) if (patch_azimuth[idx] > 180) and ( patch_azimuth[idx] < 360 @@ -501,17 +509,17 @@ def Kside_veg_v2022a( Kref_sh_w += ( shaded_surface * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * np.cos((270 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos((270 - patch_azimuth[idx] + t) * deg2rad) ) if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] < 90): Kref_sh_n += ( shaded_surface * steradian[idx] - * np.cos(patch_altitude[idx] * deg2rad) + * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * np.cos((0 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos((0 - patch_azimuth[idx] + t) * deg2rad) ) Keast = ( diff --git a/functions/SOLWEIGpython/Kup_veg_2015a.py b/functions/SOLWEIGpython/Kup_veg_2015a.py index d2c7f69..ed55af3 100644 --- a/functions/SOLWEIGpython/Kup_veg_2015a.py +++ b/functions/SOLWEIGpython/Kup_veg_2015a.py @@ -1,5 +1,5 @@ import numpy as np - +import torch def Kup_veg_2015a( radI, @@ -21,27 +21,27 @@ def Kup_veg_2015a( gvfalbnoshN, ): - Kup = (gvfalb * radI * np.sin(altitude * (np.pi / 180.0))) + ( + Kup = (gvfalb * radI * torch.sin(altitude * (torch.pi / 180.0))) + ( radD * svfbuveg + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) ) * gvfalbnosh - KupE = (gvfalbE * radI * np.sin(altitude * (np.pi / 180.0))) + ( + KupE = (gvfalbE * radI * torch.sin(altitude * (torch.pi / 180.0))) + ( radD * svfbuveg + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) ) * gvfalbnoshE - KupS = (gvfalbS * radI * np.sin(altitude * (np.pi / 180.0))) + ( + KupS = (gvfalbS * radI * torch.sin(altitude * (torch.pi / 180.0))) + ( radD * svfbuveg + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) ) * gvfalbnoshS - KupW = (gvfalbW * radI * np.sin(altitude * (np.pi / 180.0))) + ( + KupW = (gvfalbW * radI * torch.sin(altitude * (torch.pi / 180.0))) + ( radD * svfbuveg + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) ) * gvfalbnoshW - KupN = (gvfalbN * radI * np.sin(altitude * (np.pi / 180.0))) + ( + KupN = (gvfalbN * radI * torch.sin(altitude * (torch.pi / 180.0))) + ( radD * svfbuveg + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) ) * gvfalbnoshN diff --git a/functions/SOLWEIGpython/Kvikt_veg.py b/functions/SOLWEIGpython/Kvikt_veg.py index adc6d36..fecdbac 100644 --- a/functions/SOLWEIGpython/Kvikt_veg.py +++ b/functions/SOLWEIGpython/Kvikt_veg.py @@ -1,19 +1,19 @@ -def Kvikt_veg(svf, svfveg, vikttot): +def Kvikt_veg(input_svf, svfveg, vikttot): # Least viktwall = ( vikttot - ( - 63.227 * svf**6 - - 161.51 * svf**5 - + 156.91 * svf**4 - - 70.424 * svf**3 - + 16.773 * svf**2 - - 0.4863 * svf + 63.227 * input_svf**6 + - 161.51 * input_svf**5 + + 156.91 * input_svf**4 + - 70.424 * input_svf**3 + + 16.773 * input_svf**2 + - 0.4863 * input_svf ) ) / vikttot - svfvegbu = svfveg + svf - 1 # Vegetation plus buildings + svfvegbu = svfveg + input_svf - 1 # Vegetation plus buildings viktveg = ( vikttot - ( diff --git a/functions/SOLWEIGpython/Lside_veg.py b/functions/SOLWEIGpython/Lside_veg.py index da67dc7..a250b82 100644 --- a/functions/SOLWEIGpython/Lside_veg.py +++ b/functions/SOLWEIGpython/Lside_veg.py @@ -1,7 +1,7 @@ from __future__ import absolute_import import numpy as np from .Lvikt_veg import Lvikt_veg - +import torch def Lside_veg_v2022a( svfS, @@ -35,12 +35,33 @@ def Lside_veg_v2022a( ): # This m-file is the current one that estimates L from the four cardinal points 20100414 + device = ( + Ldown.device + if isinstance(Ldown, torch.Tensor) + else Ta.device + if isinstance(Ta, torch.Tensor) + else torch.device("cuda" if torch.cuda.is_available() else "cpu") + ) + + # Convert all SVF inputs to the same device + svfS = torch.as_tensor(svfS, device=device) + svfW = torch.as_tensor(svfW, device=device) + svfN = torch.as_tensor(svfN, device=device) + svfE = torch.as_tensor(svfE, device=device) + svfEveg = torch.as_tensor(svfEveg, device=device) + svfSveg = torch.as_tensor(svfSveg, device=device) + svfWveg = torch.as_tensor(svfWveg, device=device) + svfNveg = torch.as_tensor(svfNveg, device=device) + svfEaveg = torch.as_tensor(svfEaveg, device=device) + svfSaveg = torch.as_tensor(svfSaveg, device=device) + svfWaveg = torch.as_tensor(svfWaveg, device=device) + svfNaveg = torch.as_tensor(svfNaveg, device=device) # Building height angle from svf - svfalfaE = np.arcsin(np.exp((np.log(1 - svfE)) / 2)) - svfalfaS = np.arcsin(np.exp((np.log(1 - svfS)) / 2)) - svfalfaW = np.arcsin(np.exp((np.log(1 - svfW)) / 2)) - svfalfaN = np.arcsin(np.exp((np.log(1 - svfN)) / 2)) + svfalfaE = torch.arcsin(torch.exp((torch.log(1 - svfE)) / 2)) + svfalfaS = torch.arcsin(torch.exp((torch.log(1 - svfS)) / 2)) + svfalfaW = torch.arcsin(torch.exp((torch.log(1 - svfW)) / 2)) + svfalfaN = torch.arcsin(torch.exp((torch.log(1 - svfN)) / 2)) vikttot = 4.4897 aziW = azimuth + t @@ -61,18 +82,18 @@ def Lside_veg_v2022a( ) if altitude > 0: # daytime - alfaB = np.arctan(svfalfaE) - betaB = np.arctan(np.tan((svfalfaE) * F_sh)) + alfaB = torch.arctan(svfalfaE) + betaB = torch.arctan(torch.tan((svfalfaE) * F_sh)) betasun = ((alfaB - betaB) / 2) + betaB - # betasun = np.arctan(0.5*np.tan(svfalfaE)*(1+F_sh)) #TODO This should be considered in future versions + # betasun = torch.arctan(0.5*torch.tan(svfalfaE)*(1+F_sh)) #TODO This should be considered in future versions if (azimuth > (180 - t)) and (azimuth <= (360 - t)): Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * np.sin(aziE * (np.pi / 180))) ** 4) + * ((Ta + 273.15 + Tw * torch.sin(aziE * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) - * np.cos(betasun) + * torch.cos(betasun) * 0.5 ) Lwallsh = ( @@ -104,18 +125,18 @@ def Lside_veg_v2022a( ) if altitude > 0: # daytime - alfaB = np.arctan(svfalfaS) - betaB = np.arctan(np.tan((svfalfaS) * F_sh)) + alfaB = torch.arctan(svfalfaS) + betaB = torch.arctan(torch.tan((svfalfaS) * F_sh)) betasun = ((alfaB - betaB) / 2) + betaB - # betasun = np.arctan(0.5*np.tan(svfalfaS)*(1+F_sh)) + # betasun = torch.arctan(0.5*torch.tan(svfalfaS)*(1+F_sh)) if (azimuth <= (90 - t)) or (azimuth > (270 - t)): Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * np.sin(aziS * (np.pi / 180))) ** 4) + * ((Ta + 273.15 + Tw * torch.sin(aziS * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) - * np.cos(betasun) + * torch.cos(betasun) * 0.5 ) Lwallsh = ( @@ -147,18 +168,18 @@ def Lside_veg_v2022a( ) if altitude > 0: # daytime - alfaB = np.arctan(svfalfaW) - betaB = np.arctan(np.tan((svfalfaW) * F_sh)) + alfaB = torch.arctan(svfalfaW) + betaB = torch.arctan(torch.tan((svfalfaW) * F_sh)) betasun = ((alfaB - betaB) / 2) + betaB - # betasun = np.arctan(0.5*np.tan(svfalfaW)*(1+F_sh)) + # betasun = torch.arctan(0.5*torch.tan(svfalfaW)*(1+F_sh)) if (azimuth > (360 - t)) or (azimuth <= (180 - t)): Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * np.sin(aziW * (np.pi / 180))) ** 4) + * ((Ta + 273.15 + Tw * torch.sin(aziW * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) - * np.cos(betasun) + * torch.cos(betasun) * 0.5 ) Lwallsh = ( @@ -190,18 +211,18 @@ def Lside_veg_v2022a( ) if altitude > 0: # daytime - alfaB = np.arctan(svfalfaN) - betaB = np.arctan(np.tan((svfalfaN) * F_sh)) + alfaB = torch.arctan(svfalfaN) + betaB = torch.arctan(torch.tan((svfalfaN) * F_sh)) betasun = ((alfaB - betaB) / 2) + betaB - # betasun = np.arctan(0.5*np.tan(svfalfaN)*(1+F_sh)) + # betasun = torch.arctan(0.5*torch.tan(svfalfaN)*(1+F_sh)) if (azimuth > (90 - t)) and (azimuth <= (270 - t)): Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * np.sin(aziN * (np.pi / 180))) ** 4) + * ((Ta + 273.15 + Tw * torch.sin(aziN * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) - * np.cos(betasun) + * torch.cos(betasun) * 0.5 ) Lwallsh = ( @@ -258,12 +279,33 @@ def Lside_veg_v2026( ): # This m-file is the current one that estimates L from the four cardinal points 20100414 + device = ( + Ldown.device + if isinstance(Ldown, torch.Tensor) + else Ta.device + if isinstance(Ta, torch.Tensor) + else torch.device("cuda" if torch.cuda.is_available() else "cpu") + ) + + # Convert all SVF inputs to the same device + svfS = torch.as_tensor(svfS, device=device) + svfW = torch.as_tensor(svfW, device=device) + svfN = torch.as_tensor(svfN, device=device) + svfE = torch.as_tensor(svfE, device=device) + svfEveg = torch.as_tensor(svfEveg, device=device) + svfSveg = torch.as_tensor(svfSveg, device=device) + svfWveg = torch.as_tensor(svfWveg, device=device) + svfNveg = torch.as_tensor(svfNveg, device=device) + svfEaveg = torch.as_tensor(svfEaveg, device=device) + svfSaveg = torch.as_tensor(svfSaveg, device=device) + svfWaveg = torch.as_tensor(svfWaveg, device=device) + svfNaveg = torch.as_tensor(svfNaveg, device=device) # Building height angle from svf - svfalfaE = np.arcsin(np.exp((np.log(1 - svfE)) / 2)) - svfalfaS = np.arcsin(np.exp((np.log(1 - svfS)) / 2)) - svfalfaW = np.arcsin(np.exp((np.log(1 - svfW)) / 2)) - svfalfaN = np.arcsin(np.exp((np.log(1 - svfN)) / 2)) + svfalfaE = torch.arcsin(torch.exp((torch.log(1 - svfE)) / 2)) + svfalfaS = torch.arcsin(torch.exp((torch.log(1 - svfS)) / 2)) + svfalfaW = torch.arcsin(torch.exp((torch.log(1 - svfW)) / 2)) + svfalfaN = torch.arcsin(torch.exp((torch.log(1 - svfN)) / 2)) vikttot = 4.4897 aziW = azimuth + t @@ -284,26 +326,26 @@ def Lside_veg_v2026( ) if anisotropic_longwave == 1: - Least = np.zeros_like(Ldown) - Lnorth = np.zeros_like(Ldown) - Lwest = np.zeros_like(Ldown) - Lsouth = np.zeros_like(Ldown) + Least = torch.zeros_like(Ldown, device=device) + Lnorth = torch.zeros_like(Ldown, device=device) + Lwest = torch.zeros_like(Ldown, device=device) + Lsouth = torch.zeros_like(Ldown, device=device) return Least, Lsouth, Lwest, Lnorth else: if altitude > 0: # daytime - alfaB = np.arctan(svfalfaE) - betaB = np.arctan(np.tan((svfalfaE) * F_sh)) + alfaB = torch.arctan(svfalfaE) + betaB = torch.arctan(torch.tan((svfalfaE) * F_sh)) betasun = ((alfaB - betaB) / 2) + betaB - # betasun = np.arctan(0.5*np.tan(svfalfaE)*(1+F_sh)) #TODO This should be considered in future versions + # betasun = torch.arctan(0.5*torch.tan(svfalfaE)*(1+F_sh)) #TODO This should be considered in future versions if (azimuth > (180 - t)) and (azimuth <= (360 - t)): Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * np.sin(aziE * (np.pi / 180))) ** 4) + * ((Ta + 273.15 + Tw * torch.sin(aziE * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) - * np.cos(betasun) + * torch.cos(betasun) * 0.5 ) Lwallsh = ( @@ -329,18 +371,18 @@ def Lside_veg_v2026( ) if altitude > 0: # daytime - alfaB = np.arctan(svfalfaS) - betaB = np.arctan(np.tan((svfalfaS) * F_sh)) + alfaB = torch.arctan(svfalfaS) + betaB = torch.arctan(torch.tan((svfalfaS) * F_sh)) betasun = ((alfaB - betaB) / 2) + betaB - # betasun = np.arctan(0.5*np.tan(svfalfaS)*(1+F_sh)) + # betasun = torch.arctan(0.5*torch.tan(svfalfaS)*(1+F_sh)) if (azimuth <= (90 - t)) or (azimuth > (270 - t)): Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * np.sin(aziS * (np.pi / 180))) ** 4) + * ((Ta + 273.15 + Tw * torch.sin(aziS * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) - * np.cos(betasun) + * torch.cos(betasun) * 0.5 ) Lwallsh = ( @@ -366,18 +408,18 @@ def Lside_veg_v2026( ) if altitude > 0: # daytime - alfaB = np.arctan(svfalfaW) - betaB = np.arctan(np.tan((svfalfaW) * F_sh)) + alfaB = torch.arctan(svfalfaW) + betaB = torch.arctan(torch.tan((svfalfaW) * F_sh)) betasun = ((alfaB - betaB) / 2) + betaB - # betasun = np.arctan(0.5*np.tan(svfalfaW)*(1+F_sh)) + # betasun = torch.arctan(0.5*torch.tan(svfalfaW)*(1+F_sh)) if (azimuth > (360 - t)) or (azimuth <= (180 - t)): Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * np.sin(aziW * (np.pi / 180))) ** 4) + * ((Ta + 273.15 + Tw * torch.sin(aziW * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) - * np.cos(betasun) + * torch.cos(betasun) * 0.5 ) Lwallsh = ( @@ -403,18 +445,18 @@ def Lside_veg_v2026( ) if altitude > 0: # daytime - alfaB = np.arctan(svfalfaN) - betaB = np.arctan(np.tan((svfalfaN) * F_sh)) + alfaB = torch.arctan(svfalfaN) + betaB = torch.arctan(torch.tan((svfalfaN) * F_sh)) betasun = ((alfaB - betaB) / 2) + betaB - # betasun = np.arctan(0.5*np.tan(svfalfaN)*(1+F_sh)) + # betasun = torch.arctan(0.5*torch.tan(svfalfaN)*(1+F_sh)) if (azimuth > (90 - t)) and (azimuth <= (270 - t)): Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * np.sin(aziN * (np.pi / 180))) ** 4) + * ((Ta + 273.15 + Tw * torch.sin(aziN * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) - * np.cos(betasun) + * torch.cos(betasun) * 0.5 ) Lwallsh = ( diff --git a/functions/SOLWEIGpython/Lside_veg_v2015a.py b/functions/SOLWEIGpython/Lside_veg_v2015a.py index a9604b1..86baa47 100644 --- a/functions/SOLWEIGpython/Lside_veg_v2015a.py +++ b/functions/SOLWEIGpython/Lside_veg_v2015a.py @@ -1,6 +1,7 @@ from __future__ import absolute_import import numpy as np from .Lvikt_veg import Lvikt_veg +import torch def Lside_veg_v2015a( @@ -32,14 +33,21 @@ def Lside_veg_v2015a( LupW, LupN, ): - + # Load device + device = ( + Ldown.device + if isinstance(Ldown, torch.Tensor) + else Ta.device + if isinstance(Ta, torch.Tensor) + else torch.device("cuda" if torch.cuda.is_available() else "cpu") + ) # This m-file is the current one that estimates L from the four cardinal points 20100414 # Building height angle from svf - svfalfaE = np.arcsin(np.exp((np.log(1 - svfE)) / 2)) - svfalfaS = np.arcsin(np.exp((np.log(1 - svfS)) / 2)) - svfalfaW = np.arcsin(np.exp((np.log(1 - svfW)) / 2)) - svfalfaN = np.arcsin(np.exp((np.log(1 - svfN)) / 2)) + svfalfaE = torch.arcsin(torch.exp((torch.log(1 - svfE)) / 2)) + svfalfaS = torch.arcsin(torch.exp((torch.log(1 - svfS)) / 2)) + svfalfaW = torch.arcsin(torch.exp((torch.log(1 - svfW)) / 2)) + svfalfaN = torch.arcsin(torch.exp((torch.log(1 - svfN)) / 2)) vikttot = 4.4897 aziW = azimuth + t @@ -60,18 +68,18 @@ def Lside_veg_v2015a( ) if altitude > 0: # daytime - alfaB = np.arctan(svfalfaE) - betaB = np.arctan(np.tan((svfalfaE) * F_sh)) + alfaB = torch.arctan(svfalfaE) + betaB = torch.arctan(torch.tan((svfalfaE) * F_sh)) betasun = ((alfaB - betaB) / 2) + betaB - # betasun = np.arctan(0.5*np.tan(svfalfaE)*(1+F_sh)) #TODO This should be considered in future versions + # betasun = torch.arctan(0.5*torch.tan(svfalfaE)*(1+F_sh)) #TODO This should be considered in future versions if (azimuth > (180 - t)) and (azimuth <= (360 - t)): Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * np.sin(aziE * (np.pi / 180))) ** 4) + * ((Ta + 273.15 + Tw * torch.sin(aziE * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) - * np.cos(betasun) + * torch.cos(betasun) * 0.5 ) Lwallsh = ( @@ -98,18 +106,18 @@ def Lside_veg_v2015a( ) if altitude > 0: # daytime - alfaB = np.arctan(svfalfaS) - betaB = np.arctan(np.tan((svfalfaS) * F_sh)) + alfaB = torch.arctan(svfalfaS) + betaB = torch.arctan(torch.tan((svfalfaS) * F_sh)) betasun = ((alfaB - betaB) / 2) + betaB - # betasun = np.arctan(0.5*np.tan(svfalfaS)*(1+F_sh)) + # betasun = torch.arctan(0.5*torch.tan(svfalfaS)*(1+F_sh)) if (azimuth <= (90 - t)) or (azimuth > (270 - t)): Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * np.sin(aziS * (np.pi / 180))) ** 4) + * ((Ta + 273.15 + Tw * torch.sin(aziS * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) - * np.cos(betasun) + * torch.cos(betasun) * 0.5 ) Lwallsh = ( @@ -136,18 +144,18 @@ def Lside_veg_v2015a( ) if altitude > 0: # daytime - alfaB = np.arctan(svfalfaW) - betaB = np.arctan(np.tan((svfalfaW) * F_sh)) + alfaB = torch.arctan(svfalfaW) + betaB = torch.arctan(torch.tan((svfalfaW) * F_sh)) betasun = ((alfaB - betaB) / 2) + betaB - # betasun = np.arctan(0.5*np.tan(svfalfaW)*(1+F_sh)) + # betasun = torch.arctan(0.5*torch.tan(svfalfaW)*(1+F_sh)) if (azimuth > (360 - t)) or (azimuth <= (180 - t)): Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * np.sin(aziW * (np.pi / 180))) ** 4) + * ((Ta + 273.15 + Tw * torch.sin(aziW * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) - * np.cos(betasun) + * torch.cos(betasun) * 0.5 ) Lwallsh = ( @@ -174,18 +182,18 @@ def Lside_veg_v2015a( ) if altitude > 0: # daytime - alfaB = np.arctan(svfalfaN) - betaB = np.arctan(np.tan((svfalfaN) * F_sh)) + alfaB = torch.arctan(svfalfaN) + betaB = torch.arctan(torch.tan((svfalfaN) * F_sh)) betasun = ((alfaB - betaB) / 2) + betaB - # betasun = np.arctan(0.5*np.tan(svfalfaN)*(1+F_sh)) + # betasun = torch.arctan(0.5*torch.tan(svfalfaN)*(1+F_sh)) if (azimuth > (90 - t)) and (azimuth <= (270 - t)): Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * np.sin(aziN * (np.pi / 180))) ** 4) + * ((Ta + 273.15 + Tw * torch.sin(aziN * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) - * np.cos(betasun) + * torch.cos(betasun) * 0.5 ) Lwallsh = ( diff --git a/functions/SOLWEIGpython/Lvikt_veg.py b/functions/SOLWEIGpython/Lvikt_veg.py index 8ca101c..fe3cf73 100644 --- a/functions/SOLWEIGpython/Lvikt_veg.py +++ b/functions/SOLWEIGpython/Lvikt_veg.py @@ -1,4 +1,19 @@ +import torch + def Lvikt_veg(svf, svfveg, svfaveg, vikttot): + device = None + if isinstance(svf, torch.Tensor): + device = svf.device + elif isinstance(svfveg, torch.Tensor): + device = svfveg.device + elif isinstance(svfaveg, torch.Tensor): + device = svfaveg.device + else: + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + + svf = torch.as_tensor(svf, device=device) + svfveg = torch.as_tensor(svfveg, device=device) + svfaveg = torch.as_tensor(svfaveg, device=device) # Least viktonlywall = ( diff --git a/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py index 37539af..93280c2 100644 --- a/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py +++ b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py @@ -3,7 +3,6 @@ """ from __future__ import absolute_import - import numpy as np import matplotlib.pyplot as plt from .daylen import daylen @@ -35,6 +34,7 @@ from .patch_radiation import patch_steradians from copy import deepcopy import time +import torch # Wall surface temperature scheme from .wall_surface_temperature import wall_surface_temperature @@ -208,6 +208,7 @@ def Solweig_2026a_calc( TgOut1 = old Ts model diffsh, ani = Used in anisotrpic models (Wallenberg et al. 2019, 2022) """ + # # # Core program start # # # # Instrument offset in degrees @@ -217,7 +218,14 @@ def Solweig_2026a_calc( SBC = 5.67051e-8 # Degrees to radians - deg2rad = np.pi / 180 + deg2rad = torch.pi / 180 + device = ( + Tg.device + if isinstance(Tg, torch.Tensor) + else Ta.device + if isinstance(Ta, torch.Tensor) + else torch.device("cuda" if torch.cuda.is_available() else "cpu") + ) # Find sunrise decimal hour - new from 2014a _, _, _, SNUP = daylen(jday, location["latitude"]) @@ -228,7 +236,7 @@ def Solweig_2026a_calc( # Determination of clear-sky emissivity from Prata (1996) msteg = 46.5 * (ea / (Ta + 273.15)) esky = ( - 1 - (1 + msteg) * np.exp(-((1.2 + 3.0 * msteg) ** 0.5)) + 1 - (1 + msteg) * torch.exp(-((1.2 + 3.0 * msteg) ** 0.5)) ) + elvis # -0.04 old error from Jonsson et al.2006 if altitude > 0: # # # # # # DAYTIME # # # # # # @@ -237,7 +245,7 @@ def Solweig_2026a_calc( I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( zen, jday, Ta, RH / 100.0, radG, location, P ) - if (CI > 1) or (CI == np.inf): + if (CI > 1) or (CI == torch.inf): CI = 1 # Estimation of radD and radI if not measured after Reindl et al.(1990) @@ -245,7 +253,7 @@ def Solweig_2026a_calc( I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( zen, jday, Ta, RH / 100.0, radG, location, P ) - if (CI > 1) or (CI == np.inf): + if (CI > 1) or (CI == torch.inf): CI = 1 radI, radD = diffusefraction(radG, altitude, Kt, Ta, RH) @@ -254,13 +262,13 @@ def Solweig_2026a_calc( # Anisotropic Diffuse Radiation after Perez et al. 1993 if anisotropic_sky == 1: patchchoice = 1 - zenDeg = zen * (180 / np.pi) + zenDeg = zen * (180 / torch.pi) # Relative luminance lv, pc_, pb_ = Perez_v3( zenDeg, azimuth, radD, radI, jday, patchchoice, patch_option ) # Total relative luminance from sky, i.e. from each patch, into each cell - aniLum = np.zeros((rows, cols)) + aniLum = torch.zeros((rows, cols), device=device) for idx in range(lv.shape[0]): aniLum += diffsh[:, :, idx] * lv[idx, 2] @@ -285,9 +293,9 @@ def Solweig_2026a_calc( amaxvalue, bush, walls, - dirwalls * np.pi / 180.0, + dirwalls * torch.pi / 180.0, walls_scheme, - dirwalls_scheme * np.pi / 180.0, + dirwalls_scheme * torch.pi / 180.0, ) ) shadow = sh - (1 - vegsh) * (1 - psi) @@ -299,9 +307,9 @@ def Solweig_2026a_calc( altitude, scale, walls, - dirwalls * np.pi / 180.0, + dirwalls * torch.pi / 180.0, walls_scheme, - dirwalls_scheme * np.pi / 180.0, + dirwalls_scheme * torch.pi / 180.0, ) ) shadow = sh @@ -310,25 +318,25 @@ def Solweig_2026a_calc( F_sh = cylindric_wedge( zen, svfalfa, rows, cols ) # Fraction shadow on building walls based on sun alt and svf - F_sh[np.isnan(F_sh)] = 0.5 + F_sh[torch.isnan(F_sh)] = 0.5 # New estimation of Tgwall with reduction for non-clear situation based on Reindl et al. 1990 radI0, _ = diffusefraction(I0, altitude, 1.0, Ta, RH) - radG0 = radI0 * (np.sin(altitude * deg2rad)) + _ + radG0 = radI0 * (torch.sin(altitude * deg2rad)) + _ corr = ( - 0.1473 * np.log(90 - (zen / np.pi * 180)) + 0.3454 + 0.1473 * torch.log(90 - (zen / torch.pi * 180)) + 0.3454 ) # 20070329 correction of lat, Lindberg et al. 2008 CI_TgG = (radG / radG0) + (1 - corr) - if (CI_TgG > 1) or (CI_TgG == np.inf): + if (CI_TgG > 1) or (CI_TgG == torch.inf): CI_TgG = 1 Tgampwall = TgK_wall * altmax + Tstart_wall - Tgwall = Tgampwall * np.sin( + Tgwall = Tgampwall * torch.sin( ( - ((dectime - np.floor(dectime)) - SNUP / 24) + ((dectime - torch.floor(dectime)) - SNUP / 24) / (TmaxLST_wall / 24 - SNUP / 24) ) - * np.pi + * torch.pi / 2 ) # 2015a, based on max sun altitude if Tgwall < 0: # temporary for removing low Tg during morning 20130205 @@ -337,7 +345,7 @@ def Solweig_2026a_calc( # # # # Kdown # # # # Kdown = ( - radI * shadow * np.sin(altitude * (np.pi / 180)) + radI * shadow * torch.sin(altitude * (torch.pi / 180)) + dRad + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) ) # *sin(altitude(i) * (pi / 180)) @@ -428,12 +436,12 @@ def Solweig_2026a_calc( else: # using max sun alt instead of dfm Tgamp = TgK * altmax + Tstart # Fixed 2021 - Tgdiff = Tgamp * np.sin( + Tgdiff = Tgamp * torch.sin( ( - ((dectime - np.floor(dectime)) - SNUP / 24) + ((dectime - torch.floor(dectime)) - SNUP / 24) / (TmaxLST / 24 - SNUP / 24) ) - * np.pi + * torch.pi / 2 ) # 2015 a, based on max sun altitude @@ -531,7 +539,7 @@ def Solweig_2026a_calc( gvfalbnoshW, gvfalbnoshN, ) - + Keast, Ksouth, Kwest, Knorth, KsideI, KsideD, Kside = Kside_veg_v2022a( radI, radD, @@ -571,21 +579,21 @@ def Solweig_2026a_calc( else: # # # # # # # NIGHTTIME # # # # # # # # # Nocturnal K fluxes set to 0 - Knight = np.zeros((rows, cols)) - Kdown = np.zeros((rows, cols)) - Kwest = np.zeros((rows, cols)) - Kup = np.zeros((rows, cols)) - Keast = np.zeros((rows, cols)) - Ksouth = np.zeros((rows, cols)) - Knorth = np.zeros((rows, cols)) - KsideI = np.zeros((rows, cols)) - KsideD = np.zeros((rows, cols)) - F_sh = np.zeros((rows, cols)) - shadow = np.zeros((rows, cols)) + Knight = torch.zeros((rows, cols), device=device) + Kdown = torch.zeros((rows, cols), device=device) + Kwest = torch.zeros((rows, cols), device=device) + Kup = torch.zeros((rows, cols), device=device) + Keast = torch.zeros((rows, cols), device=device) + Ksouth = torch.zeros((rows, cols), device=device) + Knorth = torch.zeros((rows, cols), device=device) + KsideI = torch.zeros((rows, cols), device=device) + KsideD = torch.zeros((rows, cols), device=device) + F_sh = torch.zeros((rows, cols), device=device) + shadow = torch.zeros((rows, cols), device=device) CI_TgG = deepcopy(CI) - dRad = np.zeros((rows, cols)) - Kside = np.zeros((rows, cols)) - wallsun = np.zeros((rows, cols)) + dRad = torch.zeros((rows, cols), device=device) + Kside = torch.zeros((rows, cols), device=device) + wallsun = torch.zeros((rows, cols), device=device) Tgwall = 0 @@ -675,7 +683,7 @@ def Solweig_2026a_calc( else: # In the old scheme the ground surface temperature is equal to the air temperature during nighttime - Tg = np.ones((rows, cols)) * Ta + Tg = torch.ones((rows, cols), device=device) * Ta # # # # Lup, nighttime # # # # Lup = SBC * emis_grid * ((Knight + Tg + 273.15) ** 4) @@ -690,10 +698,10 @@ def Solweig_2026a_calc( # # # # Lside # # # # if groundScheme == 1: - Least = np.copy(gvfLsideE) - Lsouth = np.copy(gvfLsideS) - Lwest = np.copy(gvfLsideW) - Lnorth = np.copy(gvfLsideN) + Least = torch.clone(gvfLsideE) + Lsouth = torch.clone(gvfLsideS) + Lwest = torch.clone(gvfLsideW) + Lnorth = torch.clone(gvfLsideN) Least_, Lsouth_, Lwest_, Lnorth_ = Lside_veg_v2026( svfS, svfW, @@ -721,10 +729,10 @@ def Solweig_2026a_calc( anisotropic_sky, ) else: - Least = np.zeros_like(Ldown) - Lnorth = np.zeros_like(Ldown) - Lwest = np.zeros_like(Ldown) - Lsouth = np.zeros_like(Ldown) + Least = torch.zeros_like(Ldown) + Lnorth = torch.zeros_like(Ldown) + Lwest = torch.zeros_like(Ldown) + Lsouth = torch.zeros_like(Ldown) Least_, Lsouth_, Lwest_, Lnorth_ = Lside_veg_v2022a( svfS, svfW, @@ -770,13 +778,13 @@ def Solweig_2026a_calc( patch_option ) - patch_emissivities = np.zeros(skyvaultalt.shape[0]) + patch_emissivities = torch.zeros(skyvaultalt.shape[0], device=device) - x = np.transpose(np.atleast_2d(skyvaultalt)) - y = np.transpose(np.atleast_2d(skyvaultazi)) - z = np.transpose(np.atleast_2d(patch_emissivities)) + x = torch.transpose(torch.atleast_2d(skyvaultalt)) + y = torch.transpose(torch.atleast_2d(skyvaultazi)) + z = torch.transpose(torch.atleast_2d(patch_emissivities)) - L_patches = np.append(np.append(x, y, axis=1), z, axis=1) + L_patches = torch.append(torch.append(x, y, axis=1), z, axis=1) else: L_patches = deepcopy(lv) @@ -854,7 +862,7 @@ def Solweig_2026a_calc( ) Lside += Lside_ else: - Lside_ = np.zeros((rows, cols)) + Lside_ = torch.zeros((rows, cols), device=device) L_patches = None # Box and anisotropic longwave @@ -895,7 +903,7 @@ def Solweig_2026a_calc( ) # # # # Tmrt # # # # - Tmrt = np.sqrt(np.sqrt((Sstr / (absL * SBC)))) - 273.2 + Tmrt = torch.sqrt(torch.sqrt((Sstr / (absL * SBC)))) - 273.2 # Add longwave to cardinal directions for output in POI if (cyl == 1) and (anisotropic_sky == 1): diff --git a/functions/SOLWEIGpython/Solweig_run.py b/functions/SOLWEIGpython/Solweig_run.py index ef7fb2f..16d0901 100644 --- a/functions/SOLWEIGpython/Solweig_run.py +++ b/functions/SOLWEIGpython/Solweig_run.py @@ -6,6 +6,8 @@ # sommon imports from __future__ import absolute_import + +import numpy as np from ...util.umep_solweig_export_component import read_solweig_config from ...util.SEBESOLWEIGCommonFiles.Solweig_v2015_metdata_noload import ( Solweig_2015a_metdata_noload, @@ -27,19 +29,20 @@ from ...functions.SOLWEIGpython.Tgmaps_v1 import Tgmaps_v1 from ...functions import wallalgorithms as wa from ...functions.SOLWEIGpython.ground_surface import initiate_groundScheme -import numpy as np import json import zipfile import pandas as pd import matplotlib.pylab as plt from shutil import copyfile +import torch + # imports from osgeo/qgis dependency try: from osgeo import gdal from osgeo.gdalconst import * from ...util.misc import saveraster, xy2latlon_fromraster - from qgis.core import QgsVectorLayer, QgsRasterLayer + from qgis.core import QgsRasterLayer except: pass @@ -70,6 +73,10 @@ # from ...util.SEBESOLWEIGCommonFiles.Perez_v3 import Perez_v3 # from ...util.SEBESOLWEIGCommonFiles.create_patches import create_patches +device = torch.device( + "cuda" if torch.cuda.is_available() + else "cpu" +) def solweig_run(configPath, feedback): """ @@ -77,6 +84,15 @@ def solweig_run(configPath, feedback): configPath : config file including geodata paths and settings. feedback : To communicate with qgis gui. Set to None if standalone """ + # --- Load CPU OR GPU config + choice = input("Do you want to use the GPU ? (yes/no) : ").strip().lower() + selected_device = None + if choice == "yes" and torch.cuda.is_available(): + selected_device = torch.device("cuda") + print("GPU selected") + else: + selected_device = torch.device("cpu") + print("CPU selected") # Load config file configDict = read_solweig_config(configPath) @@ -120,7 +136,7 @@ def solweig_run(configPath, feedback): dsm_wkt = QgsRasterLayer(configDict["filepath_dsm"]).crs().toWkt() gdal_dsm = gdal.Open(configDict["filepath_dsm"]) lat, lon, scale, minx, miny = xy2latlon_fromraster(dsm_wkt, gdal_dsm) - dsm = gdal_dsm.ReadAsArray().astype(float) + dsm = torch.from_numpy(gdal_dsm.ReadAsArray().astype(float)).to(device) nd = gdal_dsm.GetRasterBand(1).GetNoDataValue() rows = dsm.shape[0] @@ -129,12 +145,12 @@ def solweig_run(configPath, feedback): # response to issue #85 dsm[dsm == nd] = 0.0 if dsm.min() < 0: - dsmraise = np.abs(dsm.min()) + dsmraise = torch.abs(dsm.min()) dsm = dsm + dsmraise else: dsmraise = 0 - alt = np.median(dsm) + alt = torch.median(dsm) if alt < 0: alt = 3 @@ -144,22 +160,22 @@ def solweig_run(configPath, feedback): usevegdem = int(configDict["usevegdem"]) if usevegdem == 1: if standAlone == 0: - vegdsm = ( + vegdsm = torch.from_numpy(( gdal.Open(configDict["filepath_cdsm"]) .ReadAsArray() .astype(float) - ) + )).to(device) else: vegdsm, _, _ = common.load_raster( configDict["filepath_cdsm"], bbox=None ) if configDict["filepath_tdsm"] != "": if standAlone == 0: - vegdsm2 = ( + vegdsm2 = torch.from_numpy(( gdal.Open(configDict["filepath_tdsm"]) .ReadAsArray() .astype(float) - ) + )).to(device) else: vegdsm2, _, _ = common.load_raster( configDict["filepath_tdsm"], bbox=None @@ -174,11 +190,11 @@ def solweig_run(configPath, feedback): landcover = int(configDict["landcover"]) if landcover == 1: if standAlone == 0: - lcgrid = ( + lcgrid =torch.from_numpy(( gdal.Open(configDict["filepath_lc"]) .ReadAsArray() .astype(float) - ) + )).to(device) else: lcgrid, _, _ = common.load_raster( configDict["filepath_lc"], bbox=None @@ -193,7 +209,7 @@ def solweig_run(configPath, feedback): gdal_dem = gdal.Open( configDict["filepath_dem"] ) # .ReadAsArray().astype(float) - dem = gdal_dem.ReadAsArray().astype(float) + dem = torch.from_numpy(gdal_dem.ReadAsArray().astype(float)).to(device) nd = gdal_dem.GetRasterBand(1).GetNoDataValue() else: dem, _, _ = common.load_raster( @@ -204,7 +220,7 @@ def solweig_run(configPath, feedback): # response to issue and #230 dem[dem == nd] = 0.0 if dem.min() < 0: - demraise = np.abs(dem.min()) + demraise = torch.abs(dem.min()) dem = dem + demraise else: demraise = 0 @@ -215,31 +231,31 @@ def solweig_run(configPath, feedback): zip.close() if standAlone == 0: - svf = ( + svf = torch.from_numpy(( gdal.Open(configDict["working_dir"] + "/svf.tif") .ReadAsArray() .astype(float) - ) - svfN = ( + )).to(device) + svfN = torch.from_numpy(( gdal.Open(configDict["working_dir"] + "/svfN.tif") .ReadAsArray() .astype(float) - ) - svfS = ( + )).to(device) + svfS = torch.from_numpy(( gdal.Open(configDict["working_dir"] + "/svfS.tif") .ReadAsArray() .astype(float) - ) - svfE = ( + )).to(device) + svfE = torch.from_numpy(( gdal.Open(configDict["working_dir"] + "/svfE.tif") .ReadAsArray() .astype(float) - ) - svfW = ( + )).to(device) + svfW = torch.from_numpy(( gdal.Open(configDict["working_dir"] + "/svfW.tif") .ReadAsArray() .astype(float) - ) + )).to(device) else: svf, _, _ = common.load_raster( configDict["working_dir"] + "/svf.tif", bbox=None @@ -259,57 +275,57 @@ def solweig_run(configPath, feedback): if usevegdem == 1: if standAlone == 0: - svfveg = ( + svfveg = torch.from_numpy(( gdal.Open(configDict["working_dir"] + "/svfveg.tif") .ReadAsArray() .astype(float) - ) - svfNveg = ( + )).to(device) + svfNveg = torch.from_numpy(( gdal.Open(configDict["working_dir"] + "/svfNveg.tif") .ReadAsArray() .astype(float) - ) - svfSveg = ( + )).to(device) + svfSveg = torch.from_numpy(( gdal.Open(configDict["working_dir"] + "/svfSveg.tif") .ReadAsArray() .astype(float) - ) - svfEveg = ( + )).to(device) + svfEveg = torch.from_numpy(( gdal.Open(configDict["working_dir"] + "/svfEveg.tif") .ReadAsArray() .astype(float) - ) - svfWveg = ( + )).to(device) + svfWveg = torch.from_numpy(( gdal.Open(configDict["working_dir"] + "/svfWveg.tif") .ReadAsArray() .astype(float) - ) + )).to(device) - svfaveg = ( + svfaveg = torch.from_numpy(( gdal.Open(configDict["working_dir"] + "/svfaveg.tif") .ReadAsArray() .astype(float) - ) - svfNaveg = ( + )).to(device) + svfNaveg = torch.from_numpy(( gdal.Open(configDict["working_dir"] + "/svfNaveg.tif") .ReadAsArray() .astype(float) - ) - svfSaveg = ( + )).to(device) + svfSaveg = torch.from_numpy(( gdal.Open(configDict["working_dir"] + "/svfSaveg.tif") .ReadAsArray() .astype(float) - ) - svfEaveg = ( + )).to(device) + svfEaveg = torch.from_numpy(( gdal.Open(configDict["working_dir"] + "/svfEaveg.tif") .ReadAsArray() .astype(float) - ) - svfWaveg = ( + )).to(device) + svfWaveg = torch.from_numpy(( gdal.Open(configDict["working_dir"] + "/svfWaveg.tif") .ReadAsArray() .astype(float) - ) + )).to(device) else: svfveg, _, _ = common.load_raster( configDict["working_dir"] + "/svfveg.tif", bbox=None @@ -343,29 +359,29 @@ def solweig_run(configPath, feedback): configDict["working_dir"] + "/svfWaveg.tif", bbox=None ) else: - svfveg = np.ones((rows, cols)) - svfNveg = np.ones((rows, cols)) - svfSveg = np.ones((rows, cols)) - svfEveg = np.ones((rows, cols)) - svfWveg = np.ones((rows, cols)) - svfaveg = np.ones((rows, cols)) - svfNaveg = np.ones((rows, cols)) - svfSaveg = np.ones((rows, cols)) - svfEaveg = np.ones((rows, cols)) - svfWaveg = np.ones((rows, cols)) + svfveg = torch.ones((rows, cols)).to(device) + svfNveg = torch.ones((rows, cols)).to(device) + svfSveg = torch.ones((rows, cols)).to(device) + svfEveg = torch.ones((rows, cols)).to(device) + svfWveg = torch.ones((rows, cols)).to(device) + svfaveg = torch.ones((rows, cols)).to(device) + svfNaveg = torch.ones((rows, cols)).to(device) + svfSaveg = torch.ones((rows, cols)).to(device) + svfEaveg = torch.ones((rows, cols)).to(device) + svfWaveg = torch.ones((rows, cols)).to(device) tmp = svf + svfveg - 1.0 tmp[tmp < 0.0] = 0.0 # %matlab crazyness around 0 - svfalfa = np.arcsin(np.exp((np.log((1.0 - tmp)) / 2.0))) + svfalfa = torch.arcsin(torch.exp((torch.log((1.0 - tmp)) / 2.0))) if standAlone == 0: - wallheight = ( + wallheight = torch.from_numpy(( gdal.Open(configDict["filepath_wh"]).ReadAsArray().astype(float) - ) - wallaspect = ( + )).to(device) + wallaspect = torch.from_numpy(( gdal.Open(configDict["filepath_wa"]).ReadAsArray().astype(float) - ) + )).to(device) else: wallheight, _, _ = common.load_raster( configDict["filepath_wh"], bbox=None @@ -379,9 +395,9 @@ def solweig_run(configPath, feedback): delim = " " Twater = [] - metdata = np.loadtxt( + metdata = torch.from_numpy(np.loadtxt( configDict["input_met"], skiprows=headernum, delimiter=delim - ) + )).to(device) location = {"longitude": lon, "latitude": lat, "altitude": alt} YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax = ( @@ -414,18 +430,18 @@ def solweig_run(configPath, feedback): # vlayer = QgsVectorLayer(configDict['poi_file'], 'point', 'ogr') # idx = vlayer.fields().indexFromName(poi_field) # numfeat = vlayer.featureCount() - # poisxy = np.zeros((numfeat, 3)) - 999 + # poisxy = torch.zeros((numfeat, 3)) - 999 # ind = 0 # for f in vlayer.getFeatures(): # looping through each POI # y = f.geometry().centroid().asPoint().y() # x = f.geometry().centroid().asPoint().x() # poiname.append(f.attributes()[idx]) # poisxy[ind, 0] = ind - # poisxy[ind, 1] = np.round((x - minx) * scale) + # poisxy[ind, 1] = torch.round((x - minx) * scale) # if miny >= 0: - # poisxy[ind, 2] = np.round((miny + rows * (1. / scale) - y) * scale) + # poisxy[ind, 2] = torch.round((miny + rows * (1. / scale) - y) * scale) # else: - # poisxy[ind, 2] = np.round((miny + rows * (1. / scale) - y) * scale) + # poisxy[ind, 2] = torch.round((miny + rows * (1. / scale) - y) * scale) # ind += 1 poi_field = configDict[ @@ -438,7 +454,7 @@ def solweig_run(configPath, feedback): else: pois_gdf = gpd.read_file(configDict["poi_file"]) numfeat = pois_gdf.shape[0] - poisxy = np.zeros((numfeat, 3)) - 999 + poisxy = torch.zeros((numfeat, 3)).to(device) - 999 for idx, row in pois_gdf.iterrows(): y, x = rowcol( dsm_transf, @@ -451,12 +467,12 @@ def solweig_run(configPath, feedback): poisxy[idx, 2] = y for k in range(0, poisxy.shape[0]): - poi_save = [] # np.zeros((1, 33)) + poi_save = [] # torch.zeros((1, 33)) data_out = ( configDict["output_dir"] + "/POI_" + str(poiname[k]) + ".txt" ) np.savetxt( - data_out, poi_save, delimiter=" ", header=header, comments="" + data_out, poi_save.cpu().numpy() if isinstance(poi_save, torch.Tensor) else poi_save, delimiter=" ", header=header, comments="" ) # print(poisxy) # Num format for POI output @@ -477,28 +493,28 @@ def solweig_run(configPath, feedback): if param["Tmrt_params"]["Value"]["posture"] == "Standing": Fside = param["Posture"]["Standing"]["Value"]["Fside"] Fup = param["Posture"]["Standing"]["Value"]["Fup"] - height = param["Posture"]["Standing"]["Value"]["height"] + height = torch.tensor(param["Posture"]["Standing"]["Value"]["height"]).to(device) Fcyl = param["Posture"]["Standing"]["Value"]["Fcyl"] pos = 1 else: Fside = param["Posture"]["Sitting"]["Value"]["Fside"] Fup = param["Posture"]["Sitting"]["Value"]["Fup"] - height = param["Posture"]["Sitting"]["Value"]["height"] + height = torch.tensor(param["Posture"]["Sitting"]["Value"]["height"]).to(device) Fcyl = param["Posture"]["Sitting"]["Value"]["Fcyl"] pos = 0 # Radiative surface influence, Rule of thumb by Schmid et al. (1990). - first = np.round(height) + first = torch.round(height) if first == 0.0: first = 1.0 - second = np.round((height * 20.0)) + second = torch.round((height * 20.0)) if usevegdem == 1: # Conifer or deciduous if configDict["conifer_bool"]: - leafon = np.ones((1, DOY.shape[0])) + leafon = torch.ones((1, DOY.shape[0])).to(device) else: - leafon = np.zeros((1, DOY.shape[0])) + leafon = torch.zeros((1, DOY.shape[0])).to(device) if ( param["Tree_settings"]["Value"]["First_day_leaf"] > param["Tree_settings"]["Value"]["Last_day_leaf"] @@ -518,7 +534,7 @@ def solweig_run(configPath, feedback): # amaxvalue vegmax = vegdsm.max() amaxvalue = dsm.max() - dsm.min() - amaxvalue = np.maximum(amaxvalue, vegmax) + amaxvalue = torch.maximum(amaxvalue, vegmax) # Elevation vegdsms if buildingDEM includes ground heights vegdsm = vegdsm + dsm @@ -527,7 +543,7 @@ def solweig_run(configPath, feedback): vegdsm2[vegdsm2 == dsm] = 0 # % Bush separation - bush = np.logical_not((vegdsm2 * vegdsm)) * vegdsm + bush = torch.logical_not((vegdsm2 * vegdsm)) * vegdsm svfbuveg = svf - (1.0 - svfveg) * ( 1.0 - transVeg @@ -535,20 +551,20 @@ def solweig_run(configPath, feedback): else: psi = leafon * 0.0 + 1.0 svfbuveg = svf - bush = np.zeros([rows, cols]) + bush = torch.zeros([rows, cols]).to(device) amaxvalue = 0 # Initialization of maps - Knight = np.zeros((rows, cols)) - Tgmap1 = np.zeros((rows, cols)) - Tgmap1E = np.zeros((rows, cols)) - Tgmap1S = np.zeros((rows, cols)) - Tgmap1W = np.zeros((rows, cols)) - Tgmap1N = np.zeros((rows, cols)) + Knight = torch.zeros((rows, cols)).to(device) + Tgmap1 = torch.zeros((rows, cols)).to(device) + Tgmap1E = torch.zeros((rows, cols)).to(device) + Tgmap1S = torch.zeros((rows, cols)).to(device) + Tgmap1W = torch.zeros((rows, cols)).to(device) + Tgmap1N = torch.zeros((rows, cols)).to(device) # Create building boolean raster from either land cover or height rasters if demforbuild == 0: - buildings = np.copy(lcgrid) + buildings = lcgrid.clone() buildings[buildings == 7] = 1 buildings[buildings == 6] = 1 buildings[buildings == 5] = 1 @@ -565,12 +581,12 @@ def solweig_run(configPath, feedback): saveraster( gdal_dsm, configDict["output_dir"] + "/buildings.tif", - buildings, + buildings.detach().cpu().numpy(), ) else: common.save_raster( configDict["output_dir"] + "/buildings.tif", - buildings, + buildings.detach().cpu().numpy(), dsm_transf, dsm_crs, ) @@ -578,12 +594,12 @@ def solweig_run(configPath, feedback): # Import shadow matrices (Anisotropic sky) anisotropic_sky = int(configDict["aniso"]) if anisotropic_sky == 1: # UseAniso - data = np.load(configDict["input_aniso"]) + data = torch.load(configDict["input_aniso"]).to(device) shmat = data["shadowmat"] vegshmat = data["vegshadowmat"] vbshvegshmat = data["vbshmat"] if usevegdem == 1: - diffsh = np.zeros((rows, cols, shmat.shape[2])) + diffsh = torch.zeros((rows, cols, shmat.shape[2])).to(device) for i in range(0, shmat.shape[2]): diffsh[:, :, i] = shmat[:, :, i] - (1 - vegshmat[:, :, i]) * ( 1 - transVeg @@ -602,10 +618,10 @@ def solweig_run(configPath, feedback): patch_option = 4 # patch_option = 4 # 612 patches # asvf to calculate sunlit and shaded patches - asvf = np.arccos(np.sqrt(svf)) + asvf = torch.arccos(torch.sqrt(svf)) # Empty array for steradians - steradians = np.zeros((shmat.shape[2])) + steradians = torch.zeros((shmat.shape[2])).to(device) else: # anisotropic_sky = 0 diffsh = None @@ -615,7 +631,7 @@ def solweig_run(configPath, feedback): asvf = None patch_option = 0 steradians = 0 - shadow = np.zeros_like(dsm) + shadow = torch.zeros_like(dsm).to(device) # % Ts parameterisation maps if landcover == 1.0: @@ -629,7 +645,7 @@ def solweig_run(configPath, feedback): Tstart_wall, TmaxLST, TmaxLST_wall, - ] = Tgmaps_v1(lcgrid.copy(), param) + ] = Tgmaps_v1(lcgrid.clone(), param) else: TgK = Knight + param["Ts_deg"]["Value"]["Cobble_stone_2014a"] Tstart = Knight - param["Tstart"]["Value"]["Cobble_stone_2014a"] @@ -650,7 +666,7 @@ def solweig_run(configPath, feedback): if configDict["input_surf"] != "": surfData = pd.read_csv(configDict["input_surf"]) Tg = surfData["Tg"] - Tm = np.mean(surfData["Tg"]) + Tm = torch.mean(surfData["Tg"]) ( _, _, @@ -663,7 +679,7 @@ def solweig_run(configPath, feedback): a2_grid, a3_grid, ) = initiate_groundScheme( - lcgrid.copy(), param, DOY[0], Ta, location + lcgrid.copy(), param, DOY[0], Ta, location, device ) else: ( @@ -678,7 +694,7 @@ def solweig_run(configPath, feedback): a2_grid, a3_grid, ) = initiate_groundScheme( - lcgrid.copy(), param, DOY[0], Ta, location + lcgrid.clone(), param, DOY[0], Ta, location, device ) else: pass @@ -686,7 +702,7 @@ def solweig_run(configPath, feedback): # Import data for wall temperature parameterization TODO: fix for standalone wallScheme = int(configDict["wallscheme"]) if wallScheme == 1: - wallData = np.load(configDict["input_wall"]) + wallData = torch.load(configDict["input_wall"]).to(device) voxelMaps = wallData["voxelId"] voxelTable = wallData["voxelTable"] # Get wall type from standalone @@ -705,7 +721,7 @@ def solweig_run(configPath, feedback): # Calculate wall height for wall scheme, i.e. include corners (thicker walls) walls_scheme = wa.findwalls_sp( - dsm, 2, np.array([[1, 1, 1], [1, 0, 1], [1, 1, 1]]) + dsm, 2, torch.tensor([[1, 1, 1], [1, 0, 1], [1, 1, 1]]).to(device) ) # Calculate wall aspect for wall scheme, i.e. include corners (thicker walls) dirwalls_scheme = wa.filter1Goodwin_as_aspect_v3( @@ -714,16 +730,16 @@ def solweig_run(configPath, feedback): # Used in wall temperature parameterization scheme first_timestep = ( - pd.to_datetime(YYYY[0][0], format="%Y") - + pd.to_timedelta(DOY[0] - 1, unit="d") - + pd.to_timedelta(hours[0], unit="h") - + pd.to_timedelta(minu[0], unit="m") + pd.to_datetime(int(YYYY[0][0].item()), format="%Y") + + pd.to_timedelta(DOY[0].item() - 1, unit="d") + + pd.to_timedelta(hours[0].item(), unit="h") + + pd.to_timedelta(minu[0].item(), unit="m") ) second_timestep = ( - pd.to_datetime(YYYY[0][1], format="%Y") - + pd.to_timedelta(DOY[1] - 1, unit="d") - + pd.to_timedelta(hours[1], unit="h") - + pd.to_timedelta(minu[1], unit="m") + pd.to_datetime(int(YYYY[0][1].item()), format="%Y") + + pd.to_timedelta(DOY[1].item() - 1, unit="d") + + pd.to_timedelta(hours[1].item(), unit="h") + + pd.to_timedelta(minu[1].item(), unit="m") ) timeStep = (second_timestep - first_timestep).seconds @@ -758,7 +774,7 @@ def solweig_run(configPath, feedback): else: pois_gdf = gpd.read_file(configDict["poi_file"]) numfeat = pois_gdf.shape[0] - poisxy = np.zeros((numfeat, 3)) - 999 + poisxy = torch.zeros((numfeat, 3)).to(device) - 999 for idx, row in pois_gdf.iterrows(): y, x = rowcol( dsm_transf, @@ -784,8 +800,8 @@ def solweig_run(configPath, feedback): voxelTable = 0 timeStep = 0 # thermal_effusivity = 0 - walls_scheme = np.ones((rows, cols)) * 10.0 - dirwalls_scheme = np.ones((rows, cols)) * 10.0 + walls_scheme = torch.ones((rows, cols)).to(device) * 10.0 + dirwalls_scheme = torch.ones((rows, cols)).to(device) * 10.0 # Initialisation of time related variables if Ta.__len__() == 1: @@ -810,21 +826,23 @@ def solweig_run(configPath, feedback): CI = 1.0 # Main loop - tmrtplot = np.zeros((rows, cols)) + tmrtplot = torch.zeros((rows, cols)).to(device) # Initiate array for I0 values - if np.unique(DOY).shape[0] > 1: - unique_days = np.unique(DOY) + if torch.unique(DOY).shape[0] > 1: + unique_days = torch.unique(DOY) first_unique_day = DOY[DOY == unique_days[0]] - I0_array = np.zeros((first_unique_day.shape[0])) + I0_array = torch.zeros((first_unique_day.shape[0])).to(device) else: - first_unique_day = DOY.copy() - I0_array = np.zeros((DOY.shape[0])) + first_unique_day = DOY.clone() + I0_array = torch.zeros((DOY.shape[0])).to(device) if standAlone == 1: progress = tqdm(total=Ta.__len__()) + else: + progress = None - for i in np.arange(0, Ta.__len__()): + for i in torch.arange(0, Ta.__len__()): if feedback is not None: feedback.setProgress( int(i * (100.0 / Ta.__len__())) @@ -832,19 +850,19 @@ def solweig_run(configPath, feedback): if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") break - else: + elif progress is not None: progress.update(1) # Daily water body temperature if landcover == 1: - if ((dectime[i] - np.floor(dectime[i]))) == 0 or (i == 0): - Twater = np.mean(Ta[jday[0] == np.floor(dectime[i])]) + if ((dectime[i] - torch.floor(dectime[i]))) == 0 or (i == 0): + Twater = torch.mean(Ta[jday[0] == torch.floor(dectime[i])]) # Nocturnal cloudfraction from Offerle et al. 2003 - if (dectime[i] - np.floor(dectime[i])) == 0: - daylines = np.where(np.floor(dectime) == dectime[i]) + if (dectime[i] - torch.floor(dectime[i])) == 0: + daylines = torch.where(torch.floor(dectime) == dectime[i]) if daylines.__len__() > 1: alt = altitude[0][daylines] - alt2 = np.where(alt > 1) + alt2 = torch.where(alt > 1) rise = alt2[0][0] [_, CI, _, _, _] = clearnessindex_2013b( zen[0, i + rise + 1], @@ -855,14 +873,14 @@ def solweig_run(configPath, feedback): location, P[i + rise + 1], ) - if (CI > 1.0) or (CI == np.inf): + if (CI > 1.0) or (CI == torch.inf): CI = 1.0 else: CI = 1.0 # Only if Kdir is derived from horizontal global shortwave and horizontal diffuse shortwave # if altitude[0][i] > 0: - # radI[i] = radI[i]/np.sin(altitude[0][i] * np.pi/180) + # radI[i] = radI[i]/torch.sin(altitude[0][i] * torch.pi/180) # else: # radG[i] = 0. # radD[i] = 0. @@ -870,17 +888,19 @@ def solweig_run(configPath, feedback): # Timestep of the simulation used in the ground scheme calculation first_timestep = ( - pd.to_datetime(YYYY[0][0], format="%Y") - + pd.to_timedelta(DOY[0] - 1, unit="d") - + pd.to_timedelta(hours[0], unit="h") - + pd.to_timedelta(minu[0], unit="m") + pd.to_datetime(int(YYYY[0][0].item()), format="%Y") + + pd.to_timedelta(DOY[0].item() - 1, unit="d") + + pd.to_timedelta(hours[0].item(), unit="h") + + pd.to_timedelta(minu[0].item(), unit="m") ) second_timestep = ( - pd.to_datetime(YYYY[0][1], format="%Y") - + pd.to_timedelta(DOY[1] - 1, unit="d") - + pd.to_timedelta(hours[1], unit="h") - + pd.to_timedelta(minu[1], unit="m") + pd.to_datetime(int(YYYY[0][1].item()), format="%Y") + + pd.to_timedelta(DOY[1].item() - 1, unit="d") + + pd.to_timedelta(hours[1].item(), unit="h") + + pd.to_timedelta(minu[1].item(), unit="m") ) + + timeStep = (second_timestep - first_timestep).seconds ( @@ -1059,7 +1079,7 @@ def solweig_run(configPath, feedback): # Write to POIs if not poisxy is None: for k in range(0, poisxy.shape[0]): - poi_save = np.zeros((1, 40)) + poi_save = torch.zeros((1, 40)).to(device) poi_save[0, 0] = YYYY[0][i] poi_save[0, 1] = jday[0][i] poi_save[0, 2] = hours[i] @@ -1131,7 +1151,7 @@ def solweig_run(configPath, feedback): ) # f_handle = file(data_out, 'a') f_handle = open(data_out, "ab") - np.savetxt(f_handle, poi_save, fmt=numformat) + np.savetxt(f_handle, poi_save.cpu().numpy() if isinstance(poi_save, torch.Tensor) else poi_save, fmt=numformat) f_handle.close() # If wall temperature parameterization scheme is in use @@ -1169,12 +1189,12 @@ def solweig_run(configPath, feedback): ), "wallShade", ].to_numpy() - temp_all = np.concatenate( + temp_all = torch.concatenate( [temp_wall, K_in, L_in, wallShade] - ) - # temp_all = np.concatenate([temp_wall]) - # wall_data = np.zeros((1, 7 + temp_wall.shape[0])) - wall_data = np.zeros((1, 7 + temp_all.shape[0])) + ).to(device) + # temp_all = torch.concatenate([temp_wall]) + # wall_data = torch.zeros((1, 7 + temp_wall.shape[0])) + wall_data = torch.zeros((1, 7 + temp_all.shape[0])).to(device) # Part of file name (wallid), i.e. WOI_wallid.txt data_out = ( configDict["output_dir"] @@ -1219,7 +1239,7 @@ def solweig_run(configPath, feedback): ) # Open file, add data, save f_handle = open(data_out, "ab") - np.savetxt(f_handle, wall_data, fmt=woi_numformat) + np.savetxt(f_handle, wall_data.cpu().numpy() if isinstance(wall_data, torch.Tensor) else wall_data, fmt=woi_numformat) f_handle.close() # Save wall temperature/radiation as NetCDF TODO: fix for standAlone? @@ -1262,12 +1282,12 @@ def solweig_run(configPath, feedback): saveraster( gdal_dsm, configDict["output_dir"] + "/Tmrt_" + time_code + ".tif", - Tmrt, + Tmrt.detach().cpu().numpy(), ) else: common.save_raster( configDict["output_dir"] + "/Tmrt_" + time_code + ".tif", - Tmrt, + Tmrt.detach().cpu().numpy(), dsm_transf, dsm_crs, ) @@ -1276,12 +1296,12 @@ def solweig_run(configPath, feedback): saveraster( gdal_dsm, configDict["output_dir"] + "/Kup_" + time_code + ".tif", - Kup, + Kup.detach().cpu().numpy(), ) else: common.save_raster( configDict["output_dir"] + "/Kup_" + time_code + ".tif", - Kup, + Kup.detach().cpu().numpy(), dsm_transf, dsm_crs, ) @@ -1290,12 +1310,12 @@ def solweig_run(configPath, feedback): saveraster( gdal_dsm, configDict["output_dir"] + "/Kdown_" + time_code + ".tif", - Kdown, + Kdown.detach().cpu().numpy(), ) else: common.save_raster( configDict["output_dir"] + "/Kdown_" + time_code + ".tif", - Kdown, + Kdown.detach().cpu().numpy(), dsm_transf, dsm_crs, ) @@ -1304,12 +1324,12 @@ def solweig_run(configPath, feedback): saveraster( gdal_dsm, configDict["output_dir"] + "/Lup_" + time_code + ".tif", - Lup, + Lup.detach().cpu().numpy(), ) else: common.save_raster( configDict["output_dir"] + "/Lup_" + time_code + ".tif", - Lup, + Lup.detach().cpu().numpy(), dsm_transf, dsm_crs, ) @@ -1318,12 +1338,12 @@ def solweig_run(configPath, feedback): saveraster( gdal_dsm, configDict["output_dir"] + "/Ldown_" + time_code + ".tif", - Ldown, + Ldown.detach().cpu().numpy(), ) else: common.save_raster( configDict["output_dir"] + "/Ldown_" + time_code + ".tif", - Ldown, + Ldown.detach().cpu().numpy(), dsm_transf, dsm_crs, ) @@ -1332,12 +1352,12 @@ def solweig_run(configPath, feedback): saveraster( gdal_dsm, configDict["output_dir"] + "/Shadow_" + time_code + ".tif", - shadow, + shadow.detach().cpu().numpy(), ) else: common.save_raster( configDict["output_dir"] + "/Shadow_" + time_code + ".tif", - shadow, + shadow.detach().cpu().numpy(), dsm_transf, dsm_crs, ) @@ -1347,12 +1367,12 @@ def solweig_run(configPath, feedback): saveraster( gdal_dsm, configDict["output_dir"] + "/Kdiff_" + time_code + ".tif", - dRad, + dRad.detach().cpu().numpy(), ) else: common.save_raster( configDict["output_dir"] + "/Kdiff_" + time_code + ".tif", - dRad, + dRad.detach().cpu().numpy(), dsm_transf, dsm_crs, ) @@ -1415,7 +1435,7 @@ def solweig_run(configPath, feedback): "%1.2f", "%i", ) - settingsData = np.array( + settingsData = torch.tensor( [ [ int(configDict["utc"]), @@ -1434,11 +1454,11 @@ def solweig_run(configPath, feedback): patch_option, ] ] - ) + ).to(device) # print(settingsData) np.savetxt( configDict["output_dir"] + "/treeplantersettings.txt", - settingsData, + settingsData.cpu().numpy() if isinstance(settingsData, torch.Tensor) else settingsData, fmt=settingsFmt, header=settingsHeader, delimiter=" ", @@ -1454,7 +1474,7 @@ def solweig_run(configPath, feedback): ) # fix average Tmrt instead of sum, 20191022 if standAlone == 0: saveraster( - gdal_dsm, configDict["output_dir"] + "/Tmrt_average.tif", tmrtplot + gdal_dsm, configDict["output_dir"] + "/Tmrt_average.tif", tmrtplot.detach().cpu().numpy() ) else: common.save_raster( diff --git a/functions/SOLWEIGpython/Tgmaps_v1.py b/functions/SOLWEIGpython/Tgmaps_v1.py index b3a856e..d7e29ac 100644 --- a/functions/SOLWEIGpython/Tgmaps_v1.py +++ b/functions/SOLWEIGpython/Tgmaps_v1.py @@ -1,4 +1,5 @@ import numpy as np +import torch def Tgmaps_v1(lc_grid, solweig_parameters): @@ -6,30 +7,30 @@ def Tgmaps_v1(lc_grid, solweig_parameters): # Tgmaps_v1 Populates grids with cooeficients for Tg wave # Detailed explanation goes here lc_grid[lc_grid >= 100] = 2 - id = np.unique(lc_grid) - id = lc_grid[lc_grid <= 7].astype(int) - TgK = np.copy(lc_grid) - Tstart = np.copy(lc_grid) - alb_grid = np.copy(lc_grid) - emis_grid = np.copy(lc_grid) - TmaxLST = np.copy(lc_grid) + id = torch.unique(lc_grid) + id = lc_grid[lc_grid <= 7].to(int) + TgK = torch.clone(lc_grid) + Tstart = torch.clone(lc_grid) + alb_grid = torch.clone(lc_grid) + emis_grid = torch.clone(lc_grid) + TmaxLST = torch.clone(lc_grid) for i in id: # row = (lc_class[:, 0] == id[i]) Tstart[Tstart == i] = solweig_parameters["Tstart"]["Value"][ - solweig_parameters["Names"]["Value"][str(i)] + solweig_parameters["Names"]["Value"][str((int(i.item())))] ] alb_grid[alb_grid == i] = solweig_parameters["Albedo"]["Effective"][ "Value" - ][solweig_parameters["Names"]["Value"][str(i)]] + ][solweig_parameters["Names"]["Value"][str((int(i.item())))]] emis_grid[emis_grid == i] = solweig_parameters["Emissivity"]["Value"][ - solweig_parameters["Names"]["Value"][str(i)] + solweig_parameters["Names"]["Value"][str((int(i.item())))] ] TmaxLST[TmaxLST == i] = solweig_parameters["TmaxLST"]["Value"][ - solweig_parameters["Names"]["Value"][str(i)] + solweig_parameters["Names"]["Value"][str((int(i.item())))] ] TgK[TgK == i] = solweig_parameters["Ts_deg"]["Value"][ - solweig_parameters["Names"]["Value"][str(i)] + solweig_parameters["Names"]["Value"][str((int(i.item())))] ] TgK_wall = solweig_parameters["Ts_deg"]["Value"]["Walls"] diff --git a/functions/SOLWEIGpython/cylindric_wedge.py b/functions/SOLWEIGpython/cylindric_wedge.py index 7564af8..1abd030 100644 --- a/functions/SOLWEIGpython/cylindric_wedge.py +++ b/functions/SOLWEIGpython/cylindric_wedge.py @@ -1,59 +1,62 @@ import numpy as np +import torch def cylindric_wedge(zen, svfalfa, rows, cols): - np.seterr(divide="ignore", invalid="ignore") # Fraction of sunlit walls based on sun altitude and svf wieghted building angles # input: # sun zenith angle "beta" # svf related angle "alfa" + device = svfalfa.device if isinstance(svfalfa, torch.Tensor) else torch.device( + "cuda" if torch.cuda.is_available() else "cpu" + ) beta = zen # alfa=svfalfa - alfa = np.zeros((rows, cols)) + svfalfa + alfa = torch.zeros((rows, cols), device=device) + svfalfa # measure the size of the image # sizex=size(svfalfa,2) # sizey=size(svfalfa,1) - xa = 1 - 2.0 / (np.tan(alfa) * np.tan(beta)) - ha = 2.0 / (np.tan(alfa) * np.tan(beta)) - ba = 1.0 / np.tan(alfa) + xa = 1 - 2.0 / (torch.tan(alfa) * torch.tan(beta)) + ha = 2.0 / (torch.tan(alfa) * torch.tan(beta)) + ba = 1.0 / torch.tan(alfa) hkil = 2.0 * ba * ha - qa = np.zeros((rows, cols)) + qa = torch.zeros((rows, cols), device=device) # qa(length(svfalfa),length(svfalfa))=0; - qa[xa < 0] = np.tan(beta) / 2 + qa[xa < 0] = torch.tan(beta) / 2 - Za = np.zeros((rows, cols)) + Za = torch.zeros((rows, cols), device=device) # Za(length(svfalfa),length(svfalfa))=0; - Za[xa < 0] = ((ba[xa < 0] ** 2) - ((qa[xa < 0] ** 2) / 4)) ** 0.5 + Za[xa < 0] = (((ba[xa < 0] ** 2) - ((qa[xa < 0] ** 2) / 4)) ** 0.5).float() - phi = np.zeros((rows, cols)) + phi = torch.zeros((rows, cols), device=device) # phi(length(svfalfa),length(svfalfa))=0; - phi[xa < 0] = np.arctan(Za[xa < 0] / qa[xa < 0]) + phi[xa < 0] = torch.arctan(Za[xa < 0] / qa[xa < 0]) - A = np.zeros((rows, cols)) + A = torch.zeros((rows, cols), device=device) # A(length(svfalfa),length(svfalfa))=0; - A[xa < 0] = (np.sin(phi[xa < 0]) - phi[xa < 0] * np.cos(phi[xa < 0])) / ( - 1 - np.cos(phi[xa < 0]) + A[xa < 0] = (torch.sin(phi[xa < 0]) - phi[xa < 0] * torch.cos(phi[xa < 0])) / ( + 1 - torch.cos(phi[xa < 0]) ) - ukil = np.zeros((rows, cols)) + ukil = torch.zeros((rows, cols), device=device) # ukil(length(svfalfa),length(svfalfa))=0 - ukil[xa < 0] = 2 * ba[xa < 0] * xa[xa < 0] * A[xa < 0] + ukil[xa < 0] = (2 * ba[xa < 0] * xa[xa < 0] * A[xa < 0]).float() Ssurf = hkil + ukil - F_sh = (2 * np.pi * ba - Ssurf) / (2 * np.pi * ba) # Xa + F_sh = (2 * torch.pi * ba - Ssurf) / (2 * torch.pi * ba) # Xa return F_sh def cylindric_wedge_voxel(zen, svfalfa): - np.seterr(divide="ignore", invalid="ignore") + torch.seterr(divide="ignore", invalid="ignore") # Fraction of sunlit walls based on sun altitude and svf wieghted building angles # input: @@ -67,35 +70,39 @@ def cylindric_wedge_voxel(zen, svfalfa): # sizex=size(svfalfa,2) # sizey=size(svfalfa,1) - xa = 1 - 2.0 / (np.tan(svfalfa) * np.tan(beta)) - ha = 2.0 / (np.tan(svfalfa) * np.tan(beta)) - ba = 1.0 / np.tan(svfalfa) + xa = 1 - 2.0 / (torch.tan(svfalfa) * torch.tan(beta)) + ha = 2.0 / (torch.tan(svfalfa) * torch.tan(beta)) + ba = 1.0 / torch.tan(svfalfa) hkil = 2.0 * ba * ha - qa = np.zeros((svfalfa.shape[0])) + device = svfalfa.device if isinstance(svfalfa, torch.Tensor) else torch.device( + "cuda" if torch.cuda.is_available() else "cpu" + ) + + qa = torch.zeros((svfalfa.shape[0]), device=device) # qa(length(svfalfa),length(svfalfa))=0; - qa[xa < 0] = np.tan(beta) / 2 + qa[xa < 0] = torch.tan(beta) / 2 - Za = np.zeros((svfalfa.shape[0])) + Za = torch.zeros((svfalfa.shape[0]), device=device) # Za(length(svfalfa),length(svfalfa))=0; Za[xa < 0] = ((ba[xa < 0] ** 2) - ((qa[xa < 0] ** 2) / 4)) ** 0.5 - phi = np.zeros((svfalfa.shape[0])) + phi = torch.zeros((svfalfa.shape[0]), device=device) # phi(length(svfalfa),length(svfalfa))=0; - phi[xa < 0] = np.arctan(Za[xa < 0] / qa[xa < 0]) + phi[xa < 0] = torch.arctan(Za[xa < 0] / qa[xa < 0]) - A = np.zeros((svfalfa.shape[0])) + A = torch.zeros((svfalfa.shape[0]), device=device) # A(length(svfalfa),length(svfalfa))=0; - A[xa < 0] = (np.sin(phi[xa < 0]) - phi[xa < 0] * np.cos(phi[xa < 0])) / ( - 1 - np.cos(phi[xa < 0]) + A[xa < 0] = (torch.sin(phi[xa < 0]) - phi[xa < 0] * torch.cos(phi[xa < 0])) / ( + 1 - torch.cos(phi[xa < 0]) ) - ukil = np.zeros((svfalfa.shape[0])) + ukil = torch.zeros((svfalfa.shape[0]), device=device) # ukil(length(svfalfa),length(svfalfa))=0 ukil[xa < 0] = 2 * ba[xa < 0] * xa[xa < 0] * A[xa < 0] Ssurf = hkil + ukil - F_sh = (2 * np.pi * ba - Ssurf) / (2 * np.pi * ba) # Xa + F_sh = (2 * torch.pi * ba - Ssurf) / (2 * torch.pi * ba) # Xa return F_sh diff --git a/functions/SOLWEIGpython/daylen.py b/functions/SOLWEIGpython/daylen.py index 442185e..075bec2 100644 --- a/functions/SOLWEIGpython/daylen.py +++ b/functions/SOLWEIGpython/daylen.py @@ -1,4 +1,4 @@ -import numpy as np +import torch def daylen(DOY, XLAT): @@ -7,15 +7,21 @@ def daylen(DOY, XLAT): # Sun angles. SOC limited for latitudes above polar circles. # Calculate daylength, sunrise and sunset (Eqn. 17) - RAD = np.pi / 180.0 + device = DOY.device if isinstance(DOY, torch.Tensor) else torch.device( + "cuda" if torch.cuda.is_available() else "cpu" + ) + if not isinstance(XLAT, torch.Tensor): + XLAT = torch.tensor(XLAT, device=device) - DEC = -23.45 * np.cos(2.0 * np.pi * (DOY + 10.0) / 365.0) + RAD = torch.tensor(torch.pi / 180.0, device=device) - SOC = np.tan(RAD * DEC) * np.tan(RAD * XLAT) - SOC = min(max(SOC, -1.0), 1.0) + DEC = -23.45 * torch.cos(2.0 * torch.pi * (DOY + 10.0) / 365.0) + + SOC = torch.tan(RAD * DEC) * torch.tan(RAD * XLAT) + SOC = torch.clamp(SOC, -1.0, 1.0) # SOC=alt - DAYL = 12.0 + 24.0 * np.arcsin(SOC) / np.pi + DAYL = 12.0 + 24.0 * torch.arcsin(SOC) / torch.pi SNUP = 12.0 - DAYL / 2.0 SNDN = 12.0 + DAYL / 2.0 diff --git a/functions/SOLWEIGpython/ground_surface.py b/functions/SOLWEIGpython/ground_surface.py index 4174f0f..92621c7 100644 --- a/functions/SOLWEIGpython/ground_surface.py +++ b/functions/SOLWEIGpython/ground_surface.py @@ -5,6 +5,7 @@ import numpy as np import math import matplotlib.pyplot as plt +import torch # Stefan-Boltzmann s constant SBC = 5.67e-8 @@ -26,7 +27,7 @@ def saturated_vp(T): R = 8.314 # Gas constant # August-Roche-Magnus approx. in Pa - qs = 6109.4 * np.exp(17.625 * T / (T + 243.04)) + qs = 6109.4 * torch.exp(17.625 * T / (T + 243.04)) # Clausius-Clapeyron equation slope = L * qs / R / (T + 273.15) ** 2 @@ -34,7 +35,7 @@ def saturated_vp(T): return slope, qs -def initiate_groundScheme(lc_grid, solweig_parameters, day, Ta, location): +def initiate_groundScheme(lc_grid, solweig_parameters, day, Ta, location, device): """ Setup the maps used in the ground scheme calculations depending on the landcover @@ -50,71 +51,71 @@ def initiate_groundScheme(lc_grid, solweig_parameters, day, Ta, location): # Get the landcover data from lc_grid array lc_grid[lc_grid >= 100] = 2 - id = np.unique(lc_grid) - id = id.astype(int) + id = torch.unique(lc_grid) + id = id.int() # Physical parameters grids - cap_grid = np.copy(lc_grid) # Heat capacity - diff_grid = np.copy(lc_grid) # Thermal diffusivity - a1_grid = np.copy(lc_grid) - a2_grid = np.copy(lc_grid) - a3_grid = np.copy(lc_grid) # The 3 OHM coefficients + cap_grid = torch.clone(lc_grid) # Heat capacity + diff_grid = torch.clone(lc_grid) # Thermal diffusivity + a1_grid = torch.clone(lc_grid) + a2_grid = torch.clone(lc_grid) + a3_grid = torch.clone(lc_grid) # The 3 OHM coefficients # Initial fluxes and temperature grids - Rn = np.zeros_like(lc_grid) # Net radiation - Rn_past = np.zeros_like(lc_grid) # Stored net radiation - G = np.zeros_like(lc_grid) # Ground heat flux - Tg = np.copy(lc_grid) # Surface temperature - Tm = np.copy(lc_grid) # Mean daily surface temperature + Rn = torch.zeros_like(lc_grid) # Net radiation + Rn_past = torch.zeros_like(lc_grid) # Stored net radiation + G = torch.zeros_like(lc_grid) # Ground heat flux + Tg = torch.clone(lc_grid) # Surface temperature + Tm = torch.clone(lc_grid) # Mean daily surface temperature for i in id: cap_grid[cap_grid == i] = solweig_parameters["Heat capacity"]["Value"][ - solweig_parameters["Names"]["Value"][str(i)] + solweig_parameters["Names"]["Value"][str((int(i.item())))] ] diff_grid[diff_grid == i] = solweig_parameters["Thermal_diffusivity"][ "Value" - ][solweig_parameters["Names"]["Value"][str(i)]] + ][solweig_parameters["Names"]["Value"][str((int(i.item())))]] # Coefficients of the OHM per land cover mean_a1 = solweig_parameters["OHM_coefficients"]["Values"][ - solweig_parameters["Names"]["Value"][str(i)] + solweig_parameters["Names"]["Value"][str((int(i.item())))] ][0] phi_a1 = solweig_parameters["OHM_coefficients"]["Values"][ - solweig_parameters["Names"]["Value"][str(i)] + solweig_parameters["Names"]["Value"][str((int(i.item())))] ][1] a1_grid[a1_grid == i] = mean_a1 * ( 1 + 0.33 - * np.sin(2 * np.pi / 365.25 * day + phi_a1) - * np.sign(location["latitude"]) + * torch.sin(2 * torch.pi / 365.25 * day + phi_a1) + * torch.sign(torch.tensor(location["latitude"], device=device)) ) a2_grid[a2_grid == i] = solweig_parameters["OHM_coefficients"][ "Values" - ][solweig_parameters["Names"]["Value"][str(i)]][2] + ][solweig_parameters["Names"]["Value"][str((int(i.item())))]][2] a3_grid[a3_grid == i] = solweig_parameters["OHM_coefficients"][ "Values" - ][solweig_parameters["Names"]["Value"][str(i)]][3] + ][solweig_parameters["Names"]["Value"][str((int(i.item())))]][3] # Initial ground surface temperature parameters offset_Tg = solweig_parameters["Tg_ini coefficients"]["Values"][ - solweig_parameters["Names"]["Value"][str(i)] + solweig_parameters["Names"]["Value"][str((int(i.item())))] ][0] ratio_Tg = solweig_parameters["Tg_ini coefficients"]["Values"][ - solweig_parameters["Names"]["Value"][str(i)] + solweig_parameters["Names"]["Value"][str((int(i.item())))] ][1] phi_Tg = solweig_parameters["Tg_ini coefficients"]["Values"][ - solweig_parameters["Names"]["Value"][str(i)] + solweig_parameters["Names"]["Value"][str((int(i.item())))] ][2] # Mean daily soil temperature parameters ampl_Tm = solweig_parameters["Tm_ini coefficients"]["Values"][ - solweig_parameters["Names"]["Value"][str(i)] + solweig_parameters["Names"]["Value"][str((int(i.item())))] ][0] phi_Tm = solweig_parameters["Tm_ini coefficients"]["Values"][ - solweig_parameters["Names"]["Value"][str(i)] + solweig_parameters["Names"]["Value"][str((int(i.item())))] ][1] offset_Tm = solweig_parameters["Tm_ini coefficients"]["Values"][ - solweig_parameters["Names"]["Value"][str(i)] + solweig_parameters["Names"]["Value"][str((int(i.item())))] ][2] if i == 0 or i == 1: @@ -126,16 +127,16 @@ def initiate_groundScheme(lc_grid, solweig_parameters, day, Ta, location): 1 + 1 / ratio_Tg - * np.sin(2 * np.pi / 365.25 * day + phi_Tg) - * np.sign(location["latitude"]) + * torch.sin(2 * torch.pi / 365.25 * day + phi_Tg) + * torch.sign(torch.tensor(location["latitude"], device=device)) ) + 4 ) Tm[Tm == i] = ( - np.mean(Ta) + torch.mean(Ta) + ampl_Tm - * np.sin(2 * np.pi / 365.25 * day + phi_Tm) - * np.sign(location["latitude"]) + * torch.sin(2 * torch.pi / 365.25 * day + phi_Tm) + * torch.sign(torch.tensor(location["latitude"], device=device)) + offset_Tm + 4 ) @@ -149,12 +150,12 @@ def initiate_groundScheme(lc_grid, solweig_parameters, day, Ta, location): 1 + 1 / ratio_Tg - * np.sin(2 * np.pi / 365.25 * day + phi_Tg) - * np.sign(location["latitude"]) + * torch.sin(2 * torch.pi / 365.25 * day + phi_Tg) + * torch.sign(torch.tensor(location["latitude"], device=device)) ) + 4 ) - Tm[Tm == i] = np.mean(Ta) + offset_Tm + Tm[Tm == i] = torch.mean(Ta) + offset_Tm elif i == 5: # For grass surfaces @@ -162,14 +163,14 @@ def initiate_groundScheme(lc_grid, solweig_parameters, day, Ta, location): 1 + 1 / ratio_Tg - * np.sin(2 * np.pi / 365.25 * day + phi_Tg) - * np.sign(location["latitude"]) + * torch.sin(2 * torch.pi / 365.25 * day + phi_Tg) + * torch.sign(torch.tensor(location["latitude"], device=device)) ) Tm[Tm == i] = ( - np.mean(Ta) + torch.mean(Ta) + ampl_Tm - * np.sin(2 * np.pi / 365.25 * day + phi_Tm) - * np.sign(location["latitude"]) + * torch.sin(2 * torch.pi / 365.25 * day + phi_Tm) + * torch.sign(torch.tensor(location["latitude"], device=device)) + offset_Tm ) @@ -182,16 +183,16 @@ def initiate_groundScheme(lc_grid, solweig_parameters, day, Ta, location): 1 + 1 / ratio_Tg - * np.sin(2 * np.pi / 365.25 * day + phi_Tg) - * np.sign(location["latitude"]) + * torch.sin(2 * torch.pi / 365.25 * day + phi_Tg) + * torch.sign(torch.tensor(location["latitude"], device=device)) ) + 2 ) Tm[Tm == i] = ( - np.mean(Ta) + torch.mean(Ta) + ampl_Tm - * np.sin(2 * np.pi / 365.25 * day + phi_Tm) - * np.sign(location["latitude"]) + * torch.sin(2 * torch.pi / 365.25 * day + phi_Tm) + * torch.sign(torch.tensor(location["latitude"], device=device)) + offset_Tm + 2 ) @@ -264,11 +265,12 @@ def surfaceTemperature_calc( :G: Ground heat flux for the current timestep """ + # Store the past ground surface temperature Tg_stored = Tg # Damping depths of the daily surface temperature wave - D = np.sqrt((2 * diff) / (2 * math.pi / 86400)) + D = torch.sqrt((2 * diff) / (2 * math.pi / 86400)) ### Runge Kutta method for the surface temperature calc # First estimate of the surface temperature and of the deep soil temperature given past ground heat flux @@ -291,10 +293,10 @@ def surfaceTemperature_calc( radCriterion = abs( a1 * (Rn_temp - Rn_past) ) # Criterion regarding the radiation step - mask = np.logical_and( + mask = torch.logical_and( deltaG > radCriterion, abs(shadow - shadow_past) > 0.5 ) # Grid of the pixels where the ground heat flux spikes - G_temp[mask] = G[mask] + np.sign(G_temp - G)[mask] * radCriterion[mask] + G_temp[mask] = G[mask] + torch.sign(G_temp - G)[mask] * radCriterion[mask] # Correction of the temperature estimates k2 = 2 * G_temp / cap / D - 2 * math.pi / 86400 * (Tg_temp - Tm) @@ -313,10 +315,10 @@ def surfaceTemperature_calc( radCriterion = abs( a1 * (Rn - Rn_past) ) # Criterion regarding the radiation step - mask = np.logical_and( + mask = torch.logical_and( deltaG > radCriterion, abs(shadow - shadow_past) > 0.5 ) # Grid of the pixels where the ground heat flux spikes - G[mask] = G_past[mask] + np.sign(G - G_past)[mask] * radCriterion[mask] + G[mask] = G_past[mask] + torch.sign(G - G_past)[mask] * radCriterion[mask] ### Water bodies surface temperature estimate beta = 0.45 # Amount of shortwave rad absorbed by the water surface layer @@ -324,13 +326,13 @@ def surfaceTemperature_calc( lamb = 2.260e6 # Latent heat of vaporisation rho = 1000 # Density of water (kg.m-3) Rn_water = ( - Kdown * (1 - alb) * (beta + (1 - beta) * (1 - np.exp(-1))) + Kdown * (1 - alb) * (beta + (1 - beta) * (1 - torch.exp(torch.tensor(-1, device=Tg.device)))) + Ldown - Lup ) # Net radiation for the top water layer beta described the transmitted rad _, es = saturated_vp(Tg) E = 0.0858 * (es / 1000) * (1 - RH / 100) / 3600 / 1000 * rho * lamb - deltaTg = np.copy(lc_grid) + deltaTg = torch.clone(lc_grid) deltaTg = ( timestep / cap @@ -380,9 +382,10 @@ def outgoingLongwave_calc( """ # Assessment of the distance from a pixel at which most of the radiation are received (cf view factor Lambert) - factor = 0.99 # Percentage of radiation accounted for + device = Tg.device + factor = torch.tensor(0.99, device=device) # Percentage of radiation accounted for zs = 1.1 # in m - r_max = zs * np.sqrt( + r_max = zs * torch.sqrt( factor / (1 - factor) ) # in m, maximum radius for the radiation calc @@ -403,33 +406,33 @@ def outgoingLongwave_calc( # step in meters between every iteration step = 1 - ### Initialize the ground view factor grids as np.zeros() + ### Initialize the ground view factor grids as torch.zeros() # Upwelling longwave radiation - gvfLup = np.zeros((rows, cols)) - gvfLupE = np.zeros((rows, cols)) - gvfLupS = np.zeros((rows, cols)) - gvfLupW = np.zeros((rows, cols)) - gvfLupN = np.zeros((rows, cols)) + gvfLup = torch.zeros((rows, cols), device=device) + gvfLupE = torch.zeros((rows, cols), device=device) + gvfLupS = torch.zeros((rows, cols), device=device) + gvfLupW = torch.zeros((rows, cols), device=device) + gvfLupN = torch.zeros((rows, cols), device=device) # Albedo of the sunlit surfaces - gvfalbsun = np.zeros((rows, cols)) - gvfalbsunE = np.zeros((rows, cols)) - gvfalbsunS = np.zeros((rows, cols)) - gvfalbsunW = np.zeros((rows, cols)) - gvfalbsunN = np.zeros((rows, cols)) + gvfalbsun = torch.zeros((rows, cols), device=device) + gvfalbsunE = torch.zeros((rows, cols), device=device) + gvfalbsunS = torch.zeros((rows, cols), device=device) + gvfalbsunW = torch.zeros((rows, cols), device=device) + gvfalbsunN = torch.zeros((rows, cols), device=device) # Albedo complete - gvfalbtot = np.zeros((rows, cols)) - gvfalbtotE = np.zeros((rows, cols)) - gvfalbtotS = np.zeros((rows, cols)) - gvfalbtotW = np.zeros((rows, cols)) - gvfalbtotN = np.zeros((rows, cols)) + gvfalbtot = torch.zeros((rows, cols), device=device) + gvfalbtotE = torch.zeros((rows, cols), device=device) + gvfalbtotS = torch.zeros((rows, cols), device=device) + gvfalbtotW = torch.zeros((rows, cols), device=device) + gvfalbtotN = torch.zeros((rows, cols), device=device) # Longwave radiation coming from the side - gvfLsideE = np.zeros((rows, cols)) - gvfLsideS = np.zeros((rows, cols)) - gvfLsideW = np.zeros((rows, cols)) - gvfLsideN = np.zeros((rows, cols)) + gvfLsideE = torch.zeros((rows, cols), device=device) + gvfLsideS = torch.zeros((rows, cols), device=device) + gvfLsideW = torch.zeros((rows, cols), device=device) + gvfLsideN = torch.zeros((rows, cols), device=device) # Add the radiation from the pixel directly below, only for the total gvf # Do not take the roofs into account for now @@ -441,8 +444,8 @@ def outgoingLongwave_calc( gvfalbtot = gvfalbtot + alb * view_factor * buildings # Division of the 360° field of view in 20 and convert the array in radian - azimuths = np.linspace(18, 360, num=20, endpoint=True) - azimuths = azimuths * (np.pi / 180) + azimuths = torch.linspace(18, 360, steps=20, device=device) + azimuths = azimuths * (torch.pi / 180) ### Loop for the number of azimuth values for azimuth in azimuths: @@ -451,25 +454,25 @@ def outgoingLongwave_calc( # Initialisation of the tables # First the ones containing the translated rasters (temporary) - building_temp = np.zeros((rows, cols)) - Lup_temp = np.zeros((rows, cols)) - Lwall_temp = np.zeros((rows, cols)) - albsun_temp = np.zeros((rows, cols)) - albtot_temp = np.zeros((rows, cols)) - sunlitwall_temp = np.zeros((rows, cols)) + building_temp = torch.zeros((rows, cols), device=device) + Lup_temp = torch.zeros((rows, cols), device=device) + Lwall_temp = torch.zeros((rows, cols), device=device) + albsun_temp = torch.zeros((rows, cols), device=device) + albtot_temp = torch.zeros((rows, cols), device=device) + sunlitwall_temp = torch.zeros((rows, cols), device=device) # Then the tables containing the sum of the radiations (or albedo) for this azimuth - Lup_sum = np.zeros((rows, cols)) - LsideE_sum = np.zeros((rows, cols)) - LsideN_sum = np.zeros((rows, cols)) - LsideW_sum = np.zeros((rows, cols)) - LsideS_sum = np.zeros((rows, cols)) - albsun_sum = np.zeros((rows, cols)) - albtot_sum = np.zeros((rows, cols)) + Lup_sum = torch.zeros((rows, cols), device=device) + LsideE_sum = torch.zeros((rows, cols), device=device) + LsideN_sum = torch.zeros((rows, cols), device=device) + LsideW_sum = torch.zeros((rows, cols), device=device) + LsideS_sum = torch.zeros((rows, cols), device=device) + albsun_sum = torch.zeros((rows, cols), device=device) + albtot_sum = torch.zeros((rows, cols), device=device) ### Shadow casting algorithm # Translation ranges from 1/2 a pixel to the max radius r_max - for r in np.arange(sizepx / 2, r_max, step=step): + for r in torch.arange(sizepx / 2, r_max, step=step): # Longwave radiation grids both at the ground level and from the walls Lup = ( SBC * emis * (Tg + 273.15) ** 4 + Ldown * (1 - emis) @@ -479,16 +482,16 @@ def outgoingLongwave_calc( ) # Step of the raster translation - dx = -np.cos(azimuth) - dy = -np.sin(azimuth) + dx = -torch.cos(azimuth) + dy = -torch.sin(azimuth) # Scale so that the grid is at least translated from 1px if abs(dx) > abs(dy): - dx = -r * np.sign(np.cos(azimuth)) - dy = -r * abs(np.tan(azimuth)) * np.sign(np.sin(azimuth)) + dx = -r * torch.sign(torch.cos(azimuth)) + dy = -r * abs(torch.tan(azimuth)) * torch.sign(torch.sin(azimuth)) else: - dx = -r / abs(np.tan(azimuth)) * np.sign(np.cos(azimuth)) - dy = -r * np.sign(np.sin(azimuth)) + dx = -r / abs(torch.tan(azimuth)) * torch.sign(torch.cos(azimuth)) + dy = -r * torch.sign(torch.sin(azimuth)) # Select the interested part of the initial raster and the translated one from their four corners and # translating toward the direction azimuth = 0° for dx > 0 @@ -571,7 +574,7 @@ def outgoingLongwave_calc( ] # Change the boolean building grid, if the px was already a building it remains one (px value = 0) - building_copy = np.min([building_copy, building_temp], axis=0) + building_copy = torch.min(building_copy, building_temp) # For each pixel add the translated Lup to the received rad if there where there is no building view_factor = ((r + step) ** 2 / (zs**2 + (r + step) ** 2)) - ( @@ -601,35 +604,35 @@ def outgoingLongwave_calc( ) # Finally add the radiation received from the side - dphi = np.arctan((r + step) / zs) - np.arctan(r / zs) - dtrigo = zs / np.sqrt(r**2 + zs**2) * r / np.sqrt( + dphi = torch.arctan((r + step) / zs) - torch.arctan(r / zs) + dtrigo = zs / torch.sqrt(r**2 + zs**2) * r / torch.sqrt( r**2 + zs**2 - ) - zs / np.sqrt((r + step) ** 2 + zs**2) * (r + step) / np.sqrt( + ) - zs / torch.sqrt((r + step) ** 2 + zs**2) * (r + step) / torch.sqrt( (r + step) ** 2 + zs**2 ) # Calculation of the solid angle for each of the cardinal points steradiansW, steradiansS, steradiansE, steradiansN = 0, 0, 0, 0 - if (azimuth >= 0) and (azimuth < np.pi): - dthetaW = 2 * np.pi / 20 + if (azimuth >= 0) and (azimuth < torch.pi): + dthetaW = 2 * torch.pi / 20 steradiansW += dthetaW * (dphi + dtrigo) / 2 - if (azimuth >= np.pi / 2) and (azimuth < 3 * np.pi / 2): - dthetaS = 2 * np.pi / 20 + if (azimuth >= torch.pi / 2) and (azimuth < 3 * torch.pi / 2): + dthetaS = 2 * torch.pi / 20 steradiansS += dthetaS * (dphi + dtrigo) / 2 - if (azimuth >= np.pi) and (azimuth < 2 * np.pi): - dthetaE = 2 * np.pi / 20 + if (azimuth >= torch.pi) and (azimuth < 2 * torch.pi): + dthetaE = 2 * torch.pi / 20 steradiansE += dthetaE * (dphi + dtrigo) / 2 - if (azimuth >= 3 * np.pi / 2) or (azimuth < np.pi / 2): - dthetaN = 2 * np.pi / 20 + if (azimuth >= 3 * torch.pi / 2) or (azimuth < torch.pi / 2): + dthetaN = 2 * torch.pi / 20 steradiansN += dthetaN * (dphi + dtrigo) / 2 - LsideW_sum += Lup_temp / np.pi * steradiansW * building_copy - LsideS_sum += Lup_temp / np.pi * steradiansS * building_copy - LsideE_sum += Lup_temp / np.pi * steradiansE * building_copy - LsideN_sum += Lup_temp / np.pi * steradiansN * building_copy + LsideW_sum += Lup_temp / torch.pi * steradiansW * building_copy + LsideS_sum += Lup_temp / torch.pi * steradiansS * building_copy + LsideE_sum += Lup_temp / torch.pi * steradiansE * building_copy + LsideN_sum += Lup_temp / torch.pi * steradiansN * building_copy ### Add the value for the computed part of the field of view gvfLup += Lup_sum @@ -637,25 +640,25 @@ def outgoingLongwave_calc( gvfalbtot += albtot_sum # Add the value if the azimuth correspond to the side of the compass - if (azimuth >= 0) and (azimuth < np.pi): + if (azimuth >= 0) and (azimuth < torch.pi): gvfLupW += Lup_sum gvfalbsunW += albsun_sum gvfalbtotW += albtot_sum gvfLsideW += LsideW_sum - if (azimuth >= np.pi / 2) and (azimuth < 3 * np.pi / 2): + if (azimuth >= torch.pi / 2) and (azimuth < 3 * torch.pi / 2): gvfLupS += Lup_sum gvfalbsunS += albsun_sum gvfalbtotS += albtot_sum gvfLsideS += LsideS_sum - if (azimuth >= np.pi) and (azimuth < 2 * np.pi): + if (azimuth >= torch.pi) and (azimuth < 2 * torch.pi): gvfLupE += Lup_sum gvfalbsunE += albsun_sum gvfalbtotE += albtot_sum gvfLsideE += LsideE_sum - if (azimuth >= 3 * np.pi / 2) or (azimuth < np.pi / 2): + if (azimuth >= 3 * torch.pi / 2) or (azimuth < torch.pi / 2): gvfLupN += Lup_sum gvfalbsunN += albsun_sum gvfalbtotN += albtot_sum diff --git a/processor/configsolweig.ini b/processor/configsolweig.ini index 4889102..becf3e3 100644 --- a/processor/configsolweig.ini +++ b/processor/configsolweig.ini @@ -6,31 +6,31 @@ #--------------------------------------------------------------------------------------------------------- ####### INPUTS ####### # output path -output_dir=C:\Run_UMEP\output\bin +output_dir=/home/lemap/Documents/suede/utils/tutorials/GVC_Validation_Setup/out/ # working dir -working_dir=C:/Run_UMEP +working_dir=/home/lemap/Documents/suede/utils/tutorials/GVC_Validation_Setup/ # parameters json file -para_json_path=C:/Run_UMEP/UMEP_processing_OHM/processor/parametersforsolweig.json +para_json_path=/home/lemap/Documents/suede/umep_process_execute/UMEP-processing/processor/parametersforsolweig.json # Input ground and building dsm -filepath_dsm=C:\Users\eliot\OneDrive\Documents\Docs\GUCG\Data\Data_Usable\GA\DSM_GA.tif +filepath_dsm=/home/lemap/Documents/suede/utils/tutorials/GVC_Validation_Setup/DSM_GVC.tif # Input vegetation dsm -filepath_cdsm=C:\Users\eliot\OneDrive\Documents\Docs\GUCG\Data\Data_Usable\GA\CDSM_GA.tif +filepath_cdsm=/home/lemap/Documents/suede/utils/tutorials/GVC_Validation_Setup/CDSM_GVC.tif # Input trunkzone vegetation dsm filepath_tdsm= # Input Digital Elevation Model -filepath_dem=C:\Users\eliot\OneDrive\Documents\Docs\GUCG\Data\Data_Usable\GA\DSM_GA.tif +filepath_dem=/home/lemap/Documents/suede/utils/tutorials/GVC_Validation_Setup/DEM_GVC.tif # Input Land cover dataset -filepath_lc=C:\Users\eliot\OneDrive\Documents\Docs\GUCG\Data\Data_Usable\GA\LC_GA.tif +filepath_lc=/home/lemap/Documents/suede/utils/tutorials/GVC_Validation_Setup/LC_GVC.tif # Input wall height raster -filepath_wh=C:\Users\eliot\OneDrive\Documents\Docs\GUCG\Data\Data_Usable\GA\wall_height_GA.tif +filepath_wh=/home/lemap/Documents/suede/utils/tutorials/GVC_Validation_Setup/wall_height_GVC.tif # Input wall aspect raster -filepath_wa=C:\Users\eliot\OneDrive\Documents\Docs\GUCG\Data\Data_Usable\GA\wall_aspect_GA.tif +filepath_wa=/home/lemap/Documents/suede/utils/tutorials/GVC_Validation_Setup/wall_aspect_GVC.tif # Skyview factor files -input_svf=C:\Users\eliot\OneDrive\Documents\Docs\GUCG\Data\Data_Usable\GA\svfs.zip +input_svf=/home/lemap/Documents/suede/utils/tutorials/GVC_Validation_Setup/svfs.zip # Input file for anisotrophic sky input_aniso= #Point of Interest file for ground -poi_file=C:\Users\eliot\OneDrive\Documents\Docs\GUCG\Data\Data_Usable\GA\POI_GA.shp +poi_file=/home/lemap/Documents/suede/utils/tutorials/GVC_Validation_Setup/POI_GVC.shp poi_field=id # Input file for wall temperture scheme (Wallenberg et al. 2025) input_wall= @@ -40,7 +40,7 @@ input_surf= woi_file= woi_field= # input meteorolgical file (i.e. forcing file) -input_met=C:\Users\eliot\OneDrive\Documents\Docs\GUCG\Data\Data_Usable\GA\MetFiles\MetFile_20060726.txt +input_met=/home/lemap/Documents/suede/utils/tutorials/GVC_Validation_Setup/MetFile20100523_Prepared.txt ## input settings ## # option to execute solweig outside of osgeo/qgis environment diff --git a/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload.py b/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload.py index e875532..cf01274 100644 --- a/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload.py +++ b/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload.py @@ -7,6 +7,7 @@ import numpy as np import datetime import calendar +import torch def Solweig_2015a_metdata_noload(inputdata, location, UTC): @@ -21,6 +22,7 @@ def Solweig_2015a_metdata_noload(inputdata, location, UTC): """ met = inputdata + device = met.device if isinstance(met, torch.Tensor) else torch.device("cpu") data_len = len(met[:, 0]) dectime = met[:, 1] + met[:, 2] / 24 + met[:, 3] / (60 * 24.0) dectimemin = met[:, 3] / (60 * 24.0) @@ -36,13 +38,13 @@ def Solweig_2015a_metdata_noload(inputdata, location, UTC): leafoff1 = 300 # TODO this should change # initialize matrices - altitude = np.empty(shape=(1, data_len)) - azimuth = np.empty(shape=(1, data_len)) - zen = np.empty(shape=(1, data_len)) - jday = np.empty(shape=(1, data_len)) - YYYY = np.empty(shape=(1, data_len)) - leafon = np.empty(shape=(1, data_len)) - altmax = np.empty(shape=(1, data_len)) + altitude = torch.empty(size=(1, data_len), device=device) + azimuth = torch.empty(size=(1, data_len), device=device) + zen = torch.empty(size=(1, data_len), device=device) + jday = torch.empty(size=(1, data_len), device=device) + YYYY = torch.empty(size=(1, data_len), device=device) + leafon = torch.empty(size=(1, data_len), device=device) + altmax = torch.empty(size=(1, data_len), device=device) sunmax = dict() @@ -53,12 +55,12 @@ def Solweig_2015a_metdata_noload(inputdata, location, UTC): int(met[i, 1]) - 1 ) # Finding maximum altitude in 15 min intervals (20141027) - if (i == 0) or (np.mod(dectime[i], np.floor(dectime[i])) == 0): + if (i == 0) or (torch.remainder(dectime[i], torch.floor(dectime[i])) == 0): fifteen = 0.0 sunmaximum = -90.0 sunmax["zenith"] = 90.0 - while sunmaximum <= 90.0 - np.array(sunmax["zenith"]).item(): - sunmaximum = 90.0 - np.array(sunmax["zenith"]).item() + while sunmaximum <= 90.0 - sunmax["zenith"]: + sunmaximum = 90.0 - sunmax["zenith"] fifteen = fifteen + 15.0 / 1440.0 HM = datetime.timedelta(days=(60 * 10) / 1440.0 + fifteen) YMDHM = YMD + HM @@ -70,9 +72,9 @@ def Solweig_2015a_metdata_noload(inputdata, location, UTC): sunmax = sp.sun_position(time, location) altmax[0, i] = sunmaximum - half = datetime.timedelta(days=halftimestepdec) - H = datetime.timedelta(hours=met[i, 2]) - M = datetime.timedelta(minutes=met[i, 3]) + half = datetime.timedelta(days=float(halftimestepdec)) + H = datetime.timedelta(hours=int(met[i, 2])) + M = datetime.timedelta(minutes=int(met[i, 3])) YMDHM = YMD + H + M - half time["year"] = YMDHM.year time["month"] = YMDHM.month @@ -80,26 +82,26 @@ def Solweig_2015a_metdata_noload(inputdata, location, UTC): time["hour"] = YMDHM.hour time["min"] = YMDHM.minute sun = sp.sun_position(time, location) - sun_zenith = np.array(sun["zenith"]).item() - sun_azimuth = np.array(sun["azimuth"]).item() + sun_zenith = sun["zenith"] + sun_azimuth = sun["azimuth"] if (sun_zenith > 89.0) & (sun_zenith <= 90.0): sun_zenith = 89.0 altitude[0, i] = 90.0 - sun_zenith - zen[0, i] = sun_zenith * (np.pi / 180.0) + zen[0, i] = sun_zenith * (torch.pi / 180.0) azimuth[0, i] = sun_azimuth # day of year and check for leap year if calendar.isleap(time["year"]): - dayspermonth = np.atleast_2d( - [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + dayspermonth = torch.atleast_2d(torch.tensor( + [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], device=device) ) else: - dayspermonth = np.atleast_2d( - [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + dayspermonth = torch.atleast_2d(torch.tensor( + [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], device=device) ) - # jday[0, i] = np.sum(dayspermonth[0, 0:time['month']-1]) + time['day'] # bug when a new day 20191015 + # jday[0, i] = torch.sum(dayspermonth[0, 0:time['month']-1]) + time['day'] # bug when a new day 20191015 YYYY[0, i] = met[i, 0] doy = YMD.timetuple().tm_yday jday[0, i] = doy diff --git a/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b.py b/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b.py index 3afcf08..004c507 100644 --- a/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b.py +++ b/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b.py @@ -5,6 +5,7 @@ from . import sun_distance import numpy as np import math +import torch def clearnessindex_2013b(zen, jday, Ta, RH, radG, location, P): @@ -30,7 +31,7 @@ def clearnessindex_2013b(zen, jday, Ta, RH, radG, location, P): jday ) # irradiance differences due to Sun-Earth distances m = ( - 35.0 * np.cos(zen) * ((1224.0 * (np.cos(zen) ** 2) + 1) ** (-1 / 2.0)) + 35.0 * torch.cos(zen) * ((1224.0 * (torch.cos(zen) ** 2) + 1) ** (-1 / 2.0)) ) # optical air mass at p=1013 Trpg = ( 1.021 - 0.084 * (m * (0.000949 * p + 0.051)) ** 0.5 @@ -64,34 +65,36 @@ def clearnessindex_2013b(zen, jday, Ta, RH, radG, location, P): G = G[2] elif jday > 244 and jday <= 335: G = G[3] + device = zen.device if isinstance(zen, torch.Tensor) else torch.device("cpu") + G = torch.tensor(G, device=device) # dewpoint calculation a2 = 17.27 b2 = 237.7 - Td = (b2 * (((a2 * Ta) / (b2 + Ta)) + np.log(RH))) / ( - a2 - (((a2 * Ta) / (b2 + Ta)) + np.log(RH)) + Td = (b2 * (((a2 * Ta) / (b2 + Ta)) + torch.log(RH))) / ( + a2 - (((a2 * Ta) / (b2 + Ta)) + torch.log(RH)) ) Td = (Td * 1.8) + 32 # Dewpoint (F) - u = np.exp(0.1133 - np.log(G + 1) + 0.0393 * Td) # Precipitable water + u = torch.exp(0.1133 - torch.log(G + 1) + 0.0393 * Td) # Precipitable water Tw = 1 - 0.077 * ( (u * m) ** 0.3 ) # Transmission coefficient for water vapor Tar = 0.935**m # Transmission coefficient for aerosols - I0 = Itoa * np.cos(zen) * Trpg * Tw * D * Tar - if abs(zen) > np.pi / 2: + I0 = Itoa * torch.cos(zen) * Trpg * Tw * D * Tar + if abs(zen) > torch.pi / 2: I0 = 0 - # b=I0==abs(zen)>np.pi/2 + # b=I0==abs(zen)>torch.pi/2 # I0(b==1)=0 # clear b; - if not (np.isreal(I0)): + if not (torch.isreal(I0)): I0 = 0 - corr = 0.1473 * np.log(90 - (zen / np.pi * 180)) + 0.3454 # 20070329 + corr = 0.1473 * torch.log(90 - (zen / torch.pi * 180)) + 0.3454 # 20070329 CIuncorr = radG / I0 CI = CIuncorr + (1 - corr) - I0et = Itoa * np.cos(zen) * D # extra terrestial solar radiation + I0et = Itoa * torch.cos(zen) * D # extra terrestial solar radiation Kt = radG / I0et if math.isnan(CI): CI = float("Inf") diff --git a/util/SEBESOLWEIGCommonFiles/create_patches.py b/util/SEBESOLWEIGCommonFiles/create_patches.py index cf30771..85a26fc 100644 --- a/util/SEBESOLWEIGCommonFiles/create_patches.py +++ b/util/SEBESOLWEIGCommonFiles/create_patches.py @@ -1,6 +1,5 @@ import numpy as np - def create_patches(patch_option): deg2rad = np.pi / 180 diff --git a/util/SEBESOLWEIGCommonFiles/diffusefraction.py b/util/SEBESOLWEIGCommonFiles/diffusefraction.py index 93a31d5..e033db6 100644 --- a/util/SEBESOLWEIGCommonFiles/diffusefraction.py +++ b/util/SEBESOLWEIGCommonFiles/diffusefraction.py @@ -1,5 +1,6 @@ from __future__ import division import numpy as np +import torch def diffusefraction(radG, altitude, Kt, Ta, RH): @@ -15,9 +16,9 @@ def diffusefraction(radG, altitude, Kt, Ta, RH): :return: """ - alfa = altitude * (np.pi / 180) + alfa = altitude * (torch.pi / 180) - if Ta <= -999.00 or RH <= -999.00 or np.isnan(Ta) or np.isnan(RH): + if Ta <= -999.00 or RH <= -999.00 or torch.isnan(Ta) or torch.isnan(RH): if Kt <= 0.3: radD = radG * (1.020 - 0.248 * Kt) elif Kt > 0.3 and Kt < 0.78: @@ -30,7 +31,7 @@ def diffusefraction(radG, altitude, Kt, Ta, RH): radD = radG * ( 1 - 0.232 * Kt - + 0.0239 * np.sin(alfa) + + 0.0239 * torch.sin(alfa) - 0.000682 * Ta + 0.0195 * RH ) @@ -38,16 +39,16 @@ def diffusefraction(radG, altitude, Kt, Ta, RH): radD = radG * ( 1.329 - 1.716 * Kt - + 0.267 * np.sin(alfa) + + 0.267 * torch.sin(alfa) - 0.00357 * Ta + 0.106 * RH ) else: radD = radG * ( - 0.426 * Kt - 0.256 * np.sin(alfa) + 0.00349 * Ta + 0.0734 * RH + 0.426 * Kt - 0.256 * torch.sin(alfa) + 0.00349 * Ta + 0.0734 * RH ) - radI = (radG - radD) / (np.sin(alfa)) + radI = (radG - radD) / (torch.sin(alfa)) # Corrections for low sun altitudes (20130307) if radI < 0: diff --git a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py index 8ac7ae0..d5c4745 100644 --- a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py +++ b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py @@ -2,6 +2,7 @@ from __future__ import division import numpy as np from math import radians +import torch # from scipy.ndimage.filters import median_filter diff --git a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py index 22256c4..09a2eea 100644 --- a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py +++ b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py @@ -1,52 +1,53 @@ from __future__ import division import numpy as np +import torch # import matplotlib.pylab as plt def shade_on_walls(azimuth, aspect, walls, dsm, f, shvoveg): # wall shadows wall parameterization - wallbol = (walls > 0).astype(float) + wallbol = (walls > 0).float() # Removing walls in shadow due to selfshadowing - azilow = azimuth - np.pi / 2 - azihigh = azimuth + np.pi / 2 - if azilow >= 0 and azihigh < 2 * np.pi: # 90 to 270 (SHADOW) + azilow = azimuth - torch.pi / 2 + azihigh = azimuth + torch.pi / 2 + if azilow >= 0 and azihigh < 2 * torch.pi: # 90 to 270 (SHADOW) facesh = ( - np.logical_or(aspect < azilow, aspect >= azihigh).astype(float) + torch.logical_or(aspect < azilow, aspect >= azihigh).float() - wallbol + 1 ) # TODO check - elif azilow < 0 and azihigh <= 2 * np.pi: # 0 to 90 - azilow = azilow + 2 * np.pi + elif azilow < 0 and azihigh <= 2 * torch.pi: # 0 to 90 + azilow = azilow + 2 * torch.pi facesh = ( - np.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 + torch.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 ) # (SHADOW) - elif azilow > 0 and azihigh >= 2 * np.pi: # 270 to 360 - azihigh -= 2 * np.pi + elif azilow > 0 and azihigh >= 2 * torch.pi: # 270 to 360 + azihigh -= 2 * torch.pi facesh = ( - np.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 + torch.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 ) # (SHADOW) shvo = f - dsm # building shadow volume - facesun = np.logical_and( - facesh + (walls > 0).astype(float) == 1, walls > 0 - ).astype(float) - wallsun = np.copy(walls - shvo) + facesun = torch.logical_and( + facesh + (walls > 0).float() == 1, walls > 0 + ).float() + wallsun = torch.clone(walls - shvo) wallsun[wallsun < 0] = 0 wallsun[facesh == 1] = 0 # Removing walls in "self"-shadow - wallsh = np.copy(walls - wallsun) + wallsh = torch.clone(walls - wallsun) wallshve = shvoveg * wallbol wallshve = wallshve - wallsh wallshve[wallshve < 0] = 0 - id = np.where(wallshve > walls) - wallshve[id] = walls[id] + id = torch.where(wallshve > walls) + wallshve[id] = walls[id].double() wallsun = wallsun - wallshve # problem with wallshve only - id = np.where(wallsun < 0) + id = torch.where(wallsun < 0) wallshve[id] = 0 wallsun[id] = 0 - # if np.sum(wallshve <= 0) == wallshve.size: + # if torch.sum(wallshve <= 0) == wallshve.size: # wallshve[:, :] = 0 return wallsh, wallsun, wallshve, facesh, facesun @@ -104,47 +105,48 @@ def shadowingfunction_wallheight_23( """ # conversion - degrees = np.pi / 180.0 + device = a.device if isinstance(a, torch.Tensor) else torch.device("cpu") + degrees = torch.pi / 180.0 azimuth *= degrees altitude *= degrees # measure the size of the image - sizex = np.shape(a)[0] - sizey = np.shape(a)[1] + sizex = a.shape[0] + sizey = a.shape[1] # initialise parameters - dx = 0 - dy = 0 - dz = 0 - temp = np.zeros((sizex, sizey)) - tempvegdem = np.zeros((sizex, sizey)) - tempvegdem2 = np.zeros((sizex, sizey)) - templastfabovea = np.zeros((sizex, sizey)) - templastgabovea = np.zeros((sizex, sizey)) + dx = torch.tensor(0.0, device=device) + dy = torch.tensor(0.0, device=device) + dz = torch.tensor(0.0, device=device) + temp = torch.zeros((sizex, sizey), device=device) + tempvegdem = torch.zeros((sizex, sizey), device=device) + tempvegdem2 = torch.zeros((sizex, sizey), device=device) + templastfabovea = torch.zeros((sizex, sizey), device=device) + templastgabovea = torch.zeros((sizex, sizey), device=device) bushplant = bush > 1 - sh = np.zeros((sizex, sizey)) # shadows from buildings - vbshvegsh = np.copy(sh) # vegetation blocking buildings - vegsh = np.add( - np.zeros((sizex, sizey)), bushplant, dtype=float - ) # vegetation shadow - f = np.copy(a) - shvoveg = np.copy(vegdem) # for vegetation shadowvolume - # g = np.copy(sh) - wallbol = (walls > 0).astype(float) + sh = torch.zeros((sizex, sizey), device=device) # shadows from buildings + vbshvegsh = torch.clone(sh) # vegetation blocking buildings + vegsh = torch.add( + torch.zeros((sizex, sizey), device=device), bushplant + ).float() # vegetation shadow + f = torch.clone(a) + shvoveg = torch.clone(vegdem) # for vegetation shadowvolume + # g = torch.clone(sh) + wallbol = (walls > 0).float() # other loop parameters - pibyfour = np.pi / 4 + pibyfour = torch.pi / 4 threetimespibyfour = 3 * pibyfour fivetimespibyfour = 5 * pibyfour seventimespibyfour = 7 * pibyfour - sinazimuth = np.sin(azimuth) - cosazimuth = np.cos(azimuth) - tanazimuth = np.tan(azimuth) - signsinazimuth = np.sign(sinazimuth) - signcosazimuth = np.sign(cosazimuth) - dssin = np.abs(1 / sinazimuth) - dscos = np.abs(1 / cosazimuth) - tanaltitudebyscale = np.tan(altitude) / scale + sinazimuth = torch.sin(azimuth) + cosazimuth = torch.cos(azimuth) + tanazimuth = torch.tan(azimuth) + signsinazimuth = torch.sign(sinazimuth) + signcosazimuth = torch.sign(cosazimuth) + dssin = torch.abs(1 / sinazimuth) + dscos = torch.abs(1 / cosazimuth) + tanaltitudebyscale = torch.tan(altitude) / scale index = 0 @@ -152,15 +154,15 @@ def shadowingfunction_wallheight_23( dzprev = 0 # main loop - while (amaxvalue >= dz) and (np.abs(dx) < sizex) and (np.abs(dy) < sizey): + while (amaxvalue >= dz) and (torch.abs(dx) < sizex) and (torch.abs(dy) < sizey): if ((pibyfour <= azimuth) and (azimuth < threetimespibyfour)) or ( (fivetimespibyfour <= azimuth) and (azimuth < seventimespibyfour) ): dy = signsinazimuth * index - dx = -1 * signcosazimuth * np.abs(np.round(index / tanazimuth)) + dx = -1 * signcosazimuth * torch.abs(torch.round(index / tanazimuth)) ds = dssin else: - dy = signsinazimuth * np.abs(np.round(index * tanazimuth)) + dy = signsinazimuth * torch.abs(torch.round(index * tanazimuth)) dx = -1 * signcosazimuth * index ds = dscos @@ -171,8 +173,8 @@ def shadowingfunction_wallheight_23( temp[0:sizex, 0:sizey] = 0 templastfabovea[0:sizex, 0:sizey] = 0.0 templastgabovea[0:sizex, 0:sizey] = 0.0 - absdx = np.abs(dx) - absdy = np.abs(dy) + absdx = torch.abs(dx) + absdy = torch.abs(dy) xc1 = int((dx + absdx) / 2) xc2 = int(sizex + (dx - absdx) / 2) yc1 = int((dy + absdy) / 2) @@ -186,15 +188,15 @@ def shadowingfunction_wallheight_23( tempvegdem2[xp1:xp2, yp1:yp2] = vegdem2[xc1:xc2, yc1:yc2] - dz temp[xp1:xp2, yp1:yp2] = a[xc1:xc2, yc1:yc2] - dz - f = np.fmax(f, temp) # Moving building shadow - shvoveg = np.fmax( + f = torch.fmax(f, temp) # Moving building shadow + shvoveg = torch.fmax( shvoveg, tempvegdem ) # moving vegetation shadow volume sh[f > a] = 1 sh[f <= a] = 0 - fabovea = (tempvegdem > a).astype(int) # vegdem above DEM - gabovea = (tempvegdem2 > a).astype(int) # vegdem2 above DEM + fabovea = (tempvegdem > a).int() # vegdem above DEM + gabovea = (tempvegdem2 > a).int() # vegdem2 above DEM # new pergola condition templastfabovea[xp1:xp2, yp1:yp2] = vegdem[xc1:xc2, yc1:yc2] - dzprev @@ -202,21 +204,20 @@ def shadowingfunction_wallheight_23( lastfabovea = templastfabovea > a lastgabovea = templastgabovea > a dzprev = dz - vegsh2 = np.add( - np.add( - np.add(fabovea, gabovea, dtype=float), lastfabovea, dtype=float - ), + vegsh2 = torch.add( + torch.add( + torch.add(fabovea, gabovea).float(), lastfabovea + ).float(), lastgabovea, - dtype=float, - ) + ).float() vegsh2[vegsh2 == 4] = 0.0 # vegsh2[vegsh2 == 1] = 0. # This one is the ultimate question... vegsh2[vegsh2 > 0] = 1.0 - vegsh = np.fmax(vegsh, vegsh2) + vegsh = torch.fmax(vegsh, vegsh2) vegsh[vegsh * sh > 0] = 0 vbshvegsh = ( - np.copy(vegsh) + vbshvegsh + torch.clone(vegsh) + vbshvegsh ) # removing shadows 'behind' buildings index += 1 @@ -229,17 +230,17 @@ def shadowingfunction_wallheight_23( shvoveg = (shvoveg - a) * vegsh # Vegetation shadow volume vegsh = 1 - vegsh vbshvegsh = 1 - vbshvegsh - # print(np.max(shvoveg)) + # print(torch.max(shvoveg)) wallsh, wallsun, wallshve, facesh, facesun = shade_on_walls( azimuth, aspect, walls, a, f, shvoveg ) - # print(np.max(wallshve)) + # print(torch.max(wallshve)) if walls_scheme is not False: wallsh_, wallsun_, wallshve_, facesh_, facesun_ = shade_on_walls( azimuth, aspect_scheme, walls_scheme, a, f, shvoveg ) - # print(np.max(wallshve_)) - shade_on_wall = wallsh_.copy() + # print(torch.max(wallshve_)) + shade_on_wall = wallsh_.clone() shade_on_wall[shade_on_wall < wallshve_] = wallshve_[ shade_on_wall < wallshve_ ] diff --git a/util/SEBESOLWEIGCommonFiles/sun_distance.py b/util/SEBESOLWEIGCommonFiles/sun_distance.py index a46a91d..003e479 100644 --- a/util/SEBESOLWEIGCommonFiles/sun_distance.py +++ b/util/SEBESOLWEIGCommonFiles/sun_distance.py @@ -1,5 +1,5 @@ __author__ = "xlinfr" -import numpy as np +import torch def sun_distance(jday): @@ -9,14 +9,14 @@ def sun_distance(jday): #% with day of year as input. #% Partridge and Platt, 1975 """ - b = 2.0 * np.pi * jday / 365.0 - D = np.sqrt( + b = 2.0 * torch.pi * jday / 365.0 + D = torch.sqrt( ( 1.00011 - + np.dot(0.034221, np.cos(b)) - + np.dot(0.001280, np.sin(b)) - + np.dot(0.000719, np.cos((2.0 * b))) - + np.dot(0.000077, np.sin((2.0 * b))) + + 0.034221 * torch.cos(b) + + 0.001280 * torch.sin(b) + + 0.000719 * torch.cos(2.0 * b) + + 0.000077 * torch.sin(2.0 * b) ) ) return D diff --git a/util/SEBESOLWEIGCommonFiles/sun_position.py b/util/SEBESOLWEIGCommonFiles/sun_position.py index 4bd8075..9acf685 100644 --- a/util/SEBESOLWEIGCommonFiles/sun_position.py +++ b/util/SEBESOLWEIGCommonFiles/sun_position.py @@ -3,6 +3,7 @@ from __future__ import print_function import datetime import numpy as np +import torch def sun_position(time, location): @@ -180,6 +181,7 @@ def sun_position(time, location): def julian_calculation(t_input): + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") """ % This function compute the julian day and julian century from the local % time and timezone information. Ephemeris are calculated with a delta_t=0 @@ -204,20 +206,19 @@ def julian_calculation(t_input): time = t_input if time["month"] == 1 or time["month"] == 2: - Y = time["year"] - 1 - M = time["month"] + 12 + Y = torch.tensor(time["year"] - 1, device=device) + M = torch.tensor(time["month"] + 12, device=device) else: - Y = time["year"] - M = time["month"] + Y = torch.tensor(time["year"], device=device) + M = torch.tensor(time["month"], device=device) - ut_time = ( + ut_time = torch.tensor( ((time["hour"] - time["UTC"]) / 24) + (time["min"] / (60 * 24)) + (time["sec"] / (60 * 60 * 24)) - ) # time of day in UT time. - D = ( - time["day"] + ut_time - ) # Day of month in decimal time, ex. 2sd day of month at 12:30:30UT, D=2.521180556 + , device=device) # time of day in UT time. + D = time["day"] + ut_time + # Day of month in decimal time, ex. 2sd day of month at 12:30:30UT, D=2.521180556 # In 1582, the gregorian calendar was adopted if time["year"] == 1582: @@ -225,36 +226,36 @@ def julian_calculation(t_input): if ( time["day"] <= 4 ): # The Julian calendar ended on October 4, 1582 - B = 0 + B = torch.tensor(0, device=device) elif ( time["day"] >= 15 ): # The Gregorian calendar started on October 15, 1582 - A = np.floor(Y / 100) - B = 2 - A + np.floor(A / 4) + A = torch.floor(Y / 100) + B = 2 - A + torch.floor(A / 4) else: print( "This date never existed!. Date automatically set to October 4, 1582" ) time["month"] = 10 time["day"] = 4 - B = 0 + B = torch.tensor(0, device=device) elif time["month"] < 10: # Julian calendar - B = 0 + B = torch.tensor(0, device=device) else: # Gregorian calendar - A = np.floor(Y / 100) - B = 2 - A + np.floor(A / 4) + A = torch.floor(Y / 100) + B = 2 - A + torch.floor(A / 4) elif time["year"] < 1582: # Julian calendar - B = 0 + B = torch.tensor(0, device=device) else: - A = np.floor(Y / 100) # Gregorian calendar - B = 2 - A + np.floor(A / 4) + A = torch.floor(Y / 100) # Gregorian calendar + B = 2 - A + torch.floor(A / 4) julian = dict() julian["day"] = ( D + B - + np.floor(365.25 * (Y + 4716)) - + np.floor(30.6001 * (M + 1)) + + torch.floor(365.25 * (Y + 4716)) + + torch.floor(30.6001 * (M + 1)) - 1524.5 ) @@ -268,6 +269,7 @@ def julian_calculation(t_input): def earth_heliocentric_position_calculation(julian): + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") """ % This function compute the earth position relative to the sun, using % tabulated values. @@ -277,7 +279,7 @@ def earth_heliocentric_position_calculation(julian): """ # Tabulated values for the longitude calculation # L terms from the original code. - L0_terms = np.array( + L0_terms = torch.tensor( [ [175347046.0, 0, 0], [3341656.0, 4.6692568, 6283.07585], @@ -344,9 +346,9 @@ def earth_heliocentric_position_calculation(julian): [30, 2.74, 1349.87], [25, 3.16, 4690.48], ] - ) + , device=device) - L1_terms = np.array( + L1_terms = torch.tensor( [ [628331966747.0, 0, 0], [206059.0, 2.678235, 6283.07585], @@ -383,9 +385,9 @@ def earth_heliocentric_position_calculation(julian): [6, 2.65, 9437.76], [6, 4.67, 4690.48], ] - ) + , device=device) - L2_terms = np.array( + L2_terms = torch.tensor( [ [52919.0, 0, 0], [8720.0, 1.0721, 6283.0758], @@ -408,9 +410,9 @@ def earth_heliocentric_position_calculation(julian): [2, 4.38, 5223.69], [2, 3.75, 0.98], ] - ) + , device=device) - L3_terms = np.array( + L3_terms = torch.tensor( [ [289.0, 5.844, 6283.076], [35, 0, 0], @@ -420,13 +422,13 @@ def earth_heliocentric_position_calculation(julian): [1, 5.3, 18849.23], [1, 5.97, 242.73], ] - ) - L4_terms = np.array( + , device=device) + L4_terms = torch.tensor( [[114.0, 3.142, 0], [8, 4.13, 6283.08], [1, 3.84, 12566.15]] - ) + , device=device) - L5_terms = np.array([1, 3.14, 0]) - L5_terms = np.atleast_2d( + L5_terms = torch.tensor([1, 3.14, 0], device=device) + L5_terms = torch.atleast_2d( L5_terms ) # since L5_terms is 1D, we have to convert it to 2D to avoid indexErrors @@ -457,25 +459,25 @@ def earth_heliocentric_position_calculation(julian): JME = julian["ephemeris_millenium"] # Compute the Earth Heliochentric longitude from the tabulated values. - L0 = np.sum(A0 * np.cos(B0 + (C0 * JME))) - L1 = np.sum(A1 * np.cos(B1 + (C1 * JME))) - L2 = np.sum(A2 * np.cos(B2 + (C2 * JME))) - L3 = np.sum(A3 * np.cos(B3 + (C3 * JME))) - L4 = np.sum(A4 * np.cos(B4 + (C4 * JME))) - L5 = A5 * np.cos(B5 + (C5 * JME)) + L0 = torch.sum(A0 * torch.cos(B0 + (C0 * JME))) + L1 = torch.sum(A1 * torch.cos(B1 + (C1 * JME))) + L2 = torch.sum(A2 * torch.cos(B2 + (C2 * JME))) + L3 = torch.sum(A3 * torch.cos(B3 + (C3 * JME))) + L4 = torch.sum(A4 * torch.cos(B4 + (C4 * JME))) + L5 = A5 * torch.cos(B5 + (C5 * JME)) earth_heliocentric_position = dict() earth_heliocentric_position["longitude"] = ( L0 + (L1 * JME) - + (L2 * np.power(JME, 2)) - + (L3 * np.power(JME, 3)) - + (L4 * np.power(JME, 4)) - + (L5 * np.power(JME, 5)) + + (L2 * torch.pow(JME, 2)) + + (L3 * torch.pow(JME, 3)) + + (L4 * torch.pow(JME, 4)) + + (L5 * torch.pow(JME, 5)) ) / 1e8 # Convert the longitude to degrees. earth_heliocentric_position["longitude"] = ( - earth_heliocentric_position["longitude"] * 180 / np.pi + earth_heliocentric_position["longitude"] * 180 / torch.pi ) # Limit the range to [0,360] @@ -485,7 +487,7 @@ def earth_heliocentric_position_calculation(julian): # Tabulated values for the earth heliocentric latitude. # B terms from the original code. - B0_terms = np.array( + B0_terms = torch.tensor( [ [280.0, 3.199, 84334.662], [102.0, 5.422, 5507.553], @@ -493,9 +495,9 @@ def earth_heliocentric_position_calculation(julian): [44, 3.7, 2352.87], [32, 4, 1577.34], ] - ) + , device=device) - B1_terms = np.array([[9, 3.9, 5507.55], [6, 1.73, 5223.69]]) + B1_terms = torch.tensor([[9, 3.9, 5507.55], [6, 1.73, 5223.69]], device=device) A0 = B0_terms[:, 0] B0 = B0_terms[:, 1] @@ -505,14 +507,14 @@ def earth_heliocentric_position_calculation(julian): B1 = B1_terms[:, 1] C1 = B1_terms[:, 2] - L0 = np.sum(A0 * np.cos(B0 + (C0 * JME))) - L1 = np.sum(A1 * np.cos(B1 + (C1 * JME))) + L0 = torch.sum(A0 * torch.cos(B0 + (C0 * JME))) + L1 = torch.sum(A1 * torch.cos(B1 + (C1 * JME))) earth_heliocentric_position["latitude"] = (L0 + (L1 * JME)) / 1e8 # Convert the latitude to degrees. earth_heliocentric_position["latitude"] = ( - earth_heliocentric_position["latitude"] * 180 / np.pi + earth_heliocentric_position["latitude"] * 180 / torch.pi ) # Limit the range to [0,360]; @@ -522,7 +524,7 @@ def earth_heliocentric_position_calculation(julian): # Tabulated values for radius vector. # R terms from the original code - R0_terms = np.array( + R0_terms = torch.tensor( [ [100013989.0, 0, 0], [1670700.0, 3.0984635, 6283.07585], @@ -565,9 +567,9 @@ def earth_heliocentric_position_calculation(julian): [28, 1.9, 6279.55], [26, 4.59, 10447.39], ] - ) + , device=device) - R1_terms = np.array( + R1_terms = torch.tensor( [ [103019.0, 1.10749, 6283.07585], [1721.0, 1.0644, 12566.1517], @@ -580,9 +582,9 @@ def earth_heliocentric_position_calculation(julian): [9, 1.42, 6275.96], [9, 0.27, 5486.78], ] - ) + , device=device) - R2_terms = np.array( + R2_terms = torch.tensor( [ [4359.0, 5.7846, 6283.0758], [124.0, 5.579, 12566.152], @@ -591,14 +593,14 @@ def earth_heliocentric_position_calculation(julian): [6, 1.87, 5573.14], [3, 5.47, 18849], ] - ) + , device=device) - R3_terms = np.array([[145.0, 4.273, 6283.076], [7, 3.92, 12566.15]]) + R3_terms = torch.tensor([[145.0, 4.273, 6283.076], [7, 3.92, 12566.15]], device=device) R4_terms = [4, 2.56, 6283.08] - R4_terms = np.atleast_2d( - R4_terms - ) # since L5_terms is 1D, we have to convert it to 2D to avoid indexErrors + # Force it to be a tensor first, then ensure it's 2D + R4_terms = torch.atleast_2d( + torch.tensor(R4_terms, device=device)) # since L5_terms is 1D, we have to convert it to 2D to avoid indexErrors A0 = R0_terms[:, 0] B0 = R0_terms[:, 1] @@ -621,19 +623,19 @@ def earth_heliocentric_position_calculation(julian): C4 = R4_terms[:, 2] # Compute the Earth heliocentric radius vector - L0 = np.sum(A0 * np.cos(B0 + (C0 * JME))) - L1 = np.sum(A1 * np.cos(B1 + (C1 * JME))) - L2 = np.sum(A2 * np.cos(B2 + (C2 * JME))) - L3 = np.sum(A3 * np.cos(B3 + (C3 * JME))) - L4 = A4 * np.cos(B4 + (C4 * JME)) + L0 = torch.sum(A0 * torch.cos(B0 + (C0 * JME))) + L1 = torch.sum(A1 * torch.cos(B1 + (C1 * JME))) + L2 = torch.sum(A2 * torch.cos(B2 + (C2 * JME))) + L3 = torch.sum(A3 * torch.cos(B3 + (C3 * JME))) + L4 = A4 * torch.cos(B4 + (C4 * JME)) # Units are in AU earth_heliocentric_position["radius"] = ( L0 + (L1 * JME) - + (L2 * np.power(JME, 2)) - + (L3 * np.power(JME, 3)) - + (L4 * np.power(JME, 4)) + + (L2 * torch.pow(JME, 2)) + + (L3 * torch.pow(JME, 3)) + + (L4 * torch.pow(JME, 4)) ) / 1e8 return earth_heliocentric_position @@ -663,6 +665,7 @@ def sun_geocentric_position_calculation(earth_heliocentric_position): def nutation_calculation(julian): + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") """ % This function compute the nutation in longtitude and in obliquity, in % degrees. @@ -674,63 +677,63 @@ def nutation_calculation(julian): JCE = julian["ephemeris_century"] # 1. Mean elongation of the moon from the sun - p = np.atleast_2d([(1 / 189474), -0.0019142, 445267.11148, 297.85036]) + p = torch.atleast_2d(torch.tensor([(1 / 189474), -0.0019142, 445267.11148, 297.85036], device=device)) # X0 = polyval(p, JCE); X0 = ( - p[0, 0] * np.power(JCE, 3) - + p[0, 1] * np.power(JCE, 2) + p[0, 0] * torch.pow(JCE, 3) + + p[0, 1] * torch.pow(JCE, 2) + p[0, 2] * JCE + p[0, 3] ) # This is faster than polyval... # 2. Mean anomaly of the sun (earth) - p = np.atleast_2d([-(1 / 300000), -0.0001603, 35999.05034, 357.52772]) + p = torch.atleast_2d(torch.tensor([-(1 / 300000), -0.0001603, 35999.05034, 357.52772], device=device)) # X1 = polyval(p, JCE) X1 = ( - p[0, 0] * np.power(JCE, 3) - + p[0, 1] * np.power(JCE, 2) + p[0, 0] * torch.pow(JCE, 3) + + p[0, 1] * torch.pow(JCE, 2) + p[0, 2] * JCE + p[0, 3] ) # 3. Mean anomaly of the moon - p = np.atleast_2d([(1 / 56250), 0.0086972, 477198.867398, 134.96298]) + p = torch.atleast_2d(torch.tensor([(1 / 56250), 0.0086972, 477198.867398, 134.96298], device=device)) # X2 = polyval(p, JCE); X2 = ( - p[0, 0] * np.power(JCE, 3) - + p[0, 1] * np.power(JCE, 2) + p[0, 0] * torch.pow(JCE, 3) + + p[0, 1] * torch.pow(JCE, 2) + p[0, 2] * JCE + p[0, 3] ) # 4. Moon argument of latitude - p = np.atleast_2d([(1 / 327270), -0.0036825, 483202.017538, 93.27191]) + p = torch.atleast_2d(torch.tensor([(1 / 327270), -0.0036825, 483202.017538, 93.27191], device=device)) # X3 = polyval(p, JCE) X3 = ( - p[0, 0] * np.power(JCE, 3) - + p[0, 1] * np.power(JCE, 2) + p[0, 0] * torch.pow(JCE, 3) + + p[0, 1] * torch.pow(JCE, 2) + p[0, 2] * JCE + p[0, 3] ) # 5. Longitude of the ascending node of the moon's mean orbit on the # ecliptic, measured from the mean equinox of the date - p = np.atleast_2d([(1 / 450000), 0.0020708, -1934.136261, 125.04452]) + p = torch.atleast_2d(torch.tensor([(1 / 450000), 0.0020708, -1934.136261, 125.04452], device=device)) # X4 = polyval(p, JCE); X4 = ( - p[0, 0] * np.power(JCE, 3) - + p[0, 1] * np.power(JCE, 2) + p[0, 0] * torch.pow(JCE, 3) + + p[0, 1] * torch.pow(JCE, 2) + p[0, 2] * JCE + p[0, 3] ) # Y tabulated terms from the original code - Y_terms = np.array( + Y_terms = torch.tensor( [ [0, 0, 0, 0, 1], [-2, 0, 0, 2, 2], @@ -796,9 +799,9 @@ def nutation_calculation(julian): [0, 0, 3, 2, 2], [2, -1, 0, 2, 2], ] - ) + , device=device) - nutation_terms = np.array( + nutation_terms = torch.tensor( [ [-171996, -174.2, 92025, 8.9], [-13187, -1.6, 5736, -3.1], @@ -864,32 +867,33 @@ def nutation_calculation(julian): [-3, 0, 0, 0], [-3, 0, 0, 0], ] - ) + , device=device) # Using the tabulated values, compute the delta_longitude and # delta_obliquity. - Xi = np.array([X0, X1, X2, X3, X4]) # a col mat in octave + Xi = torch.tensor([X0, X1, X2, X3, X4], device=device) # a col mat in octave - tabulated_argument = Y_terms.dot(np.transpose(Xi)) * (np.pi / 180) + tabulated_argument = torch.matmul(Y_terms.float(), Xi) * (torch.pi / 180) delta_longitude = ( nutation_terms[:, 0] + (nutation_terms[:, 1] * JCE) - ) * np.sin(tabulated_argument) + ) * torch.sin(tabulated_argument) delta_obliquity = ( nutation_terms[:, 2] + (nutation_terms[:, 3] * JCE) - ) * np.cos(tabulated_argument) + ) * torch.cos(tabulated_argument) nutation = dict() # init nutation dictionary # Nutation in longitude - nutation["longitude"] = np.sum(delta_longitude) / 36000000 + nutation["longitude"] = torch.sum(delta_longitude) / 36000000 # Nutation in obliquity - nutation["obliquity"] = np.sum(delta_obliquity) / 36000000 + nutation["obliquity"] = torch.sum(delta_obliquity) / 36000000 return nutation def true_obliquity_calculation(julian, nutation): + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") """ This function compute the true obliquity of the ecliptic. @@ -898,8 +902,8 @@ def true_obliquity_calculation(julian, nutation): :return: """ - p = np.atleast_2d( - [ + p = torch.atleast_2d( + torch.tensor([ 2.45, 5.79, 27.87, @@ -911,21 +915,21 @@ def true_obliquity_calculation(julian, nutation): -1.55, -4680.93, 84381.448, - ] + ], device=device) ) # mean_obliquity = polyval(p, julian.ephemeris_millenium/10); U = julian["ephemeris_millenium"] / 10 mean_obliquity = ( - p[0, 0] * np.power(U, 10) - + p[0, 1] * np.power(U, 9) - + p[0, 2] * np.power(U, 8) - + p[0, 3] * np.power(U, 7) - + p[0, 4] * np.power(U, 6) - + p[0, 5] * np.power(U, 5) - + p[0, 6] * np.power(U, 4) - + p[0, 7] * np.power(U, 3) - + p[0, 8] * np.power(U, 2) + p[0, 0] * torch.pow(U, 10) + + p[0, 1] * torch.pow(U, 9) + + p[0, 2] * torch.pow(U, 8) + + p[0, 3] * torch.pow(U, 7) + + p[0, 4] * torch.pow(U, 6) + + p[0, 5] * torch.pow(U, 5) + + p[0, 6] * torch.pow(U, 4) + + p[0, 7] * torch.pow(U, 3) + + p[0, 8] * torch.pow(U, 2) + p[0, 9] * U + p[0, 10] ) @@ -985,15 +989,15 @@ def apparent_stime_at_greenwich_calculation(julian, nutation, true_obliquity): mean_stime = ( 280.46061837 + (360.98564736629 * (JD - 2451545)) - + (0.000387933 * np.power(JC, 2)) - - (np.power(JC, 3) / 38710000) + + (0.000387933 * torch.pow(JC, 2)) + - (torch.pow(JC, 3) / 38710000) ) # Limit the range to [0-360]; mean_stime = set_to_range(mean_stime, 0, 360) apparent_stime_at_greenwich = mean_stime + ( - nutation["longitude"] * np.cos(true_obliquity * np.pi / 180) + nutation["longitude"] * torch.cos(true_obliquity * torch.pi / 180) ) return apparent_stime_at_greenwich @@ -1010,16 +1014,16 @@ def sun_rigth_ascension_calculation( """ argument_numerator = ( - np.sin(apparent_sun_longitude * np.pi / 180) - * np.cos(true_obliquity * np.pi / 180) + torch.sin(apparent_sun_longitude * torch.pi / 180) + * torch.cos(true_obliquity * torch.pi / 180) ) - ( - np.tan(sun_geocentric_position["latitude"] * np.pi / 180) - * np.sin(true_obliquity * np.pi / 180) + torch.tan(sun_geocentric_position["latitude"] * torch.pi / 180) + * torch.sin(true_obliquity * torch.pi / 180) ) - argument_denominator = np.cos(apparent_sun_longitude * np.pi / 180) + argument_denominator = torch.cos(apparent_sun_longitude * torch.pi / 180) sun_rigth_ascension = ( - np.arctan2(argument_numerator, argument_denominator) * 180 / np.pi + torch.arctan2(argument_numerator, argument_denominator) * 180 / torch.pi ) # Limit the range to [0,360]; sun_rigth_ascension = set_to_range(sun_rigth_ascension, 0, 360) @@ -1038,15 +1042,15 @@ def sun_geocentric_declination_calculation( """ argument = ( - np.sin(sun_geocentric_position["latitude"] * np.pi / 180) - * np.cos(true_obliquity * np.pi / 180) + torch.sin(sun_geocentric_position["latitude"] * torch.pi / 180) + * torch.cos(true_obliquity * torch.pi / 180) ) + ( - np.cos(sun_geocentric_position["latitude"] * np.pi / 180) - * np.sin(true_obliquity * np.pi / 180) - * np.sin(apparent_sun_longitude * np.pi / 180) + torch.cos(sun_geocentric_position["latitude"] * torch.pi / 180) + * torch.sin(true_obliquity * torch.pi / 180) + * torch.sin(apparent_sun_longitude * torch.pi / 180) ) - sun_geocentric_declination = np.arcsin(argument) * 180 / np.pi + sun_geocentric_declination = torch.arcsin(argument) * 180 / torch.pi return sun_geocentric_declination @@ -1097,53 +1101,53 @@ def topocentric_sun_position_calculate( ) # Term u, used in the following calculations (in radians) - u = np.arctan(0.99664719 * np.tan(location["latitude"] * np.pi / 180)) + u = torch.arctan(0.99664719 * torch.tan(torch.as_tensor(location["latitude"]) * torch.pi / 180)) # Term x, used in the following calculations - x = np.cos(u) + ( - (location["altitude"] / 6378140) - * np.cos(location["latitude"] * np.pi / 180) + x = torch.cos(u) + ( + (torch.as_tensor(location["altitude"]) / 6378140) + * torch.cos(torch.as_tensor(location["latitude"]) * torch.pi / 180) ) # Term y, used in the following calculations - y = (0.99664719 * np.sin(u)) + ( - (location["altitude"] / 6378140) - * np.sin(location["latitude"] * np.pi / 180) + y = (0.99664719 * torch.sin(u)) + ( + (torch.as_tensor(location["altitude"]) / 6378140) + * torch.sin(torch.as_tensor(location["latitude"]) * torch.pi / 180) ) # Parallax in the sun rigth ascension (in radians) nominator = ( -x - * np.sin(eq_horizontal_parallax * np.pi / 180) - * np.sin(observer_local_hour * np.pi / 180) + * torch.sin(eq_horizontal_parallax * torch.pi / 180) + * torch.sin(observer_local_hour * torch.pi / 180) ) - denominator = np.cos(sun_geocentric_declination * np.pi / 180) - ( + denominator = torch.cos(sun_geocentric_declination * torch.pi / 180) - ( x - * np.sin(eq_horizontal_parallax * np.pi / 180) - * np.cos(observer_local_hour * np.pi / 180) + * torch.sin(eq_horizontal_parallax * torch.pi / 180) + * torch.cos(observer_local_hour * torch.pi / 180) ) - sun_rigth_ascension_parallax = np.arctan2(nominator, denominator) + sun_rigth_ascension_parallax = torch.arctan2(nominator, denominator) # Conversion to degrees. topocentric_sun_position = dict() topocentric_sun_position["rigth_ascension_parallax"] = ( - sun_rigth_ascension_parallax * 180 / np.pi + sun_rigth_ascension_parallax * 180 / torch.pi ) # Topocentric sun rigth ascension (in degrees) topocentric_sun_position["rigth_ascension"] = sun_rigth_ascension + ( - sun_rigth_ascension_parallax * 180 / np.pi + sun_rigth_ascension_parallax * 180 / torch.pi ) # Topocentric sun declination (in degrees) nominator = ( - np.sin(sun_geocentric_declination * np.pi / 180) - - (y * np.sin(eq_horizontal_parallax * np.pi / 180)) - ) * np.cos(sun_rigth_ascension_parallax) - denominator = np.cos(sun_geocentric_declination * np.pi / 180) - ( - y * np.sin(eq_horizontal_parallax * np.pi / 180) - ) * np.cos(observer_local_hour * np.pi / 180) + torch.sin(sun_geocentric_declination * torch.pi / 180) + - (y * torch.sin(eq_horizontal_parallax * torch.pi / 180)) + ) * torch.cos(sun_rigth_ascension_parallax) + denominator = torch.cos(sun_geocentric_declination * torch.pi / 180) - ( + y * torch.sin(eq_horizontal_parallax * torch.pi / 180) + ) * torch.cos(observer_local_hour * torch.pi / 180) topocentric_sun_position["declination"] = ( - np.arctan2(nominator, denominator) * 180 / np.pi + torch.arctan2(nominator, denominator) * 180 / torch.pi ) return topocentric_sun_position @@ -1182,18 +1186,18 @@ def sun_topocentric_zenith_angle_calculate( # Topocentric elevation, without atmospheric refraction argument = ( - np.sin(location["latitude"] * np.pi / 180) - * np.sin(topocentric_sun_position["declination"] * np.pi / 180) + torch.sin(torch.as_tensor(location["latitude"]) * torch.pi / 180) + * torch.sin(torch.as_tensor(topocentric_sun_position["declination"]) * torch.pi / 180) ) + ( - np.cos(location["latitude"] * np.pi / 180) - * np.cos(topocentric_sun_position["declination"] * np.pi / 180) - * np.cos(topocentric_local_hour * np.pi / 180) + torch.cos(torch.as_tensor(location["latitude"]) * torch.pi / 180) + * torch.cos(torch.as_tensor(topocentric_sun_position["declination"]) * torch.pi / 180) + * torch.cos(torch.as_tensor(topocentric_local_hour) * torch.pi / 180) ) - true_elevation = np.arcsin(argument) * 180 / np.pi + true_elevation = torch.arcsin(argument) * 180 / torch.pi # Atmospheric refraction correction (in degrees) argument = true_elevation + (10.3 / (true_elevation + 5.11)) - refraction_corr = 1.02 / (60 * np.tan(argument * np.pi / 180)) + refraction_corr = 1.02 / (60 * torch.tan(argument * torch.pi / 180)) # For exact pressure and temperature correction, use this, # with P the pressure in mbar amd T the temperature in Kelvins: @@ -1208,15 +1212,15 @@ def sun_topocentric_zenith_angle_calculate( # Topocentric azimuth angle. The +180 conversion is to pass from astronomer # notation (westward from south) to navigation notation (eastward from # north); - nominator = np.sin(topocentric_local_hour * np.pi / 180) + nominator = torch.sin(torch.as_tensor(topocentric_local_hour * torch.pi / 180)) denominator = ( - np.cos(topocentric_local_hour * np.pi / 180) - * np.sin(location["latitude"] * np.pi / 180) + torch.cos(torch.as_tensor(topocentric_local_hour) * torch.pi / 180) + * torch.sin(torch.as_tensor(location["latitude"]) * torch.pi / 180) ) - ( - np.tan(topocentric_sun_position["declination"] * np.pi / 180) - * np.cos(location["latitude"] * np.pi / 180) + torch.tan(torch.as_tensor(topocentric_sun_position["declination"]) * torch.pi / 180) + * torch.cos(torch.as_tensor(location["latitude"]) * torch.pi / 180) ) - sun["azimuth"] = (np.arctan2(nominator, denominator) * 180 / np.pi) + 180 + sun["azimuth"] = (torch.arctan2(nominator, denominator) * 180 / torch.pi) + 180 # Set the range to [0-360] sun["azimuth"] = set_to_range(sun["azimuth"], 0, 360) @@ -1232,8 +1236,8 @@ def set_to_range(var, min_interval, max_interval): :param max_interval: :return: """ - var = var - max_interval * np.floor(var / max_interval) + var = var - max_interval * torch.floor(var / max_interval) if var < min_interval: var = var + max_interval - return var + return var \ No newline at end of file From 8f5b16def808c579213ca26694ea0a3061dde0b4 Mon Sep 17 00:00:00 2001 From: "Mael.PHILIPPE" Date: Wed, 20 May 2026 10:29:56 +0200 Subject: [PATCH 03/20] feat/added gpu for skyview factor and solweig --- functions/SOLWEIGpython/Solweig_run.py | 30 +- functions/dailyshading.py | 2 +- functions/svf_for_voxels.py | 130 ++-- functions/svf_functions.py | 330 +++++---- functions/wallalgorithms.py | 164 +++-- preprocessor/skyviewfactor_algorithm.py | 71 +- processor/configsolweig.ini | 2 + processor/solweig_algorithm.py | 24 + .../shadowingfunction_wallheight_13.py | 1 - .../shadowingfunction_wallheight_23.py | 2 +- util/shadowingfunctions.py | 676 +++++++----------- util/umep_solweig_export_component.py | 2 + 12 files changed, 754 insertions(+), 680 deletions(-) diff --git a/functions/SOLWEIGpython/Solweig_run.py b/functions/SOLWEIGpython/Solweig_run.py index 16d0901..023a473 100644 --- a/functions/SOLWEIGpython/Solweig_run.py +++ b/functions/SOLWEIGpython/Solweig_run.py @@ -73,10 +73,7 @@ # from ...util.SEBESOLWEIGCommonFiles.Perez_v3 import Perez_v3 # from ...util.SEBESOLWEIGCommonFiles.create_patches import create_patches -device = torch.device( - "cuda" if torch.cuda.is_available() - else "cpu" -) + def solweig_run(configPath, feedback): """ @@ -84,15 +81,7 @@ def solweig_run(configPath, feedback): configPath : config file including geodata paths and settings. feedback : To communicate with qgis gui. Set to None if standalone """ - # --- Load CPU OR GPU config - choice = input("Do you want to use the GPU ? (yes/no) : ").strip().lower() - selected_device = None - if choice == "yes" and torch.cuda.is_available(): - selected_device = torch.device("cuda") - print("GPU selected") - else: - selected_device = torch.device("cpu") - print("CPU selected") + # Load config file configDict = read_solweig_config(configPath) @@ -100,8 +89,8 @@ def solweig_run(configPath, feedback): # Load parameters settings for SOLWEIG with open(configDict["para_json_path"], "r") as jsn: param = json.load(jsn) + - standAlone = int(configDict["standalone"]) # reading variables from config and parameters that is not yet presented cyl = int(configDict["cyl"]) @@ -111,6 +100,19 @@ def solweig_run(configPath, feedback): elvis = 0.0 absK = param["Tmrt_params"]["Value"]["absK"] absL = param["Tmrt_params"]["Value"]["absL"] + + # --- Load on CPU or GPU config + device = torch.device("cpu") + print("passe") + + if configDict["calculation_mode"] == "gpu" and torch.cuda.is_available(): + device = torch.device("cuda") + print("using gpu") + + else: + print("using cpu") + + standAlone = int(configDict["standalone"]) # Load DSM if standAlone == 1: diff --git a/functions/dailyshading.py b/functions/dailyshading.py index ec20cd9..50f1c18 100644 --- a/functions/dailyshading.py +++ b/functions/dailyshading.py @@ -192,7 +192,7 @@ def dailyshading( else: if usevegdem == 0: sh = shadow.shadowingfunctionglobalradiation( - dsm, azi[i], alt[i], scale, feedback, 0 + dsm, azi[i], alt[i], scale, feedback, 0, ) # shtot = shtot + sh else: diff --git a/functions/svf_for_voxels.py b/functions/svf_for_voxels.py index c069545..3b7f9cf 100644 --- a/functions/svf_for_voxels.py +++ b/functions/svf_for_voxels.py @@ -1,33 +1,43 @@ -import numpy as np - +import torch try: from sklearn.cluster import KMeans except: + print("passe") pass -import time + + +def _to_tensor(x, device, dtype=torch.float32): + if isinstance(x, torch.Tensor): + return x.to(device) + if x is None: + return None + return torch.tensor(x, dtype=dtype, device=device) from ..functions import svf_functions as svf from ..functions import wallalgorithms as wa -def wallscheme_prepare(dsm, scale, pixel_resolution, feedback): +def wallscheme_prepare( + dsm, scale, pixel_resolution, feedback, device=torch.device("cpu") +): + dsm = _to_tensor(dsm, device) total = 100.0 / (int(dsm.shape[0] * dsm.shape[1])) walls = wa.findwalls_sp( - dsm, 2, np.array([[1, 1, 1], [1, 0, 1], [1, 1, 1]]) + dsm, 2, torch.tensor([[1, 1, 1], [1, 0, 1], [1, 1, 1]], device=dsm.device) ) - walls_copy = np.copy(walls) + walls_copy = torch.clone(walls) aspect = wa.filter1Goodwin_as_aspect_v3( walls_copy, scale, dsm, feedback, 100 ) # Copy to keep exact height values - walls_exact = walls.copy() + walls_exact = walls.clone() # Rounding wall heights (ceil or round?) - # walls_round = np.round(walls).astype(int) - walls_round = np.ceil(walls).astype(int) + # walls_round = torch.round(walls).astype(int) + walls_round = torch.ceil(walls).int() # create wall IDs - wall_rows, wall_cols = np.where(walls_round > 0) + wall_rows, wall_cols = torch.where(walls_round > 0) voxel_height = list() wall2d_id = list() wall_height = list() @@ -35,8 +45,8 @@ def wallscheme_prepare(dsm, scale, pixel_resolution, feedback): y_position = list() x_position = list() index = 1 - uniqueWallIDs = np.zeros((dsm.shape[0], dsm.shape[1])) - for i in np.arange(wall_rows.shape[0]): + uniqueWallIDs = torch.zeros((dsm.shape[0], dsm.shape[1]), device=device) + for i in torch.arange(wall_rows.shape[0]): uniqueWallIDs[wall_rows[i], wall_cols[i]] = index number_of_voxels = int( walls_round[wall_rows[i], wall_cols[i]] / pixel_resolution @@ -71,18 +81,22 @@ def wallscheme_prepare(dsm, scale, pixel_resolution, feedback): # saveraster(dataSet, output_uniquewallid, uniqueWallIDs) # Unique IDs for each voxel - voxelId_list = np.arange(1, wall2d_id.__len__() + 1) + voxelId_list = torch.arange(1, wall2d_id.__len__() + 1, device=device) # Table with unique voxel ID, height of voxel, total height of wall, unique ID of wall (based on 2D-location in raster) and y and x coordinates - voxelTable = np.column_stack( + voxelTable = torch.column_stack( + [ - voxelId_list, - voxel_height, - wall_height, - wall_height_exact, - wall2d_id, - y_position, - x_position, + t if isinstance(t, torch.Tensor) else torch.tensor(t, device=device) + for t in [ + voxelId_list, + voxel_height, + wall_height, + wall_height_exact, + wall2d_id, + y_position, + x_position, + ] ] ) @@ -115,9 +129,21 @@ def svf_for_voxels( svfaveg_array, svf_height_array, feedback, + device=torch.device("cpu"), ): """This function calculates sky view factor at all voxel levels""" + dsm = _to_tensor(dsm, device) + dem = _to_tensor(dem, device) + vegdsm = _to_tensor(vegdsm, device) + vegdsm2 = _to_tensor(vegdsm2, device) + voxelTable = _to_tensor(voxelTable, device) + svf_array = _to_tensor(svf_array, device) + svfbu_array = _to_tensor(svfbu_array, device) + svfveg_array = _to_tensor(svfveg_array, device) + svfaveg_array = _to_tensor(svfaveg_array, device) + svf_height_array = _to_tensor(svf_height_array, device) + # Calculate where there are buildings and not. Used to elevate dem. ground = dsm - dem # Ground == 1 = ground @@ -126,12 +152,12 @@ def svf_for_voxels( ground[ground >= 2] = 0.0 # Find maximum wall height, used to estimate how many iterations of svf_calc that are required - maxWallHeight = np.max(voxelTable[:, 2]) - svf_height + maxWallHeight = torch.max(voxelTable[:, 2]) - svf_height # Counter to feedback current iteration counter = 1 # How many iterations are required to calculate svf for all voxels - loop_range = np.arange(svf_height, maxWallHeight + svf_height, svf_height) + loop_range = torch.arange(svf_height, maxWallHeight + svf_height, svf_height) # Loop for svf calculations of all voxel heights for i in loop_range: @@ -172,6 +198,7 @@ def svf_for_voxels( wallScheme, dem, feedback, + device=device, ) svfbu = ret_["svf"] @@ -186,7 +213,7 @@ def svf_for_voxels( svftotal = svfbu - (1 - svfveg) * (1 - trans) # Get svf for each voxel - voxel_y = np.where(voxelTable[:, 1] == i + svf_height) # +svf_height) + voxel_y = torch.where(voxelTable[:, 1] == i + svf_height) # +svf_height) for temp_y in voxel_y[0]: svf_array[temp_y] = svftotal[ int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) @@ -208,11 +235,11 @@ def svf_for_voxels( counter += 1 - svf_05 = svf_array.copy() + svf_05 = svf_array.clone() svf_05[svf_05 > 0.5] = 0.5 # Add svf arrays as volumns to voxelTable - voxelTable = np.column_stack( + voxelTable = torch.column_stack( [ voxelTable, svf_height_array, @@ -246,7 +273,20 @@ def svf_kmeans( svfaveg_array, svf_height_array, feedback, + device=torch.device("cpu"), ): + dsm = _to_tensor(dsm, device) + dem = _to_tensor(dem, device) + vegdsm = _to_tensor(vegdsm, device) + vegdsm2 = _to_tensor(vegdsm2, device) + wallHeights = _to_tensor(wallHeights, device) + voxelTable = _to_tensor(voxelTable, device) + svf_array = _to_tensor(svf_array, device) + svfbu_array = _to_tensor(svfbu_array, device) + svfveg_array = _to_tensor(svfveg_array, device) + svfaveg_array = _to_tensor(svfaveg_array, device) + svf_height_array = _to_tensor(svf_height_array, device) + # Calculate where there are buildings and not. Used to elevate dem. ground = dsm - dem @@ -259,33 +299,33 @@ def svf_kmeans( # Reshape data for clustering # data_reshaped = building_heights.reshape(-1, 1) - data_reshaped = wallHeights.reshape(-1, 1) + data_reshaped = wallHeights.detach().cpu().numpy().reshape(-1, 1) # Apply K-means clustering # clusters = 3 # Number of clusters kmeans = KMeans(n_clusters=clusters, random_state=0) labels = kmeans.fit_predict(data_reshaped) - # Reshape the labels back to the original data shape - kmeans_clusters = labels.reshape(dsm.shape[0], dsm.shape[1]) + # Reshape the labels back into a torch tensor on the selected device + kmeans_clusters = torch.from_numpy(labels.reshape(dsm.shape[0], dsm.shape[1])).to(device) # Remove cluster representing ground areas, i.e. where dsm - dem = 0 - cluster_range = np.arange(clusters) - # cluster_range = cluster_range[cluster_range != np.unique(kmeans_clusters[ground == 1])] + cluster_range = torch.arange(clusters, device=device) + # cluster_range = cluster_range[cluster_range != torch.unique(kmeans_clusters[ground == 1])] # Array to store mean heights of clusters - cluster_heights = np.zeros((cluster_range.shape[0])) + cluster_heights = torch.zeros((cluster_range.shape[0]), device=device) counter = 0 for i in cluster_range: - # cluster_heights[counter] = np.round(building_heights[kmeans_clusters == i].mean()) + # cluster_heights[counter] = torch.round(building_heights[kmeans_clusters == i].mean()) cluster_heights[counter] = ( - np.round(wallHeights[kmeans_clusters == i].mean()) - svf_height + torch.round(wallHeights[kmeans_clusters == i].mean()) - svf_height ) # Remove svf_height which is the voxel size to be below the top of the wall counter += 1 # Unique heights based on mean height of clusters, sorted from min to max - cluster_heights = np.unique(cluster_heights) + cluster_heights = torch.unique(cluster_heights) cluster_heights = cluster_heights[cluster_heights > 0] # Counter to feedback current iteration @@ -335,7 +375,9 @@ def svf_kmeans( wallScheme, dem, feedback, + device=device, ) + svfbu = ret_["svf"] if usevegdem == 0: @@ -349,7 +391,7 @@ def svf_kmeans( svftotal = svfbu - (1 - svfveg) * (1 - trans) # Get svf for each voxel - voxel_y = np.where(voxelTable[:, 1] == i + svf_height) # +svf_height) + voxel_y = torch.where(voxelTable[:, 1] == i + svf_height) # +svf_height) for temp_y in voxel_y[0]: svf_array[temp_y] = svftotal[ int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) @@ -369,7 +411,7 @@ def svf_kmeans( temp_data = voxelTable[ voxelTable[:, 2] > i, : ] # Get all walls that are taller than the mean of the lowest cluster - unique_walls = np.unique( + unique_walls = torch.unique( temp_data[:, 4] ) # Get their unique wall ids for slicing for ( @@ -380,7 +422,7 @@ def svf_kmeans( temp_wall = temp_data[temp_data[:, 4] == unique_wall, :][ :, 1 ].max() # Max height of highest voxel in unique_wall - temp_y = np.where( + temp_y = torch.where( (voxelTable[:, 4] == unique_wall) & (voxelTable[:, 1] == temp_wall) )[ @@ -414,7 +456,7 @@ def svf_kmeans( & (voxelTable[:, 2] < i), :, ] - unique_walls = np.unique( + unique_walls = torch.unique( temp_data[:, 4] ) # Get their unique wall ids for slicing for ( @@ -425,7 +467,7 @@ def svf_kmeans( temp_wall = temp_data[temp_data[:, 4] == unique_wall, :][ :, 1 ].max() # Max height of highest voxel in unique_wall - temp_y = np.where( + temp_y = torch.where( (voxelTable[:, 4] == unique_wall) & (voxelTable[:, 1] == temp_wall) )[ @@ -459,11 +501,11 @@ def svf_kmeans( counter += 1 - svf_05 = svf_array.copy() + svf_05 = svf_array.clone() svf_05[svf_05 > 0.5] = 0.5 # Add svf arrays as volumns to voxelTable - voxelTable = np.column_stack( + voxelTable = torch.column_stack( [ voxelTable, svf_height_array, @@ -480,7 +522,7 @@ def svf_kmeans( def interpolate_svf(voxelTable, cluster_heights, kmeans): - unique_wall_pixels = np.unique(voxelTable[:, 4]) + unique_wall_pixels = torch.unique(voxelTable[:, 4]) unique_wall_pixels = unique_wall_pixels[unique_wall_pixels != 0] for unique_wall in unique_wall_pixels: temp_data = voxelTable[ @@ -496,7 +538,7 @@ def interpolate_svf(voxelTable, cluster_heights, kmeans): new_svf = temp_data[temp_data[:, -4] != 0, -4] new_svf[new_svf == 0] = new_svf[new_svf != 0] elif temp_heights.size > 1: # Interpolate - new_svf = np.interp( + new_svf = torch.interp( temp_data[:, 1], temp_heights, temp_svf ) # SVF for all voxels from interpolated values of calculated SVF at different heights (depend on svf_height) diff --git a/functions/svf_functions.py b/functions/svf_functions.py index 5ce4016..4d5e0bf 100644 --- a/functions/svf_functions.py +++ b/functions/svf_functions.py @@ -1,7 +1,20 @@ -import numpy as np +import torch from ..util import shadowingfunctions as shadow from ..util.SEBESOLWEIGCommonFiles.create_patches import create_patches + +def _to_tensor(x, device, dtype=torch.float32): + if isinstance(x, torch.Tensor): + return x.to(device) + if x is None: + return None + return torch.tensor(x, dtype=dtype, device=device) + + +def _sync_if_cuda(device): + if device is not None and device.type == "cuda": + torch.cuda.synchronize() + # from ..functions.wallalgorithms import findwalls from ..functions import wallalgorithms as wa from ..functions import svf_for_voxels as svfv @@ -18,43 +31,43 @@ from osgeo import gdal, osr -def annulus_weight(altitude, aziinterval): - n = 90.0 - steprad = (360.0 / aziinterval) * (np.pi / 180.0) +def annulus_weight(altitude, aziinterval, device): + n = torch.tensor(90.0, device=device) + steprad = (360.0 / aziinterval) * (torch.pi / 180.0) annulus = 91.0 - altitude w = ( - (1.0 / (2.0 * np.pi)) - * np.sin(np.pi / (2.0 * n)) - * np.sin((np.pi * (2.0 * annulus - 1.0)) / (2.0 * n)) + (1.0 / (2.0 * torch.pi)) + * torch.sin(torch.pi / (2.0 * n)) + * torch.sin((torch.pi * (2.0 * annulus - 1.0)) / (2.0 * n)) ) weight = steprad * w return weight -def svf_angles_100121(): - azi1 = np.arange(1.0, 360.0, 360.0 / 16.0) # %22.5 - azi2 = np.arange(12.0, 360.0, 360.0 / 16.0) # %22.5 - azi3 = np.arange(5.0, 360.0, 360.0 / 32.0) # %11.25 - azi4 = np.arange(2.0, 360.0, 360.0 / 32.0) # %11.25 - azi5 = np.arange(4.0, 360.0, 360.0 / 40.0) # %9 - azi6 = np.arange(7.0, 360.0, 360.0 / 48.0) # %7.50 - azi7 = np.arange(6.0, 360.0, 360.0 / 48.0) # %7.50 - azi8 = np.arange(1.0, 360.0, 360.0 / 48.0) # %7.50 - azi9 = np.arange(4.0, 359.0, 360.0 / 52.0) # %6.9231 - azi10 = np.arange(5.0, 360.0, 360.0 / 52.0) # %6.9231 - azi11 = np.arange(1.0, 360.0, 360.0 / 48.0) # %7.50 - azi12 = np.arange(0.0, 359.0, 360.0 / 44.0) # %8.1818 - azi13 = np.arange(3.0, 360.0, 360.0 / 44.0) # %8.1818 - azi14 = np.arange(2.0, 360.0, 360.0 / 40.0) # %9 - azi15 = np.arange(7.0, 360.0, 360.0 / 32.0) # %10 - azi16 = np.arange(3.0, 360.0, 360.0 / 24.0) # %11.25 - azi17 = np.arange(10.0, 360.0, 360.0 / 16.0) # %15 - azi18 = np.arange(19.0, 360.0, 360.0 / 12.0) # %22.5 - azi19 = np.arange(17.0, 360.0, 360.0 / 8.0) # %45 +def svf_angles_100121(device): + azi1 = torch.arange(1.0, 360.0, 360.0 / 16.0) # %22.5 + azi2 = torch.arange(12.0, 360.0, 360.0 / 16.0) # %22.5 + azi3 = torch.arange(5.0, 360.0, 360.0 / 32.0) # %11.25 + azi4 = torch.arange(2.0, 360.0, 360.0 / 32.0) # %11.25 + azi5 = torch.arange(4.0, 360.0, 360.0 / 40.0) # %9 + azi6 = torch.arange(7.0, 360.0, 360.0 / 48.0) # %7.50 + azi7 = torch.arange(6.0, 360.0, 360.0 / 48.0) # %7.50 + azi8 = torch.arange(1.0, 360.0, 360.0 / 48.0) # %7.50 + azi9 = torch.arange(4.0, 359.0, 360.0 / 52.0) # %6.9231 + azi10 = torch.arange(5.0, 360.0, 360.0 / 52.0) # %6.9231 + azi11 = torch.arange(1.0, 360.0, 360.0 / 48.0) # %7.50 + azi12 = torch.arange(0.0, 359.0, 360.0 / 44.0) # %8.1818 + azi13 = torch.arange(3.0, 360.0, 360.0 / 44.0) # %8.1818 + azi14 = torch.arange(2.0, 360.0, 360.0 / 40.0) # %9 + azi15 = torch.arange(7.0, 360.0, 360.0 / 32.0) # %10 + azi16 = torch.arange(3.0, 360.0, 360.0 / 24.0) # %11.25 + azi17 = torch.arange(10.0, 360.0, 360.0 / 16.0) # %15 + azi18 = torch.arange(19.0, 360.0, 360.0 / 12.0) # %22.5 + azi19 = torch.arange(17.0, 360.0, 360.0 / 8.0) # %45 azi20 = 0.0 # %360 - iazimuth = np.array( - np.hstack( + iazimuth = torch.tensor( + torch.hstack( ( azi1, azi2, @@ -77,10 +90,10 @@ def svf_angles_100121(): azi19, azi20, ) - ) + ), device=device ) - aziinterval = np.array( - np.hstack( + aziinterval = torch.tensor( + torch.hstack( ( 16.0, 16.0, @@ -103,7 +116,7 @@ def svf_angles_100121(): 8.0, 1.0, ) - ) + ), device=device ) angleresult = {"iazimuth": iazimuth, "aziinterval": aziinterval} @@ -120,29 +133,37 @@ def svfForProcessing153( wallScheme, demlayer, feedback, + device=torch.device("cpu"), ): + if device is None: + device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") + dsm = _to_tensor(dsm, device) + vegdem = _to_tensor(vegdem, device) + vegdem2 = _to_tensor(vegdem2, device) + demlayer = _to_tensor(demlayer, device) + rows = dsm.shape[0] cols = dsm.shape[1] - svf = np.zeros([rows, cols]) - svfE = np.zeros([rows, cols]) - svfS = np.zeros([rows, cols]) - svfW = np.zeros([rows, cols]) - svfN = np.zeros([rows, cols]) - svfveg = np.zeros((rows, cols)) - svfEveg = np.zeros((rows, cols)) - svfSveg = np.zeros((rows, cols)) - svfWveg = np.zeros((rows, cols)) - svfNveg = np.zeros((rows, cols)) - svfaveg = np.zeros((rows, cols)) - svfEaveg = np.zeros((rows, cols)) - svfSaveg = np.zeros((rows, cols)) - svfWaveg = np.zeros((rows, cols)) - svfNaveg = np.zeros((rows, cols)) + svf = torch.zeros([rows, cols], device=device) + svfE = torch.zeros([rows, cols], device=device) + svfS = torch.zeros([rows, cols], device=device) + svfW = torch.zeros([rows, cols], device=device) + svfN = torch.zeros([rows, cols], device=device) + svfveg = torch.zeros((rows, cols), device=device) + svfEveg = torch.zeros((rows, cols), device=device) + svfSveg = torch.zeros((rows, cols), device=device) + svfWveg = torch.zeros((rows, cols), device=device) + svfNveg = torch.zeros((rows, cols), device=device) + svfaveg = torch.zeros((rows, cols), device=device) + svfEaveg = torch.zeros((rows, cols), device=device) + svfSaveg = torch.zeros((rows, cols), device=device) + svfWaveg = torch.zeros((rows, cols), device=device) + svfNaveg = torch.zeros((rows, cols), device=device) # % amaxvalue vegmax = vegdem.max() amaxvalue = dsm.max() - amaxvalue = np.maximum(amaxvalue, vegmax) + amaxvalue = torch.maximum(amaxvalue, vegmax) # % Elevation vegdems if buildingDSM inclused ground heights vegdem = vegdem + dsm @@ -150,7 +171,7 @@ def svfForProcessing153( vegdem2 = vegdem2 + dsm vegdem2[vegdem2 == dsm] = 0 # % Bush separation - bush = np.logical_not((vegdem2 * vegdem)) * vegdem + bush = torch.logical_not((vegdem2 * vegdem)) * vegdem # index = int(0) @@ -170,12 +191,24 @@ def svfForProcessing153( azistart, ) = create_patches(patch_option) - skyvaultaziint = np.array([360 / patches for patches in aziinterval]) - iazimuth = np.hstack(np.zeros((1, np.sum(aziinterval)))) # Nils + skyvaultalt = _to_tensor(skyvaultalt, device) + skyvaultazi = _to_tensor(skyvaultazi, device) + annulino = _to_tensor(annulino, device) + skyvaultaltint = _to_tensor(skyvaultaltint, device) + aziinterval = _to_tensor(aziinterval, device) + skyvaultaziint = _to_tensor(skyvaultaziint, device) + azistart = _to_tensor(azistart, device) + + skyvaultaziint = torch.tensor( + [360 / patches for patches in aziinterval], device=device + ) + iazimuth = torch.zeros(int(torch.sum(aziinterval).item()), device=device) - shmat = np.zeros((rows, cols, np.sum(aziinterval))) - vegshmat = np.zeros((rows, cols, np.sum(aziinterval))) - vbshvegshmat = np.zeros((rows, cols, np.sum(aziinterval))) + shmat = torch.zeros((rows, cols, int(torch.sum(aziinterval).item())), device=device) + vegshmat = torch.zeros((rows, cols, int(torch.sum(aziinterval).item())), device=device) + vbshvegshmat = torch.zeros( + (rows, cols, int(torch.sum(aziinterval).item())), device=device + ) # Preparations for wall temperature scheme if wallScheme: @@ -189,12 +222,20 @@ def svfForProcessing153( uniqueWallIDs, wall2d_id, voxel_height, - ) = svfv.wallscheme_prepare(dsm, scale, pixel_resolution, feedback) + ) = svfv.wallscheme_prepare( + dsm, scale, pixel_resolution, feedback, device=device + ) # Rasters to fill with values in loop - all_buildIDSeen = np.zeros((rows, cols, skyvaultalt.shape[0])) - all_voxelHeight = np.zeros((rows, cols, skyvaultalt.shape[0])) - all_voxelId = np.zeros((rows, cols, skyvaultalt.shape[0])) + all_buildIDSeen = torch.zeros( + (rows, cols, skyvaultalt.shape[0]), device=device + ) + all_voxelHeight = torch.zeros( + (rows, cols, skyvaultalt.shape[0]), device=device + ) + all_voxelId = torch.zeros( + (rows, cols, skyvaultalt.shape[0]), device=device + ) else: voxelTable = 0 allbuildIDSeen = 0 @@ -204,20 +245,20 @@ def svfForProcessing153( index = 0 for j in range(0, skyvaultaltint.shape[0]): - for k in range(0, int(360 / skyvaultaziint[j])): + for k in range(0, int(360 / skyvaultaziint[j].item())): iazimuth[index] = k * skyvaultaziint[j] + azistart[j] if iazimuth[index] > 360.0: iazimuth[index] = iazimuth[index] - 360.0 index = index + 1 - aziintervalaniso = np.ceil(aziinterval / 2.0) - index = int(0) + aziintervalaniso = torch.ceil(aziinterval / 2.0) + index = 0 for i in range(0, skyvaultaltint.shape[0]): - for j in np.arange(0, (aziinterval[int(i)])): + for j in range(0, int(aziinterval[int(i)].item())): if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") break - altitude = skyvaultaltint[int(i)] - azimuth = iazimuth[int(index)] + altitude = torch.tensor(float(skyvaultaltint[int(i)].item()), device=device) + azimuth = torch.tensor(float(iazimuth[int(index)].item()), device=device) # Casting shadow if wallScheme: @@ -241,7 +282,7 @@ def svfForProcessing153( amaxvalue, bush, walls, - aspect * np.pi / 180, + aspect * torch.pi / 180, ) vegshmat[:, :, index] = vegsh vbshvegshmat[:, :, index] = vbshvegsh @@ -253,12 +294,14 @@ def svfForProcessing153( altitude, scale, walls, - aspect * np.pi / 180.0, + aspect * torch.pi / 180.0, ) ) - vegsh = np.ones((sh.shape[0], sh.shape[1])).astype(float) - vbshvegsh = np.ones((sh.shape[0], sh.shape[1])).astype( - float + vegsh = torch.ones( + (sh.shape[0], sh.shape[1]), device=device, dtype=torch.float32 + ) + vbshvegsh = torch.ones( + (sh.shape[0], sh.shape[1]), device=device, dtype=torch.float32 ) vegshmat[:, :, index] = vegsh vbshvegshmat[:, :, index] = vbshvegsh @@ -275,17 +318,20 @@ def svfForProcessing153( bush, feedback, 1, + device, ) - vegsh = shadowresult["vegsh"] - vbshvegsh = shadowresult["vbshvegsh"] + + vegsh = torch.tensor(shadowresult["vegsh"], device=device) + vbshvegsh = torch.tensor(shadowresult["vbshvegsh"], device=device) vegshmat[:, :, index] = vegsh vbshvegshmat[:, :, index] = vbshvegsh - sh = shadowresult["sh"] + sh = torch.tensor(shadowresult["sh"], device=device) else: sh = shadow.shadowingfunctionglobalradiation( - dsm, azimuth, altitude, scale, feedback, 1 + dsm, azimuth, altitude, scale, feedback, 1, device ) + shmat[:, :, index] = sh # Wall temperature scheme, i.e. finding out which voxel is seen from each pixel, where direction is patch azimuth and altitude @@ -308,15 +354,18 @@ def svfForProcessing153( facesh, wall_dict, sh, + device, ) # Calculate svfs - for k in np.arange( - annulino[int(i)] + 1, (annulino[int(i + 1.0)]) + 1 + for k in range( + int(annulino[int(i)].item()) + 1, + int(annulino[int(i + 1.0)].item()) + 1, ): - weight = annulus_weight(k, aziinterval[i]) * sh + + weight = annulus_weight(k, aziinterval[i], device) * sh svf = svf + weight - weight = annulus_weight(k, aziintervalaniso[i]) * sh + weight = annulus_weight(k, aziintervalaniso[i], device) * sh if (azimuth >= 0) and (azimuth < 180): svfE = svfE + weight if (azimuth >= 90) and (azimuth < 270): @@ -327,14 +376,14 @@ def svfForProcessing153( svfN = svfN + weight if usevegdem == 1: - for k in np.arange( + for k in torch.arange( annulino[int(i)] + 1, (annulino[int(i + 1.0)]) + 1 ): # % changed to include 90 - weight = annulus_weight(k, aziinterval[i]) + weight = annulus_weight(k, aziinterval[i], device) svfveg = svfveg + weight * vegsh svfaveg = svfaveg + weight * vbshvegsh - weight = annulus_weight(k, aziintervalaniso[i]) + weight = annulus_weight(k, aziintervalaniso[i], device) if (azimuth >= 0) and (azimuth < 180): svfEveg = svfEveg + weight * vegsh svfEaveg = svfEaveg + weight * vbshvegsh @@ -349,7 +398,7 @@ def svfForProcessing153( svfNaveg = svfNaveg + weight * vbshvegsh index += 1 - feedback.setProgress(int(index * (100.0 / np.sum(aziinterval)))) + feedback.setProgress(int(index * (100.0 / torch.sum(aziinterval)))) svfS = svfS + 3.0459e-004 svfW = svfW + 3.0459e-004 @@ -362,7 +411,7 @@ def svfForProcessing153( svfN[(svfN > 1.0)] = 1.0 if usevegdem == 1: - last = np.zeros((rows, cols)) + last = torch.zeros((rows, cols), device=device) last[(vegdem2 == 0.0)] = 3.0459e-004 svfSveg = svfSveg + last svfWveg = svfWveg + last @@ -403,35 +452,50 @@ def svfForProcessing153( "voxelTable": voxelTable, "walls": walls, } + # , # 'vbshvegshmat': vbshvegshmat, 'wallshmat': wallshmat, 'wallsunmat': wallsunmat, # 'wallshvemat': wallshvemat, 'facesunmat': facesunmat} return svfresult -def svfForProcessing655(dsm, vegdem, vegdem2, scale, usevegdem, feedback): +def svfForProcessing655( + dsm, + vegdem, + vegdem2, + scale, + usevegdem, + feedback, + device=torch.device("cpu"), +): + if device is None: + device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") + dsm = _to_tensor(dsm, device) + vegdem = _to_tensor(vegdem, device) + vegdem2 = _to_tensor(vegdem2, device) + rows = dsm.shape[0] cols = dsm.shape[1] - svf = np.zeros([rows, cols]) - svfE = np.zeros([rows, cols]) - svfS = np.zeros([rows, cols]) - svfW = np.zeros([rows, cols]) - svfN = np.zeros([rows, cols]) - svfveg = np.zeros((rows, cols)) - svfEveg = np.zeros((rows, cols)) - svfSveg = np.zeros((rows, cols)) - svfWveg = np.zeros((rows, cols)) - svfNveg = np.zeros((rows, cols)) - svfaveg = np.zeros((rows, cols)) - svfEaveg = np.zeros((rows, cols)) - svfSaveg = np.zeros((rows, cols)) - svfWaveg = np.zeros((rows, cols)) - svfNaveg = np.zeros((rows, cols)) + svf = torch.zeros([rows, cols], device=device) + svfE = torch.zeros([rows, cols], device=device) + svfS = torch.zeros([rows, cols], device=device) + svfW = torch.zeros([rows, cols], device=device) + svfN = torch.zeros([rows, cols], device=device) + svfveg = torch.zeros((rows, cols), device=device) + svfEveg = torch.zeros((rows, cols), device=device) + svfSveg = torch.zeros((rows, cols), device=device) + svfWveg = torch.zeros((rows, cols), device=device) + svfNveg = torch.zeros((rows, cols), device=device) + svfaveg = torch.zeros((rows, cols), device=device) + svfEaveg = torch.zeros((rows, cols), device=device) + svfSaveg = torch.zeros((rows, cols), device=device) + svfWaveg = torch.zeros((rows, cols), device=device) + svfNaveg = torch.zeros((rows, cols), device=device) # % amaxvalue vegmax = vegdem.max() amaxvalue = dsm.max() - amaxvalue = np.maximum(amaxvalue, vegmax) + amaxvalue = torch.maximum(amaxvalue, vegmax) # % Elevation vegdems if buildingDSM inclused ground heights vegdem = vegdem + dsm @@ -439,31 +503,32 @@ def svfForProcessing655(dsm, vegdem, vegdem2, scale, usevegdem, feedback): vegdem2 = vegdem2 + dsm vegdem2[vegdem2 == dsm] = 0 # % Bush separation - bush = np.logical_not((vegdem2 * vegdem)) * vegdem - - # shmat = np.zeros((rows, cols, 145)) - # vegshmat = np.zeros((rows, cols, 145)) + bush = torch.logical_not((vegdem2 * vegdem)) * vegdem - noa = 19.0 + noa = torch.tensor(19.0, device=device) # % No. of anglesteps minus 1 step = 89.0 / noa - iangle = np.array(np.hstack((np.arange(step / 2.0, 89.0, step), 90.0))) - annulino = np.array( - np.hstack((np.round(np.arange(0.0, 89.0, step)), 90.0)) + iangle = torch.tensor( + torch.hstack((torch.arange(step / 2.0, 89.0, step), 90.0)), + device=device, ) - angleresult = svf_angles_100121() - aziinterval = angleresult["aziinterval"] - iazimuth = angleresult["iazimuth"] - aziintervalaniso = np.ceil((aziinterval / 2.0)) - index = 1.0 - - for i in np.arange(0, iangle.shape[0] - 1): - for j in np.arange(0, (aziinterval[int(i)])): + annulino = torch.tensor( + torch.hstack((torch.round(torch.arange(0.0, 89.0, step)), 90.0)), + device=device, + ) + angleresult = svf_angles_100121(device) + aziinterval = angleresult["aziinterval"].to(device) + iazimuth = angleresult["iazimuth"].to(device) + aziintervalaniso = torch.ceil((aziinterval / 2.0)) + index = 1 + + for i in range(0, iangle.shape[0] - 1): + for j in range(0, int(aziinterval[int(i)].item())): if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") break - altitude = iangle[int(i)] - azimuth = iazimuth[int(index) - 1] + altitude = float(iangle[int(i)].item()) + azimuth = float(iazimuth[int(index) - 1].item()) # Casting shadow if usevegdem == 1: @@ -478,22 +543,26 @@ def svfForProcessing655(dsm, vegdem, vegdem2, scale, usevegdem, feedback): bush, feedback, 1, + device, ) - vegsh = shadowresult["vegsh"] - vbshvegsh = shadowresult["vbshvegsh"] - sh = shadowresult["sh"] + + vegsh = torch.tensor(shadowresult["vegsh"], device=device) + vbshvegsh = torch.tensor(shadowresult["vbshvegsh"], device=device) + sh = torch.tensor(shadowresult["sh"], device=device) else: sh = shadow.shadowingfunctionglobalradiation( - dsm, azimuth, altitude, scale, feedback, 1 + dsm, azimuth, altitude, scale, feedback, 1, device ) + # Calculate svfs - for k in np.arange( - annulino[int(i)] + 1, (annulino[int(i + 1.0)]) + 1 + for k in range( + int(annulino[int(i)].item()) + 1, + int(annulino[int(i + 1.0)].item()) + 1, ): - weight = annulus_weight(k, aziinterval[i]) * sh + weight = annulus_weight(k, aziinterval[i], device) * sh svf = svf + weight - weight = annulus_weight(k, aziintervalaniso[i]) * sh + weight = annulus_weight(k, aziintervalaniso[i], device) * sh if (azimuth >= 0) and (azimuth < 180): svfE = svfE + weight if (azimuth >= 90) and (azimuth < 270): @@ -504,14 +573,14 @@ def svfForProcessing655(dsm, vegdem, vegdem2, scale, usevegdem, feedback): svfN = svfN + weight if usevegdem == 1: - for k in np.arange( + for k in torch.arange( annulino[int(i)] + 1, (annulino[int(i + 1.0)]) + 1 ): # % changed to include 90 - weight = annulus_weight(k, aziinterval[i]) + weight = annulus_weight(k, aziinterval[i], device) svfveg = svfveg + weight * vegsh svfaveg = svfaveg + weight * vbshvegsh - weight = annulus_weight(k, aziintervalaniso[i]) + weight = annulus_weight(k, aziintervalaniso[i], device) if (azimuth >= 0) and (azimuth < 180): svfEveg = svfEveg + weight * vegsh svfEaveg = svfEaveg + weight * vbshvegsh @@ -539,7 +608,7 @@ def svfForProcessing655(dsm, vegdem, vegdem2, scale, usevegdem, feedback): svfN[(svfN > 1.0)] = 1.0 if usevegdem == 1: - last = np.zeros((rows, cols)) + last = torch.zeros((rows, cols), device=device) last[(vegdem2 == 0.0)] = 3.0459e-004 svfSveg = svfSveg + last svfWveg = svfWveg + last @@ -574,5 +643,4 @@ def svfForProcessing655(dsm, vegdem, vegdem2, scale, usevegdem, feedback): "svfWaveg": svfWaveg, "svfNaveg": svfNaveg, } - return svfresult diff --git a/functions/wallalgorithms.py b/functions/wallalgorithms.py index a37e12a..d393ebf 100644 --- a/functions/wallalgorithms.py +++ b/functions/wallalgorithms.py @@ -5,45 +5,81 @@ import math import numpy as np +import torch +import torch.nn.functional as F + # import scipy.misc as sc import scipy.ndimage as sc from scipy.ndimage import maximum_filter -def findwalls_sp(arr_dsm, walllimit, footprint=False): - # This function identifies walls based on a DSM and a wall height limit. - # arr_dsm = DSM - # walllimit = wall height limit - # footprint = footprint for maximum filter, default = np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) - - # Get the shape of the input array - col, row = arr_dsm.shape - walls = np.zeros((col, row)) - - # Create a padded version of the array - padded_a = np.pad(arr_dsm, pad_width=1, mode="edge") - - # Default footprint for cardinal points - if footprint is False: - footprint = np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) - - # Use maximum_filter with the custom footprint - max_neighbors = maximum_filter( - padded_a, footprint=footprint, mode="constant", cval=0 - ) +def findwalls_sp(arr_dsm, walllimit, footprint=None): + """ + This function identifies walls based on a DSM and a wall height limit. + + Parameters: + arr_dsm (Tensor or array-like): Digital Surface Model + walllimit (float): Wall height limit + footprint (Tensor, optional): Structuring element for the maximum filter. + Defaults to a diamond shape (cardinal neighbors). + """ + # Ensure input is a PyTorch tensor and preserve its device (CPU or GPU) + if isinstance(arr_dsm, torch.Tensor): + dsm_tensor = arr_dsm + else: + dsm_tensor = torch.tensor(arr_dsm) + + # 1. Padding: 'edge' mode in NumPy becomes 'replicate' in PyTorch + # PyTorch requires at least a 3D tensor for 'replicate', so we unsqueeze(0) + padded_a = dsm_tensor.unsqueeze(0) + padded_a = F.pad(padded_a, pad=(1, 1, 1, 1), mode="replicate") + padded_a = padded_a.squeeze(0) + + # 2. Maximum Filter using Max Pooling + # Default footprint is a 3x3 diamond shape (cardinal points) + if footprint is None or footprint is False: + # For max_pool2d, we simulate the footprint by looking at a 3x3 window. + # To strictly replicate a diamond footprint in pure PyTorch, we can use + # a 2D morphological dilation with a custom kernel: + kernel = torch.tensor([[0., 1., 0.], + [1., 1., 1.], # Including center for max filter + [0., 1., 0.]], device=dsm_tensor.device) + + # Reshape padded_a to (Batch=1, Channel=1, H, W) for 2D convolutions + x = padded_a.unsqueeze(0).unsqueeze(0) + + # We use unfolds or a specialized max_pool, but since your footprint has 0s, + # the cleanest PyTorch way is to mask the 3x3 windows: + windows = F.unfold(x, kernel_size=3, padding=0) # Shape: (1, 9, L) + # Multiply by kernel flattened, replacing 0s with -inf so they are ignored in max + kernel_flat = kernel.flatten().unsqueeze(1) + windows = windows + torch.where(kernel_flat == 1, 0.0, float('-inf')) + + # Take the maximum over the 9 neighbors + max_neighbors = windows.max(dim=1)[0] # Shape: (1, L) + + # Reshape back to original DSM size (col, row) + max_neighbors = max_neighbors.view(dsm_tensor.shape) + else: + # If a custom footprint is provided, you can apply a similar unfold method + x = padded_a.unsqueeze(0).unsqueeze(0) + windows = F.unfold(x, kernel_size=footprint.shape, padding=0) + footprint_flat = footprint.to(dsm_tensor.device).flatten().unsqueeze(1) + windows = windows + torch.where(footprint_flat == 1, 0.0, float('-inf')) + max_neighbors = windows.max(dim=1)[0].view(dsm_tensor.shape) - # Identify wall pixels: walls are where the max neighbors are greater than the original DSM - walls = max_neighbors[1:-1, 1:-1] - arr_dsm + # 3. Identify wall pixels + walls = max_neighbors - dsm_tensor # Apply wall height limit walls[walls < walllimit] = 0 - # Set the edges to zero - walls[0 : walls.shape[0], 0] = 0 - walls[0 : walls.shape[0], walls.shape[1] - 1] = 0 - walls[0, 0 : walls.shape[1]] = 0 - walls[walls.shape[0] - 1, 0 : walls.shape[1]] = 0 + # 4. Set the outermost boundaries to zero + walls[0, :] = 0 + walls[-1, :] = 0 + walls[:, 0] = 0 + walls[:, -1] = 0 return walls @@ -58,20 +94,20 @@ def findwalls(a, walllimit, feedback, total): col = a.shape[0] row = a.shape[1] - walls = np.zeros((col, row)) - domain = np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) + walls = torch.zeros((col, row)) + domain = torch.tensor([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) index = 0 - for i in np.arange(1, row - 1): + for i in torch.arange(1, row - 1): if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") break - for j in np.arange(1, col - 1): + for j in torch.arange(1, col - 1): dom = a[j - 1 : j + 2, i - 1 : i + 2] - walls[j, i] = np.max(dom[np.where(domain == 1)]) # new 20171006 + walls[j, i] = torch.max(dom[torch.where(domain == 1)]) # new 20171006 index = index + 1 feedback.setProgress(int(index * total)) - walls = np.copy(walls - a) # new 20171006 + walls = torch.clone(walls - a) # new 20171006 walls[(walls < walllimit)] = 0 walls[0 : walls.shape[0], 0] = 0 @@ -98,12 +134,12 @@ def filter1Goodwin_as_aspect_v3(walls_for_dir, scale, a, feedback, total): :return: dirwalls """ - walls = walls_for_dir.copy() + walls = walls_for_dir.clone().to(a.device) row = a.shape[0] col = a.shape[1] - filtersize = np.floor((scale + 0.0000000001) * 9) + filtersize = torch.floor((scale + 0.0000000001) * 9) if filtersize <= 2: filtersize = 3 else: @@ -111,20 +147,20 @@ def filter1Goodwin_as_aspect_v3(walls_for_dir, scale, a, feedback, total): if filtersize % 2 == 0: filtersize = filtersize + 1 - filthalveceil = int(np.ceil(filtersize / 2.0)) - filthalvefloor = int(np.floor(filtersize / 2.0)) + filthalveceil = int(torch.ceil(filtersize / 2.0)) + filthalvefloor = int(torch.floor(filtersize / 2.0)) - filtmatrix = np.zeros((int(filtersize), int(filtersize))) - buildfilt = np.zeros((int(filtersize), int(filtersize))) + filtmatrix = torch.zeros((int(filtersize), int(filtersize))) + buildfilt = torch.zeros((int(filtersize), int(filtersize))) filtmatrix[:, filthalveceil - 1] = 1 n = filtmatrix.shape[0] - 1 buildfilt[filthalveceil - 1, 0:filthalvefloor] = 1 buildfilt[filthalveceil - 1, filthalveceil : int(filtersize)] = 2 - y = np.zeros((row, col)) # final direction - z = np.zeros((row, col)) # temporary direction - x = np.zeros((row, col)) # building side + y = torch.zeros((row, col), device=a.device) # final direction + z = torch.zeros((row, col), device=a.device) # temporary direction + x = torch.zeros((row, col), device=a.device) # building side walls[walls > 0] = 1 for h in range( @@ -136,17 +172,33 @@ def filter1Goodwin_as_aspect_v3(walls_for_dir, scale, a, feedback, total): feedback.setProgressText("Calculation cancelled") break filtmatrix1temp = sc.rotate( - filtmatrix, h, order=1, reshape=False, mode="nearest" + filtmatrix.cpu().numpy() + if filtmatrix.device.type == "cuda" + else filtmatrix.numpy(), + h, + order=1, + reshape=False, + mode="nearest", ) # bilinear - filtmatrix1 = np.round(filtmatrix1temp) + filtmatrix1 = torch.round( + torch.from_numpy(filtmatrix1temp).to(walls.device) + ) # filtmatrix1temp = sc.imrotate(filtmatrix, h, 'bilinear') - # filtmatrix1 = np.round(filtmatrix1temp / 255.) + # filtmatrix1 = torch.round(filtmatrix1temp / 255.) # filtmatrixbuildtemp = sc.imrotate(buildfilt, h, 'nearest') filtmatrixbuildtemp = sc.rotate( - buildfilt, h, order=0, reshape=False, mode="nearest" + buildfilt.cpu().numpy() + if buildfilt.device.type == "cuda" + else buildfilt.numpy(), + h, + order=0, + reshape=False, + mode="nearest", ) # Nearest neighbor - # filtmatrixbuild = np.round(filtmatrixbuildtemp / 127.) - filtmatrixbuild = np.round(filtmatrixbuildtemp) + # filtmatrixbuild = torch.round(filtmatrixbuildtemp / 127.) + filtmatrixbuild = torch.round( + torch.from_numpy(filtmatrixbuildtemp).to(walls.device) + ) index = 270 - h if h == 150: filtmatrixbuild[:, n] = 0 @@ -181,7 +233,7 @@ def filter1Goodwin_as_aspect_v3(walls_for_dir, scale, a, feedback, total): ] if z[i, j] < wallscut.sum(): # sum(sum(wallscut)) z[i, j] = wallscut.sum() # sum(sum(wallscut)); - if np.sum(dsmcut[filtmatrixbuild == 1]) > np.sum( + if torch.sum(dsmcut[filtmatrixbuild == 1]) > torch.sum( dsmcut[filtmatrixbuild == 2] ): x[i, j] = 1 @@ -203,10 +255,10 @@ def filter1Goodwin_as_aspect_v3(walls_for_dir, scale, a, feedback, total): def cart2pol(x, y, units="deg"): - radius = np.sqrt(x**2 + y**2) - theta = np.arctan2(y, x) + radius = torch.sqrt(x**2 + y**2) + theta = torch.arctan2(y, x) if units in ["deg", "degs"]: - theta = theta * 180 / np.pi + theta = theta * 180 / torch.pi return theta, radius @@ -214,9 +266,9 @@ def get_ders(dsm, scale): # dem,_,_=read_dem_grid(dem_file) dx = 1 / scale # dx=0.5 - fy, fx = np.gradient(dsm, dx, dx) + fy, fx = torch.gradient(dsm, spacing=dx) asp, grad = cart2pol(fy, fx, "rad") - grad = np.arctan(grad) + grad = torch.arctan(grad) asp = asp * -1 - asp = asp + (asp < 0) * (np.pi * 2) + asp = asp + (asp < 0) * (torch.pi * 2) return grad, asp diff --git a/preprocessor/skyviewfactor_algorithm.py b/preprocessor/skyviewfactor_algorithm.py index 94d2f79..f657162 100644 --- a/preprocessor/skyviewfactor_algorithm.py +++ b/preprocessor/skyviewfactor_algorithm.py @@ -49,6 +49,7 @@ import os import numpy as np import inspect +import torch from pathlib import Path import zipfile import sys @@ -72,6 +73,8 @@ class ProcessingSkyViewFactorAlgorithm(QgsProcessingAlgorithm): INPUT_THEIGHT = "INPUT_THEIGHT" ANISO = "ANISO" KMEANS = "KMEANS" + USE_GPU = "USE_GPU" + CLUSTERS = "CLUSTERS" WALL_SCHEME = "WALL_SCHEME" INPUT_DEM = "INPUT_DEM" @@ -124,6 +127,18 @@ def initAlgorithm(self, config): maxValue=99.9, ) ) + + # Wall parameterization + self.addParameter( + QgsProcessingParameterBoolean( + self.USE_GPU, + self.tr( + "Use GPU" + ), + defaultValue=False, + ), + ) + self.addParameter( QgsProcessingParameterBoolean( self.ANISO, @@ -246,6 +261,7 @@ def processAlgorithm(self, parameters, context, feedback): parameters, self.INPUT_THEIGHT, context ) aniso = self.parameterAsBool(parameters, self.ANISO, context) + use_gpu = self.parameterAsBool(parameters, self.USE_GPU, context) # Wall parameterization settings demlayer = self.parameterAsRasterLayer( @@ -270,6 +286,10 @@ def processAlgorithm(self, parameters, context, feedback): if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): os.mkdir(outputDir) + + device = torch.device("cpu") + if use_gpu and torch.cuda.is_available(): + device = torch.device("cuda") provider = dsmlayer.dataProvider() filepath_dsm = str(provider.dataSourceUri()) @@ -287,7 +307,7 @@ def processAlgorithm(self, parameters, context, feedback): geotransform = gdal_dsm.GetGeoTransform() pixel_resolution = geotransform[1] - scale = 1 / pixel_resolution + scale = torch.tensor(1 / pixel_resolution, device=device) if wallScheme: # Load DEM layer if calculating exact SVFs for wall surface temperature scheme @@ -363,6 +383,8 @@ def processAlgorithm(self, parameters, context, feedback): vegdsm = np.zeros([rows, cols]) vegdsm2 = 0.0 usevegdem = 0 + + if aniso == 1: feedback.setProgressText("Calculating SVF using 153 iterations") @@ -376,12 +398,15 @@ def processAlgorithm(self, parameters, context, feedback): wallScheme, dem, feedback, + device=device, ) + else: feedback.setProgressText("Calculating SVF using 655 iterations") ret = svf.svfForProcessing655( - dsm, vegdsm, vegdsm2, scale, usevegdem, feedback + dsm, vegdsm, vegdsm2, scale, usevegdem, feedback, device=device ) + # print('Time to finish first SVF calculation = ' + str(run_time)) if wallScheme == 1: @@ -442,6 +467,7 @@ def processAlgorithm(self, parameters, context, feedback): svfaveg_array, svf_height_array, feedback, + device=device, ) # Interpolate for voxels where SVF has not been calculated @@ -472,6 +498,7 @@ def processAlgorithm(self, parameters, context, feedback): svfaveg_array, svf_height_array, feedback, + device=device, ) # Remove rows where svfbu, sfveg and svfaveg is zero @@ -505,11 +532,11 @@ def processAlgorithm(self, parameters, context, feedback): svfbuW = ret["svfW"] svfbuN = ret["svfN"] - misc.saveraster(gdal_dsm, outputDir + "/" + "svf.tif", svfbu) - misc.saveraster(gdal_dsm, outputDir + "/" + "svfE.tif", svfbuE) - misc.saveraster(gdal_dsm, outputDir + "/" + "svfS.tif", svfbuS) - misc.saveraster(gdal_dsm, outputDir + "/" + "svfW.tif", svfbuW) - misc.saveraster(gdal_dsm, outputDir + "/" + "svfN.tif", svfbuN) + misc.saveraster(gdal_dsm, outputDir + "/" + "svf.tif", svfbu.cpu().detach().numpy()) + misc.saveraster(gdal_dsm, outputDir + "/" + "svfE.tif", svfbuE.cpu().detach().numpy()) + misc.saveraster(gdal_dsm, outputDir + "/" + "svfS.tif", svfbuS.cpu().detach().numpy()) + misc.saveraster(gdal_dsm, outputDir + "/" + "svfW.tif", svfbuW.cpu().detach().numpy()) + misc.saveraster(gdal_dsm, outputDir + "/" + "svfN.tif", svfbuN.cpu().detach().numpy()) if os.path.isfile(outputDir + "/" + "svfs.zip"): os.remove(outputDir + "/" + "svfs.zip") @@ -544,34 +571,34 @@ def processAlgorithm(self, parameters, context, feedback): svfNaveg = ret["svfNaveg"] misc.saveraster( - gdal_dsm, outputDir + "/" + "svfveg.tif", svfveg + gdal_dsm, outputDir + "/" + "svfveg.tif", svfveg.cpu().detach().numpy() ) misc.saveraster( - gdal_dsm, outputDir + "/" + "svfEveg.tif", svfEveg + gdal_dsm, outputDir + "/" + "svfEveg.tif", svfEveg.cpu().detach().numpy() ) misc.saveraster( - gdal_dsm, outputDir + "/" + "svfSveg.tif", svfSveg + gdal_dsm, outputDir + "/" + "svfSveg.tif", svfSveg.cpu().detach().numpy() ) misc.saveraster( - gdal_dsm, outputDir + "/" + "svfWveg.tif", svfWveg + gdal_dsm, outputDir + "/" + "svfWveg.tif", svfWveg.cpu().detach().numpy() ) misc.saveraster( - gdal_dsm, outputDir + "/" + "svfNveg.tif", svfNveg + gdal_dsm, outputDir + "/" + "svfNveg.tif", svfNveg.cpu().detach().numpy() ) misc.saveraster( - gdal_dsm, outputDir + "/" + "svfaveg.tif", svfaveg + gdal_dsm, outputDir + "/" + "svfaveg.tif", svfaveg.cpu().detach().numpy() ) misc.saveraster( - gdal_dsm, outputDir + "/" + "svfEaveg.tif", svfEaveg + gdal_dsm, outputDir + "/" + "svfEaveg.tif", svfEaveg.cpu().detach().numpy() ) misc.saveraster( - gdal_dsm, outputDir + "/" + "svfSaveg.tif", svfSaveg + gdal_dsm, outputDir + "/" + "svfSaveg.tif", svfSaveg.cpu().detach().numpy() ) misc.saveraster( - gdal_dsm, outputDir + "/" + "svfWaveg.tif", svfWaveg + gdal_dsm, outputDir + "/" + "svfWaveg.tif", svfWaveg.cpu().detach().numpy() ) misc.saveraster( - gdal_dsm, outputDir + "/" + "svfNaveg.tif", svfNaveg + gdal_dsm, outputDir + "/" + "svfNaveg.tif", svfNaveg.cpu().detach().numpy() ) zippo = zipfile.ZipFile(outputDir + "/" + "svfs.zip", "a") @@ -601,7 +628,7 @@ def processAlgorithm(self, parameters, context, feedback): trans = transVeg / 100.0 svftotal = svfbu - (1 - svfveg) * (1 - trans) - misc.saveraster(gdal_dsm, filename, svftotal) + misc.saveraster(gdal_dsm, filename, svftotal.cpu().detach().numpy()) # Save shadow images for SOLWEIG 2019a if aniso == 1: @@ -615,9 +642,9 @@ def processAlgorithm(self, parameters, context, feedback): np.savez_compressed( outputDir + "/" + "shadowmats.npz", - shadowmat=shmat, - vegshadowmat=vegshmat, - vbshmat=vbshvegshmat, + shadowmat=shmat.cpu().detach().numpy(), + vegshadowmat=vegshmat.cpu().detach().numpy(), + vbshmat=vbshvegshmat.cpu().detach().numpy(), ) # , # vbshvegshmat=vbshvegshmat, wallshmat=wallshmat, wallsunmat=wallsunmat, # facesunmat=facesunmat, wallshvemat=wallshvemat) @@ -629,7 +656,7 @@ def processAlgorithm(self, parameters, context, feedback): np.savez_compressed( outputDir + "/" + "wallScheme.npz", voxelId=voxelId, - voxelTable=voxelTable, + voxelTable=voxelTable.cpu().detach().numpy(), ) feedback.setProgressText( diff --git a/processor/configsolweig.ini b/processor/configsolweig.ini index becf3e3..76e223e 100644 --- a/processor/configsolweig.ini +++ b/processor/configsolweig.ini @@ -86,6 +86,8 @@ outputkdiff=1 outputtreeplanter=1 wallnetcdf=0 +calculation_mode=cpu + #--------------------------------------------------------------------------------------------------------- # dates - used if an EPW-file is used #--------------------------------------------------------------------------------------------------------- diff --git a/processor/solweig_algorithm.py b/processor/solweig_algorithm.py index f1ece4f..60ad81a 100644 --- a/processor/solweig_algorithm.py +++ b/processor/solweig_algorithm.py @@ -125,6 +125,8 @@ class ProcessingSOLWEIGAlgorithm(QgsProcessingAlgorithm): POI_FILE = "POI_FILE" POI_FIELD = "POI_FIELD" CYL = "CYL" + USE_GPU = "USE_GPU" + # solweig groundmodel = "groundmodel" @@ -583,6 +585,15 @@ def initAlgorithm(self, config): shei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced ) self.addParameter(shei) + + self.addParameter( + QgsProcessingParameterBoolean( + self.USE_GPU, + self.tr("Use GPU for calculations (if not ticked, CPU will be used)"), + defaultValue=False, + optional=False, + ) + ) # OUTPUT self.addParameter( @@ -627,6 +638,7 @@ def initAlgorithm(self, config): defaultValue=False, ) ) + self.addParameter( QgsProcessingParameterBoolean( self.OUTPUT_TREEPLANTER, @@ -682,6 +694,9 @@ def processAlgorithm(self, parameters, context, feedback): walayer = self.parameterAsRasterLayer( parameters, self.INPUT_ASPECT, context ) + + + trunkr = self.parameterAsDouble( parameters, self.INPUT_THEIGHT, context ) @@ -770,6 +785,9 @@ def processAlgorithm(self, parameters, context, feedback): outputLdown = self.parameterAsBool( parameters, self.OUTPUT_LDOWN, context ) + + gpu_bool = self.parameterAsBool(parameters, self.USE_GPU, context) + outputTreeplanter = self.parameterAsBool( parameters, self.OUTPUT_TREEPLANTER, context ) @@ -787,6 +805,11 @@ def processAlgorithm(self, parameters, context, feedback): saveBuild = True outputKdiff = True # outputSstr = True + + calculation_mode = "cpu" + if gpu_bool: + calculation_mode = "gpu" + if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): @@ -1228,6 +1251,7 @@ def processAlgorithm(self, parameters, context, feedback): "outputkdiff": int(outputKdiff), "outputtreeplanter": int(outputTreeplanter), "wallnetcdf": int(wallNetCDF), + "calculation_mode": calculation_mode, "date1": "2018,5,1,0", # used in standalone "date2": "2018,8,1,18", # used in standalone } diff --git a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py index d5c4745..8ac7ae0 100644 --- a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py +++ b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py @@ -2,7 +2,6 @@ from __future__ import division import numpy as np from math import radians -import torch # from scipy.ndimage.filters import median_filter diff --git a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py index 09a2eea..4a28224 100644 --- a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py +++ b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py @@ -42,7 +42,7 @@ def shade_on_walls(azimuth, aspect, walls, dsm, f, shvoveg): wallshve = wallshve - wallsh wallshve[wallshve < 0] = 0 id = torch.where(wallshve > walls) - wallshve[id] = walls[id].double() + wallshve[id] = walls[id].float() wallsun = wallsun - wallshve # problem with wallshve only id = torch.where(wallsun < 0) wallshve[id] = 0 diff --git a/util/shadowingfunctions.py b/util/shadowingfunctions.py index dde117a..0cb952d 100644 --- a/util/shadowingfunctions.py +++ b/util/shadowingfunctions.py @@ -1,94 +1,92 @@ # -*- coding: utf-8 -*- # Ready for python action! -import numpy as np +import torch from math import radians import matplotlib.pylab as plt +import numpy as np + -# from numba import jit +def _to_tensor(x, device, dtype=torch.float32): + if isinstance(x, torch.Tensor): + return x.to(device) + if x is None: + return None + return torch.tensor(x, dtype=dtype, device=device) def shadowingfunctionglobalradiation( - a, azimuth, altitude, scale, feedback, forsvf + a, azimuth, altitude, scale, feedback, forsvf, device=torch.device("cpu") ): + a = _to_tensor(a, device) + + degrees = torch.pi / 180.0 + azimuth = azimuth * degrees + altitude = altitude * degrees - # %This m.file calculates shadows on a DEM - # % conversion - degrees = np.pi / 180.0 - # if azimuth == 0.0: - # azimuth = 0.000000000001 - azimuth = np.dot(azimuth, degrees) - altitude = np.dot(altitude, degrees) - # % measure the size of the image sizex = a.shape[0] sizey = a.shape[1] if forsvf == 0: - barstep = np.max([sizex, sizey]) - total = 100.0 / barstep # dlg.progressBar.setRange(0, barstep) - # % initialise parameters + barstep = max(sizex, sizey) + total = 100.0 / barstep + f = a dx = 0.0 dy = 0.0 dz = 0.0 - temp = np.zeros((sizex, sizey)) + temp = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) index = 1.0 - # % other loop parameters - amaxvalue = a.max() - pibyfour = np.pi / 4.0 + + amaxvalue = torch.max(a).item() + pibyfour = torch.pi / 4.0 threetimespibyfour = 3.0 * pibyfour fivetimespibyfour = 5.0 * pibyfour seventimespibyfour = 7.0 * pibyfour - sinazimuth = np.sin(azimuth) - cosazimuth = np.cos(azimuth) - tanazimuth = np.tan(azimuth) - signsinazimuth = np.sign(sinazimuth) - signcosazimuth = np.sign(cosazimuth) - dssin = np.abs((1.0 / sinazimuth)) - dscos = np.abs((1.0 / cosazimuth)) - tanaltitudebyscale = np.tan(altitude) / scale - # % main loop - while amaxvalue >= dz and np.abs(dx) < sizex and np.abs(dy) < sizey: + sinazimuth = torch.sin(azimuth) + cosazimuth = torch.cos(azimuth) + tanazimuth = torch.tan(azimuth) + signsinazimuth = torch.sign(sinazimuth).item() + signcosazimuth = torch.sign(cosazimuth).item() + dssin = torch.abs(1.0 / sinazimuth).item() + dscos = torch.abs(1.0 / cosazimuth).item() + tanaltitudebyscale = torch.tan(altitude).item() / scale + + while amaxvalue >= dz and abs(dx) < sizex and abs(dy) < sizey: if forsvf == 0: feedback.setProgress(int(index * total)) - # dlg.progressBar.setValue(index) - # while np.logical_and(np.logical_and(amaxvalue >= dz, np.abs(dx) <= sizex), np.abs(dy) <= sizey):(np.logical_and(amaxvalue >= dz, np.abs(dx) <= sizex), np.abs(dy) <= sizey): - # if np.logical_or(np.logical_and(pibyfour <= azimuth, azimuth < threetimespibyfour), np.logical_and(fivetimespibyfour <= azimuth, azimuth < seventimespibyfour)): + if ( - pibyfour <= azimuth - and azimuth < threetimespibyfour - or fivetimespibyfour <= azimuth - and azimuth < seventimespibyfour + (pibyfour <= azimuth and azimuth < threetimespibyfour) + or (fivetimespibyfour <= azimuth and azimuth < seventimespibyfour) ): dy = signsinazimuth * index - dx = -1.0 * signcosazimuth * np.abs(np.round(index / tanazimuth)) + dx = -1.0 * signcosazimuth * abs(torch.round(index / tanazimuth)) ds = dssin else: - dy = signsinazimuth * np.abs(np.round(index * tanazimuth)) + dy = signsinazimuth * abs(torch.round(index * tanazimuth)) dx = -1.0 * signcosazimuth * index ds = dscos - # % note: dx and dy represent absolute values while ds is an incremental value dz = ds * index * tanaltitudebyscale - temp[0:sizex, 0:sizey] = 0.0 - absdx = np.abs(dx) - absdy = np.abs(dy) - xc1 = (dx + absdx) / 2.0 + 1.0 - xc2 = sizex + (dx - absdx) / 2.0 - yc1 = (dy + absdy) / 2.0 + 1.0 - yc2 = sizey + (dy - absdy) / 2.0 - xp1 = -((dx - absdx) / 2.0) + 1.0 - xp2 = sizex - (dx + absdx) / 2.0 - yp1 = -((dy - absdy) / 2.0) + 1.0 - yp2 = sizey - (dy + absdy) / 2.0 - temp[int(xp1) - 1 : int(xp2), int(yp1) - 1 : int(yp2)] = ( - a[int(xc1) - 1 : int(xc2), int(yc1) - 1 : int(yc2)] - dz - ) - # f = np.maximum(f, temp) # bad performance in python3. Replaced with fmax - f = np.fmax(f, temp) + temp[:, :] = 0.0 + + absdx = abs(dx) + absdy = abs(dy) + xc1 = int((dx + absdx) / 2.0 + 1.0) + xc2 = int(sizex + (dx - absdx) / 2.0) + yc1 = int((dy + absdy) / 2.0 + 1.0) + yc2 = int(sizey + (dy - absdy) / 2.0) + xp1 = int(-((dx - absdx) / 2.0) + 1.0) + xp2 = int(sizex - (dx + absdx) / 2.0) + yp1 = int(-((dy - absdy) / 2.0) + 1.0) + yp2 = int(sizey - (dy + absdy) / 2.0) + + temp[xp1:xp2, yp1:yp2] = a[xc1:xc2, yc1:yc2] - dz + f = torch.maximum(f, temp) index += 1.0 f = f - a - f = np.logical_not(f) - sh = np.double(f) + f = f == 0 + sh = f.to(dtype=a.dtype) return sh @@ -105,85 +103,59 @@ def shadowingfunction_20( bush, feedback, forsvf, + device=torch.device("cpu"), ): + a = _to_tensor(a, device) + vegdem = _to_tensor(vegdem, device) + vegdem2 = _to_tensor(vegdem2, device) + bush = _to_tensor(bush, device) - # plt.ion() - # fig = plt.figure(figsize=(24, 7)) - # plt.axis('image') - # ax1 = plt.subplot(2, 3, 1) - # ax2 = plt.subplot(2, 3, 2) - # ax3 = plt.subplot(2, 3, 3) - # ax4 = plt.subplot(2, 3, 4) - # ax5 = plt.subplot(2, 3, 5) - # ax6 = plt.subplot(2, 3, 6) - # ax1.title.set_text('fabovea') - # ax2.title.set_text('gabovea') - # ax3.title.set_text('vegsh at ' + str(altitude)) - # ax4.title.set_text('lastfabovea') - # ax5.title.set_text('lastgabovea') - # ax6.title.set_text('vegdem') - - # This function casts shadows on buildings and vegetation units. - # New capability to deal with pergolas 20210827 - - # conversion - degrees = np.pi / 180.0 + degrees = torch.pi / 180.0 azimuth = azimuth * degrees altitude = altitude * degrees - # measure the size of grid sizex = a.shape[0] sizey = a.shape[1] - # progressbar for svf plugin if forsvf == 0: - barstep = np.max([sizex, sizey]) + barstep = max(sizex, sizey) total = 100.0 / barstep feedback.setProgress(0) - # dlg.progressBar.setRange(0, barstep) - # dlg.progressBar.setValue(0) - # initialise parameters dx = 0.0 dy = 0.0 dz = 0.0 - temp = np.zeros((sizex, sizey)) - tempvegdem = np.zeros((sizex, sizey)) - tempvegdem2 = np.zeros((sizex, sizey)) - templastfabovea = np.zeros((sizex, sizey)) - templastgabovea = np.zeros((sizex, sizey)) + temp = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) + tempvegdem = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) + tempvegdem2 = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) + templastfabovea = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) + templastgabovea = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) bushplant = bush > 1.0 - sh = np.zeros((sizex, sizey)) # shadows from buildings - vbshvegsh = np.zeros((sizex, sizey)) # vegetation blocking buildings - vegsh = np.add( - np.zeros((sizex, sizey)), bushplant, dtype=float - ) # vegetation shadow + sh = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) + vbshvegsh = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) + vegsh = bushplant.to(dtype=a.dtype) f = a - pibyfour = np.pi / 4.0 + pibyfour = torch.pi / 4.0 threetimespibyfour = 3.0 * pibyfour fivetimespibyfour = 5.0 * pibyfour seventimespibyfour = 7.0 * pibyfour - sinazimuth = np.sin(azimuth) - cosazimuth = np.cos(azimuth) - tanazimuth = np.tan(azimuth) - signsinazimuth = np.sign(sinazimuth) - signcosazimuth = np.sign(cosazimuth) - dssin = np.abs((1.0 / sinazimuth)) - dscos = np.abs((1.0 / cosazimuth)) - tanaltitudebyscale = np.tan(altitude) / scale - # index = 1 - index = 0 - - # new case with pergola (thin vertical layer of vegetation), August 2021 - dzprev = 0 - - # main loop - while (amaxvalue >= dz) and (np.abs(dx) < sizex) and (np.abs(dy) < sizey): + sinazimuth = torch.sin(azimuth) + cosazimuth = torch.cos(azimuth) + tanazimuth = torch.tan(azimuth) + signsinazimuth = torch.sign(sinazimuth).item() + signcosazimuth = torch.sign(cosazimuth).item() + dssin = torch.abs(1.0 / sinazimuth).item() + dscos = torch.abs(1.0 / cosazimuth).item() + tanaltitudebyscale = torch.tan(altitude).item() / scale + index = 0.0 + + dzprev = 0.0 + + while (amaxvalue >= dz) and (abs(dx) < sizex) and (abs(dy) < sizey): if forsvf == 0: - feedback.setProgress( - int(index * total) - ) # dlg.progressBar.setValue(index) + feedback.setProgress(int(index * total)) + if ( (pibyfour <= azimuth) and (azimuth < threetimespibyfour) @@ -191,21 +163,22 @@ def shadowingfunction_20( and (azimuth < seventimespibyfour) ): dy = signsinazimuth * index - dx = -1.0 * signcosazimuth * np.abs(np.round(index / tanazimuth)) + dx = -1.0 * signcosazimuth * abs(torch.round(index / tanazimuth)) ds = dssin else: - dy = signsinazimuth * np.abs(np.round(index * tanazimuth)) + dy = signsinazimuth * abs(torch.round(index * tanazimuth)) dx = -1.0 * signcosazimuth * index ds = dscos - # note: dx and dy represent absolute values while ds is an incremental value + dz = (ds * index) * tanaltitudebyscale - tempvegdem[0:sizex, 0:sizey] = 0.0 - tempvegdem2[0:sizex, 0:sizey] = 0.0 - temp[0:sizex, 0:sizey] = 0.0 - templastfabovea[0:sizex, 0:sizey] = 0.0 - templastgabovea[0:sizex, 0:sizey] = 0.0 - absdx = np.abs(dx) - absdy = np.abs(dy) + tempvegdem[:, :] = 0.0 + tempvegdem2[:, :] = 0.0 + temp[:, :] = 0.0 + templastfabovea[:, :] = 0.0 + templastgabovea[:, :] = 0.0 + + absdx = abs(dx) + absdy = abs(dy) xc1 = int((dx + absdx) / 2.0) xc2 = int(sizex + (dx - absdx) / 2.0) yc1 = int((dy + absdy) / 2.0) @@ -219,133 +192,96 @@ def shadowingfunction_20( tempvegdem2[xp1:xp2, yp1:yp2] = vegdem2[xc1:xc2, yc1:yc2] - dz temp[xp1:xp2, yp1:yp2] = a[xc1:xc2, yc1:yc2] - dz - f = np.fmax(f, temp) # Moving building shadow - sh[(f > a)] = 1.0 - sh[(f <= a)] = 0.0 - fabovea = tempvegdem > a # vegdem above DEM - gabovea = tempvegdem2 > a # vegdem2 above DEM + f = torch.maximum(f, temp) + sh = (f > a).to(dtype=a.dtype) + vbshvegsh = vbshvegsh + + fabovea = tempvegdem > a + gabovea = tempvegdem2 > a - # new pergola condition templastfabovea[xp1:xp2, yp1:yp2] = vegdem[xc1:xc2, yc1:yc2] - dzprev templastgabovea[xp1:xp2, yp1:yp2] = vegdem2[xc1:xc2, yc1:yc2] - dzprev lastfabovea = templastfabovea > a lastgabovea = templastgabovea > a dzprev = dz - vegsh2 = np.add( - np.add( - np.add(fabovea, gabovea, dtype=float), lastfabovea, dtype=float - ), - lastgabovea, - dtype=float, + + vegsh2 = ( + fabovea.to(dtype=a.dtype) + + gabovea.to(dtype=a.dtype) + + lastfabovea.to(dtype=a.dtype) + + lastgabovea.to(dtype=a.dtype) ) - vegsh2[vegsh2 == 4] = 0.0 - # vegsh2[vegsh2 == 1] = 0. # This one is the ultimate question... - vegsh2[vegsh2 > 0] = 1.0 - - vegsh = np.fmax(vegsh, vegsh2) - vegsh[(vegsh * sh > 0.0)] = 0.0 - vbshvegsh = vegsh + vbshvegsh # removing shadows 'behind' buildings - - # im1 = ax1.imshow(fabovea) - # im2 = ax2.imshow(gabovea) - # im3 = ax3.imshow(vegsh) - # im4 = ax4.imshow(lastfabovea) - # im5 = ax5.imshow(lastgabovea) - # im6 = ax6.imshow(vegshtest) - # im1 = ax1.imshow(tempvegdem) - # im2 = ax2.imshow(tempvegdem2) - # im3 = ax3.imshow(vegsh) - # im4 = ax4.imshow(templastfabovea) - # im5 = ax5.imshow(templastgabovea) - # im6 = ax6.imshow(vegshtest) - # plt.show() - # plt.pause(0.05) + vegsh2[vegsh2 == 4.0] = 0.0 + vegsh2[vegsh2 > 0.0] = 1.0 + + vegsh = torch.maximum(vegsh, vegsh2) + vegsh[(vegsh * sh) > 0.0] = 0.0 + vbshvegsh = vegsh + vbshvegsh index += 1.0 sh = 1.0 - sh - vbshvegsh[(vbshvegsh > 0.0)] = 1.0 + vbshvegsh[vbshvegsh > 0.0] = 1.0 vbshvegsh = vbshvegsh - vegsh vegsh = 1.0 - vegsh vbshvegsh = 1.0 - vbshvegsh - # plt.close() - # plt.ion() - # fig = plt.figure(figsize=(24, 7)) - # plt.axis('image') - # ax1 = plt.subplot(1, 3, 1) - # im1 = ax1.imshow(vegsh) - # plt.colorbar(im1) - - # ax2 = plt.subplot(1, 3, 2) - # im2 = ax2.imshow(vegdem2) - # plt.colorbar(im2) - # plt.title('TDSM') - - # ax3 = plt.subplot(1, 3, 3) - # im3 = ax3.imshow(vegdem) - # plt.colorbar(im3) - # plt.tight_layout() - # plt.title('CDSM') - # plt.show() - # plt.pause(0.05) - shadowresult = {"sh": sh, "vegsh": vegsh, "vbshvegsh": vbshvegsh} - return shadowresult def shadowingfunction_20_old( a, vegdem, vegdem2, azimuth, altitude, scale, amaxvalue, bush, dlg, forsvf ): + a = _to_tensor(a, torch.device("cpu")) + vegdem = _to_tensor(vegdem, torch.device("cpu")) + vegdem2 = _to_tensor(vegdem2, torch.device("cpu")) + bush = _to_tensor(bush, torch.device("cpu")) - # % This function casts shadows on buildings and vegetation units - # % conversion - degrees = np.pi / 180.0 + degrees = torch.pi / 180.0 if azimuth == 0.0: - azimuth = 0.000000000001 - azimuth = np.dot(azimuth, degrees) - altitude = np.dot(altitude, degrees) - # % measure the size of the image + azimuth = 1e-12 + azimuth = azimuth * degrees + altitude = altitude * degrees + sizex = a.shape[0] sizey = a.shape[1] - # % initialise parameters if forsvf == 0: - barstep = np.max([sizex, sizey]) + barstep = max(sizex, sizey) dlg.progressBar.setRange(0, barstep) dlg.progressBar.setValue(0) dx = 0.0 dy = 0.0 dz = 0.0 - temp = np.zeros((sizex, sizey)) - tempvegdem = np.zeros((sizex, sizey)) - tempvegdem2 = np.zeros((sizex, sizey)) - sh = np.zeros((sizex, sizey)) - vbshvegsh = np.zeros((sizex, sizey)) - vegsh = np.zeros((sizex, sizey)) - tempbush = np.zeros((sizex, sizey)) + temp = torch.zeros((sizex, sizey), dtype=a.dtype) + tempvegdem = torch.zeros((sizex, sizey), dtype=a.dtype) + tempvegdem2 = torch.zeros((sizex, sizey), dtype=a.dtype) + sh = torch.zeros((sizex, sizey), dtype=a.dtype) + vbshvegsh = torch.zeros((sizex, sizey), dtype=a.dtype) + vegsh = torch.zeros((sizex, sizey), dtype=a.dtype) + tempbush = torch.zeros((sizex, sizey), dtype=a.dtype) f = a - g = np.zeros((sizex, sizey)) + g = torch.zeros((sizex, sizey), dtype=a.dtype) bushplant = bush > 1.0 - pibyfour = np.pi / 4.0 + + pibyfour = torch.pi / 4.0 threetimespibyfour = 3.0 * pibyfour fivetimespibyfour = 5.0 * pibyfour seventimespibyfour = 7.0 * pibyfour - sinazimuth = np.sin(azimuth) - cosazimuth = np.cos(azimuth) - tanazimuth = np.tan(azimuth) - signsinazimuth = np.sign(sinazimuth) - signcosazimuth = np.sign(cosazimuth) - dssin = np.abs((1.0 / sinazimuth)) - dscos = np.abs((1.0 / cosazimuth)) - tanaltitudebyscale = np.tan(altitude) / scale - index = 1 + sinazimuth = torch.sin(azimuth) + cosazimuth = torch.cos(azimuth) + tanazimuth = torch.tan(azimuth) + signsinazimuth = torch.sign(sinazimuth).item() + signcosazimuth = torch.sign(cosazimuth).item() + dssin = torch.abs(1.0 / sinazimuth).item() + dscos = torch.abs(1.0 / cosazimuth).item() + tanaltitudebyscale = torch.tan(altitude).item() / scale + index = 1.0 - # % main loop - while amaxvalue >= dz and np.abs(dx) < sizex and np.abs(dy) < sizey: + while amaxvalue >= dz and abs(dx) < sizex and abs(dy) < sizey: if forsvf == 0: - dlg.progressBar.setValue(index) + dlg.progressBar.setValue(int(index)) if ( pibyfour <= azimuth and azimuth < threetimespibyfour @@ -353,88 +289,74 @@ def shadowingfunction_20_old( and azimuth < seventimespibyfour ): dy = signsinazimuth * index - dx = -1.0 * signcosazimuth * np.abs(np.round(index / tanazimuth)) + dx = -1.0 * signcosazimuth * abs(torch.round(index / tanazimuth)) ds = dssin else: - dy = signsinazimuth * np.abs(np.round(index * tanazimuth)) + dy = signsinazimuth * abs(torch.round(index * tanazimuth)) dx = -1.0 * signcosazimuth * index ds = dscos - # % note: dx and dy represent absolute values while ds is an incremental value - dz = np.dot(np.dot(ds, index), tanaltitudebyscale) - tempvegdem[0:sizex, 0:sizey] = 0.0 - tempvegdem2[0:sizex, 0:sizey] = 0.0 - temp[0:sizex, 0:sizey] = 0.0 - absdx = np.abs(dx) - absdy = np.abs(dy) - xc1 = (dx + absdx) / 2.0 + 1.0 - xc2 = sizex + (dx - absdx) / 2.0 - yc1 = (dy + absdy) / 2.0 + 1.0 - yc2 = sizey + (dy - absdy) / 2.0 - xp1 = -((dx - absdx) / 2.0) + 1.0 - xp2 = sizex - (dx + absdx) / 2.0 - yp1 = -((dy - absdy) / 2.0) + 1.0 - yp2 = sizey - (dy + absdy) / 2.0 - tempvegdem[int(xp1) - 1 : int(xp2), int(yp1) - 1 : int(yp2)] = ( - vegdem[int(xc1) - 1 : int(xc2), int(yc1) - 1 : int(yc2)] - dz - ) - tempvegdem2[int(xp1) - 1 : int(xp2), int(yp1) - 1 : int(yp2)] = ( - vegdem2[int(xc1) - 1 : int(xc2), int(yc1) - 1 : int(yc2)] - dz - ) - temp[int(xp1) - 1 : int(xp2), int(yp1) - 1 : int(yp2)] = ( - a[int(xc1) - 1 : int(xc2), int(yc1) - 1 : int(yc2)] - dz - ) - # f = np.maximum(f, temp) # bad performance in python3. Replaced with fmax - f = np.fmax(f, temp) + + dz = ds * index * tanaltitudebyscale + tempvegdem[:, :] = 0.0 + tempvegdem2[:, :] = 0.0 + temp[:, :] = 0.0 + + absdx = abs(dx) + absdy = abs(dy) + xc1 = int((dx + absdx) / 2.0 + 1.0) + xc2 = int(sizex + (dx - absdx) / 2.0) + yc1 = int((dy + absdy) / 2.0 + 1.0) + yc2 = int(sizey + (dy - absdy) / 2.0) + xp1 = int(-((dx - absdx) / 2.0) + 1.0) + xp2 = int(sizex - (dx + absdx) / 2.0) + yp1 = int(-((dy - absdy) / 2.0) + 1.0) + yp2 = int(sizey - (dy + absdy) / 2.0) + + tempvegdem[xp1:xp2, yp1:yp2] = vegdem[xc1:xc2, yc1:yc2] - dz + tempvegdem2[xp1:xp2, yp1:yp2] = vegdem2[xc1:xc2, yc1:yc2] - dz + temp[xp1:xp2, yp1:yp2] = a[xc1:xc2, yc1:yc2] - dz + + f = torch.maximum(f, temp) sh[(f > a)] = 1.0 sh[(f <= a)] = 0.0 - # %Moving building shadow fabovea = tempvegdem > a - # %vegdem above DEM gabovea = tempvegdem2 > a - # %vegdem2 above DEM - # vegsh2 = np.float(fabovea)-np.float(gabovea) - vegsh2 = np.subtract(fabovea, gabovea, dtype=float) - # vegsh = np.maximum(vegsh, vegsh2) # bad performance in python3. Replaced with fmax - vegsh = np.fmax(vegsh, vegsh2) - vegsh[(vegsh * sh > 0.0)] = 0.0 - # % removing shadows 'behind' buildings + vegsh2 = fabovea.to(dtype=a.dtype) - gabovea.to(dtype=a.dtype) + vegsh = torch.maximum(vegsh, vegsh2) + vegsh[(vegsh * sh) > 0.0] = 0.0 vbshvegsh = vegsh + vbshvegsh - # % vegsh at high sun altitudes + if index == 1.0: firstvegdem = tempvegdem - temp - firstvegdem[(firstvegdem <= 0.0)] = 1000.0 - vegsh[(firstvegdem < dz)] = 1.0 - vegsh = vegsh * (vegdem2 > a) - vbshvegsh = np.zeros((sizex, sizey)) - - # % Bush shadow on bush plant - if np.logical_and(bush.max() > 0.0, np.max((fabovea * bush)) > 0.0): - tempbush[0:sizex, 0:sizey] = 0.0 - tempbush[int(xp1) - 1 : int(xp2), int(yp1) - 1 : int(yp2)] = ( - bush[int(xc1) - 1 : int(xc2), int(yc1) - 1 : int(yc2)] - dz - ) - # g = np.maximum(g, tempbush) # bad performance in python3. Replaced with fmax - g = np.fmax(g, tempbush) - g *= bushplant + firstvegdem[firstvegdem <= 0.0] = 1000.0 + vegsh[firstvegdem < dz] = 1.0 + vegsh = vegsh * (vegdem2 > a).to(dtype=a.dtype) + vbshvegsh = torch.zeros((sizex, sizey), dtype=a.dtype) + + if torch.max(bush) > 0.0 and torch.max((fabovea.to(dtype=a.dtype) * bush)) > 0.0: + tempbush[:, :] = 0.0 + tempbush[xp1:xp2, yp1:yp2] = bush[xc1:xc2, yc1:yc2] - dz + g = torch.maximum(g, tempbush) + g = g * bushplant + index += 1.0 sh = 1.0 - sh - vbshvegsh[(vbshvegsh > 0.0)] = 1.0 + vbshvegsh[vbshvegsh > 0.0] = 1.0 vbshvegsh = vbshvegsh - vegsh - if bush.max() > 0.0: + if torch.max(bush) > 0.0: g = g - bush - g[(g > 0.0)] = 1.0 - g[(g < 0.0)] = 0.0 - vegsh = vegsh - bushplant + g - vegsh[(vegsh < 0.0)] = 0.0 + g[g > 0.0] = 1.0 + g[g < 0.0] = 0.0 + vegsh = vegsh - bushplant.to(dtype=a.dtype) + g + vegsh[vegsh < 0.0] = 0.0 - vegsh[(vegsh > 0.0)] = 1.0 + vegsh[vegsh > 0.0] = 1.0 vegsh = 1.0 - vegsh vbshvegsh = 1.0 - vbshvegsh shadowresult = {"sh": sh, "vegsh": vegsh, "vbshvegsh": vbshvegsh} - return shadowresult @@ -452,6 +374,7 @@ def shadowingfunction_findwallID( facesh, wall_dict, sh, + device ): """ This function identifies what wall id and voxel height that is seen from a ground pixel @@ -475,7 +398,6 @@ def shadowingfunction_findwallID( # Remove ground heights dsm = dsm - dem - # buildings = 1 - ((dsm) > 0) dsm[dsm < 0.5] = 0 # conversion, degrees to radians @@ -483,63 +405,71 @@ def shadowingfunction_findwallID( altitude = radians(altitude) # measure the size of the image - rows = np.shape(dsm)[0] - cols = np.shape(dsm)[1] + rows = dsm.shape[0] + cols = dsm.shape[1] # initialise parameters - f = np.copy(dsm) - buildIDSeen = np.zeros((rows, cols)) - # h = np.zeros((rows, cols)) - - dx = 0 - dy = 0 - dz = 0 - temp = np.zeros((rows, cols)) - temp2 = np.zeros((rows, cols)) # walls - tempwallID = np.zeros((rows, cols)) - uniqueWallIDsOrig = np.copy(uniqueWallIDs) - - voxelHeight = np.zeros((rows, cols)) - temp3 = np.ones((rows, cols)) + f = torch.clone(dsm) + buildIDSeen = torch.zeros((rows, cols), device=device) + + dx = torch.tensor(0, device=device) + dy = torch.tensor(0, device=device) + dz = torch.tensor(0, device=device) + temp = torch.zeros((rows, cols), device=device) + temp2 = torch.zeros((rows, cols), device=device) # walls + tempwallID = torch.zeros((rows, cols), device=device) + uniqueWallIDsOrig = torch.clone(uniqueWallIDs) + + voxelHeight = torch.zeros((rows, cols), device=device) + temp3 = torch.ones((rows, cols), device=device) + + # create a fast PyTorch tensor lookup table for wall_dict + max_wall_id = int(max(wall_dict.keys())) if wall_dict else 0 + wall_height_lookup = torch.zeros(max_wall_id + 1, device=device) + for k, v in wall_dict.items(): + wall_height_lookup[int(k)] = v # other loop parameters - amaxvalue = np.max(dsm) - pibyfour = np.pi / 4 + amaxvalue = torch.max(dsm) + pibyfour = torch.pi / 4 threetimespibyfour = 3 * pibyfour fivetimespibyfour = 5 * pibyfour seventimespibyfour = 7 * pibyfour - sinazimuth = np.sin(azimuth) - cosazimuth = np.cos(azimuth) - tanazimuth = np.tan(azimuth) - signsinazimuth = np.sign(sinazimuth) - signcosazimuth = np.sign(cosazimuth) - dssin = np.abs(1 / sinazimuth) - dscos = np.abs(1 / cosazimuth) - tanaltitudebyscale = np.tan(altitude) / scale + azimuth = torch.tensor(azimuth, device=device) + sinazimuth = torch.sin(azimuth) + cosazimuth = torch.cos(azimuth) + tanazimuth = torch.tan(azimuth) + signsinazimuth = torch.sign(sinazimuth) + signcosazimuth = torch.sign(cosazimuth) + dssin = torch.abs(1 / sinazimuth) + dscos = torch.abs(1 / cosazimuth) + altitude = torch.tensor(altitude, device=device) + scale = torch.tensor(scale, device=device) + + tanaltitudebyscale = torch.tan(altitude) / scale index = 1 # main loop - while (amaxvalue >= dz) and (np.abs(dx) < rows) and (np.abs(dy) < cols): + while (amaxvalue >= dz) and (torch.abs(dx) < rows) and (torch.abs(dy) < cols): if (pibyfour <= azimuth and azimuth < threetimespibyfour) or ( fivetimespibyfour <= azimuth and azimuth < seventimespibyfour ): dy = signsinazimuth * index - dx = -1 * signcosazimuth * np.abs(np.round(index / tanazimuth)) + dx = -1 * signcosazimuth * torch.abs(torch.round(index / tanazimuth)) ds = dssin else: - dy = signsinazimuth * np.abs(np.round(index * tanazimuth)) + dy = signsinazimuth * torch.abs(torch.round(index * tanazimuth)) dx = -1 * signcosazimuth * index ds = dscos - # note: dx and dy represent absolute values while ds is an incremental value dz = ds * index * tanaltitudebyscale temp[0:rows, 0:cols] = 0 temp2[0:rows, 0:cols] = 0 - absdx = np.abs(dx) - absdy = np.abs(dy) + absdx = torch.abs(dx) + absdy = torch.abs(dy) xc1 = int((dx + absdx) / 2) xc2 = int(rows + (dx - absdx) / 2) @@ -553,136 +483,62 @@ def shadowingfunction_findwallID( wallSeen = facesh uniqueWallIDs = uniqueWallIDs * wallSeen - # uniqueWallIDs = ((uniqueWallIDs - firstMove) < 0) * uniqueWallIDsOrig + uniqueWallIDs # adding missing corner - # wallSeenHeight = walls * wallSeen - # temp2[xp1:xp2, yp1:yp2] = wallSeenHeight[xc1:xc2, yc1:yc2] - dz # Moving wall shadow # Moving wall id tempwallID[xp1:xp2, yp1:yp2] = uniqueWallIDs[xc1:xc2, yc1:yc2] - # Get wall height from wall id - temp_wallHeight = np.vectorize(wall_dict.__getitem__)(tempwallID) - + # FIX: Native PyTorch tensor lookup instead of np.vectorize + temp_wallHeight = wall_height_lookup[tempwallID.long()] + # Descending wall, how much of the wall that is still above ground level temp2 = temp_wallHeight - dz - # buildIDSeen = Wall pixels/voxels seen, i.e. only voxels that are positive (above ground level) (temp2 > 0). - # temp3 indicates those pixels that the walls have not progressed into yet (saved in previous iteration). + # buildIDSeen calculations buildIDSeen = (temp2 > 0) * temp3 * tempwallID + buildIDSeen - # voxelHeight = (temp2 > 0) * temp3 * temp2 + voxelHeight # seen wall heights - - # voxelHeight = the elevation on a wall that is seen from a pixel with the given altitude and azimuth (only above ground leve, i.e. (temp2 > 0)). - # voxelHeight = wall height - descending wall, i.e. temp_wallHeight - temp2. Only applicable to pixels where there is no value from previous iterations (temp3). - voxelHeight = (temp2 > 0) * temp3 * ( - temp_wallHeight - temp2 - ) + voxelHeight - # voxelHeight = (temp2 > 0) * temp3 * (temp_wallHeight - (temp2 * (temp2 > 0))) + voxelHeight # seen wall heights + # voxelHeight calculations + voxelHeight = (temp2 > 0) * temp3 * (temp_wallHeight - temp2) + voxelHeight # Remember pixels previous iteration that walls have not progressed into yet. - temp3 = np.copy(temp2 <= 0) * (buildIDSeen == 0) + temp3 = torch.clone(temp2 <= 0) * (buildIDSeen == 0) index += 1 # Ceil voxel height values to integers - voxelHeight_ceil = np.ceil(voxelHeight) - # voxelHeight_ceil = np.round(voxelHeight) + voxelHeight_ceil = torch.ceil(voxelHeight) # Empty raster to fill with voxel IDs - voxelId = np.zeros((rows, cols)) - # Convert wall2d_id from list to numpy array - wall2d_id = np.array(wall2d_id) - # Convert voxel_height from list to numpy array - voxel_height = np.array(voxel_height) - # Convert voxelId_list from list to numpy array - voxelId_list = np.array(voxelId_list, dtype=int) - - # Flatten buildIDseen from matrix to array + voxelId = torch.zeros((rows, cols), device=device) + + # Ensure mapping references are native PyTorch tensors on the correct device + wall2d_id = torch.tensor(wall2d_id, device=device) + voxel_height = torch.tensor(voxel_height, device=device) + voxelId_list = torch.tensor(voxelId_list, dtype=torch.long, device=device) + + # Flatten maps to find unique combinations a = buildIDSeen.flatten() - # Flatten voxelHeight_ceil from matrix to array b = voxelHeight_ceil.flatten() - # Combine the two above arrays into an n by 2 array - c = np.column_stack([a, b]) - # Find unique values in c - d = np.unique(c, axis=0) - # Remove rows where both columns are zero - # d = d[((d[:,0] > 0) & (d[:,1] > 0)), :] - # d = d[d[:,:] > 0, :] - d = d[~np.all(d == 0, axis=1)] - # d = d[d[:, 0] > 0, :] - # d = d[d[:, 1] > 0, :] - - not_in_list = 0 - in_list = 0 + c = torch.column_stack([a, b]) + d = torch.unique(c, dim=0) + d = d[~torch.all(d == 0, dim=1)] + # Fill voxelId matrix with unique voxel IDs for temp_id, temp_height in d: - # print(str(temp_id) + ' ' + str(temp_height)) - temp_fill_id = voxelId_list[ - ((wall2d_id == temp_id) & (voxel_height == temp_height)) - ] - if temp_fill_id.__len__() > 0: - # print('temp_fill_id = ' + str(temp_fill_id)) - voxelId[ - (buildIDSeen == temp_id) & (voxelHeight_ceil == temp_height) - ] = temp_fill_id - in_list += 1 + # FIX: Safer element checking using .numel() and extraction via index [0] + mask = (wall2d_id == temp_id) & (voxel_height == temp_height) + temp_fill_id = voxelId_list[mask] + + pixel_mask = (buildIDSeen == temp_id) & (voxelHeight_ceil == temp_height) + + if temp_fill_id.numel() > 0: + voxelId[pixel_mask] = temp_fill_id[0].float() else: - not_in_list += 1 - buildIDSeen[ - (buildIDSeen == temp_id) & (voxelHeight_ceil == temp_height) - ] = 0 - voxelHeight_ceil[ - (buildIDSeen == temp_id) & (voxelHeight_ceil == temp_height) - ] = 0 - - # if ((np.any(buildIDSeen == temp_id) & ~np.all(voxelHeight_ceil == temp_height)) or (~np.all(buildIDSeen == temp_id) & np.any(voxelHeight_ceil == temp_height))): - # print('temp_id = ' + str(temp_id)) - # print('temp_height = ' + str(temp_height)) - - # ax = plt.subplot(1, 2, 1) - # im = ax.imshow(buildIDSeen, vmin=0, vmax=uniqueWallIDs.max()) - # ax1 = plt.subplot(1, 2, 2) - # im = ax1.imshow(voxelHeight, vmin=0, vmax=40) - # plt.pause(0.1) # In interactive mode, need a small delay to get the plot to appear - # plt.draw() - - # Correct for shadows, i.e. remove weird pixels on top of buildings etc + buildIDSeen[pixel_mask] = 0 + voxelHeight_ceil[pixel_mask] = 0 + + # Correct for shadows buildIDSeen = buildIDSeen * (1 - sh) voxelHeight = voxelHeight * (1 - sh) voxelId = voxelId * (1 - sh) - return buildIDSeen, voxelHeight, voxelId - - -# temp[xp1:xp2, yp1:yp2] = dsm[xc1:xc2, yc1:yc2] - dz -# f = np.fmax(f, temp) #Moving building shadow - -# if index == 1: #Remove walls on "wrong" side of buildings during first iteration - -# firstMove = (f-dsm) > 0 -# if (pibyfour <= azimuth and azimuth < threetimespibyfour) or \ -# (fivetimespibyfour <= azimuth and azimuth < seventimespibyfour): -# dy = signsinazimuth * index -# dx = -1 * signcosazimuth -# else: -# dy = signsinazimuth -# dx = -1 * signcosazimuth * index - -# absdx = np.abs(dx) -# absdy = np.abs(dy) - -# xc1b = int((dx+absdx)/2) -# xc2b = int(rows+(dx-absdx)/2) -# yc1b = int((dy+absdy)/2) -# yc2b = int(cols+(dy-absdy)/2) - -# xp1b = int(-((dx-absdx)/2)) -# xp2b = int(rows-(dx+absdx)/2) -# yp1b = int(-((dy-absdy)/2)) -# yp2b = int(cols-(dy+absdy)/2) - -# temp[xp1b:xp2b, yp1b:yp2b] = dsm[xc1b:xc2b, yc1b:yc2b] - -# wallSeen = temp-dsm -# wallSeen[wallSeen > 0] = 1 -# wallSeen[wallSeen < 0] = 0 + return buildIDSeen, voxelHeight, voxelId \ No newline at end of file diff --git a/util/umep_solweig_export_component.py b/util/umep_solweig_export_component.py index cf79b19..0c8d34e 100644 --- a/util/umep_solweig_export_component.py +++ b/util/umep_solweig_export_component.py @@ -125,6 +125,8 @@ def write_solweig_config(configDict, refdir): f.write("outputkdiff={}\n".format(configDict["outputkdiff"])) f.write("outputtreeplanter={}\n".format(configDict["outputtreeplanter"])) f.write("wallnetcdf={}\n".format(configDict["wallnetcdf"])) + f.write("# calculation mode (cpu or gpu)\n") + f.write("calculation_mode={}\n".format(configDict["calculation_mode"])) f.write("#-------------------------------------------------------\n") f.write("# dates - used if an EPW-file is used\n") f.write("#-------------------------------------------------------\n") From cff423a491bd1202248c49c4b18ca89475744ede Mon Sep 17 00:00:00 2001 From: "Mael.PHILIPPE" Date: Wed, 20 May 2026 11:45:33 +0200 Subject: [PATCH 04/20] fix/bugs --- functions/svf_functions.py | 1 + preprocessor/skyviewfactor_algorithm.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/functions/svf_functions.py b/functions/svf_functions.py index 4d5e0bf..adb0a85 100644 --- a/functions/svf_functions.py +++ b/functions/svf_functions.py @@ -137,6 +137,7 @@ def svfForProcessing153( ): if device is None: device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") + dsm = _to_tensor(dsm, device) vegdem = _to_tensor(vegdem, device) vegdem2 = _to_tensor(vegdem2, device) diff --git a/preprocessor/skyviewfactor_algorithm.py b/preprocessor/skyviewfactor_algorithm.py index f657162..b125b3f 100644 --- a/preprocessor/skyviewfactor_algorithm.py +++ b/preprocessor/skyviewfactor_algorithm.py @@ -380,7 +380,7 @@ def processAlgorithm(self, parameters, context, feedback): else: rows = dsm.shape[0] cols = dsm.shape[1] - vegdsm = np.zeros([rows, cols]) + vegdsm = torch.zeros([rows, cols], device=device) vegdsm2 = 0.0 usevegdem = 0 @@ -426,12 +426,12 @@ def processAlgorithm(self, parameters, context, feedback): trans = transVeg / 100.0 svftotal = svfbu - (1 - svfveg) * (1 - trans) # Lägg till loop för att lägga till i tabellen - svf_array = np.zeros((voxelTable.shape[0])) - svf_height_array = np.zeros((voxelTable.shape[0])) - svfbu_array = np.zeros((voxelTable.shape[0])) - svfveg_array = np.zeros((voxelTable.shape[0])) - svfaveg_array = np.zeros((voxelTable.shape[0])) - voxel_y = np.where(voxelTable[:, 1] == svf_height) + svf_array = torch.zeros((voxelTable.shape[0]), device=device) + svf_height_array = torch.zeros((voxelTable.shape[0]), device=device) + svfbu_array = torch.zeros((voxelTable.shape[0]), device=device) + svfveg_array = torch.zeros((voxelTable.shape[0]), device=device) + svfaveg_array = torch.zeros((voxelTable.shape[0]), device=device) + voxel_y = torch.where(voxelTable[:, 1] == svf_height) for temp_y in voxel_y[0]: svf_array[temp_y] = svftotal[ int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) @@ -655,7 +655,7 @@ def processAlgorithm(self, parameters, context, feedback): np.savez_compressed( outputDir + "/" + "wallScheme.npz", - voxelId=voxelId, + voxelId=voxelId.cpu().detach().numpy(), voxelTable=voxelTable.cpu().detach().numpy(), ) From 13c5252599be7e02963496a64bc8594fca705998 Mon Sep 17 00:00:00 2001 From: "Mael.PHILIPPE" Date: Fri, 22 May 2026 11:30:58 +0200 Subject: [PATCH 05/20] really optimized algorithm. Changed the shadow calculation functions to gpu optimized but the algorithm became very memory hungry. --- functions/SEBEfiles/Perez_v3_moved.py | 348 --------- .../SEBE_2015a_calc_forprocessing.py | 84 +-- functions/SEBEfiles/importdata.py | 11 +- functions/SEBEfiles/sunmapcreator_2015a.py | 114 +-- functions/SOLWEIGpython/Kside_veg_v2022a.py | 283 ++++--- functions/SOLWEIGpython/Kup_veg_2015a.py | 1 + functions/SOLWEIGpython/Lside_veg.py | 57 +- functions/SOLWEIGpython/Lside_veg_v2015a.py | 28 +- functions/SOLWEIGpython/Lside_veg_v2022a.py | 230 ------ functions/SOLWEIGpython/Lvikt_veg.py | 1 + functions/SOLWEIGpython/Perez_v3_moved.py | 351 --------- .../Solweig_2025a_calc_forprocessing.py | 9 +- .../Solweig_2026a_calc_forprocessing.py | 28 +- functions/SOLWEIGpython/Solweig_run.py | 304 +++++--- functions/SOLWEIGpython/cylindric_wedge.py | 26 +- functions/SOLWEIGpython/daylen.py | 6 +- functions/SOLWEIGpython/ground_surface.py | 44 +- functions/URock/Obstacles.py | 2 +- functions/dailyshading.py | 7 +- functions/svf_for_voxels.py | 165 +++-- functions/svf_functions.py | 59 +- functions/wallalgorithms.py | 407 +++++----- preprocessor/skyviewfactor_algorithm.py | 221 +++--- preprocessor/wall_heightaspect_algorithm.py | 56 +- processor/sebe_algorithm.py | 33 +- processor/solweig_algorithm.py | 16 +- util/SEBESOLWEIGCommonFiles/Perez_v3.py | 267 +++---- .../Solweig_v2015_metdata_noload.py | 25 +- .../clearnessindex_2013b.py | 12 +- util/SEBESOLWEIGCommonFiles/create_patches.py | 93 ++- .../SEBESOLWEIGCommonFiles/diffusefraction.py | 6 +- .../shadowingfunction_wallheight_13.py | 301 ++++---- .../shadowingfunction_wallheight_23.py | 294 ++++---- util/SEBESOLWEIGCommonFiles/sun_position.py | 175 +++-- util/shadowingfunctions.py | 698 ++++++++---------- 35 files changed, 2021 insertions(+), 2741 deletions(-) delete mode 100644 functions/SEBEfiles/Perez_v3_moved.py delete mode 100644 functions/SOLWEIGpython/Lside_veg_v2022a.py delete mode 100644 functions/SOLWEIGpython/Perez_v3_moved.py diff --git a/functions/SEBEfiles/Perez_v3_moved.py b/functions/SEBEfiles/Perez_v3_moved.py deleted file mode 100644 index bf240cf..0000000 --- a/functions/SEBEfiles/Perez_v3_moved.py +++ /dev/null @@ -1,348 +0,0 @@ -from __future__ import division -from __future__ import print_function -from builtins import range -import numpy as np - - -def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): - """ - This function calculates distribution of luminance on the skyvault based on - Perez luminince distribution model. - - Created by: - Fredrik Lindberg 20120527, fredrikl@gvc.gu.se - Gothenburg University, Sweden - Urban Climte Group - - Input parameters: - - zen: Zenith angle of the Sun (in degrees) - - azimuth: Azimuth angle of the Sun (in degrees) - - radD: Horizontal diffuse radiation (W m-2) - - radI: Direct radiation perpendicular to the Sun beam (W m-2) - - jday: Day of year - - Output parameters: - - lv: Relative luminance map (same dimensions as theta. gamma) - - - acoeff=[1.353 -0.258 -0.269 -1.437 - -1.222 -0.773 1.415 1.102 - -1.100 -0.252 0.895 0.016 - -0.585 -0.665 -0.267 0.712 - -0.600 -0.347 -2.500 2.323 - -1.016 -0.367 1.008 1.405 - -1.000 0.021 0.503 -0.512 - -1.050 0.029 0.426 0.359]; - - bcoeff=[-0.767 0.001 1.273 -0.123 - -0.205 0.037 -3.913 0.916 - 0.278 -0.181 -4.500 1.177 - 0.723 -0.622 -5.681 2.630 - 0.294 0.049 -5.681 1.842 - 0.288 -0.533 -3.850 3.375 - -0.300 0.192 0.702 -1.632 - -0.325 0.116 0.778 0.003]; - - ccoeff=[2.800 0.600 1.238 1.000 - 6.975 0.177 6.448 -0.124 - 24.22 -13.08 -37.70 34.84 - 33.34 -18.30 -62.25 52.08 - 21.00 -4.766 -21.59 7.249 - 14.00 -0.999 -7.14 7.547 - 19.00 -5.000 1.243 -1.91 - 31.06 -14.50 -46.11 55.37]; - - dcoeff=[1.874 0.630 0.974 0.281 - -1.580 -0.508 -1.781 0.108 - -5.00 1.522 3.923 -2.62 - -3.50 0.002 1.148 0.106 - -3.50 -0.155 1.406 0.399 - -3.40 -0.108 -1.075 1.57 - -4.00 0.025 0.384 0.266 - -7.23 0.405 13.35 0.623]; - - ecoeff=[0.035 -0.125 -0.572 0.994 - 0.262 0.067 -0.219 -0.428 - -0.016 0.160 0.420 -0.556 - 0.466 -0.33 -0.088 -0.033 - 0.003 0.077 -0.066 -0.129 - -0.067 0.402 0.302 -0.484 - 1.047 -0.379 -2.452 1.466 - 1.500 -0.643 1.856 0.564]; - - :param zen: - :param azimuth: - :param radD: - :param radI: - :param jday: - :param patchchoice: - :return: - """ - - m_a1 = np.array( - [1.3525, -1.2219, -1.1000, -0.5484, -0.6000, -1.0156, -1.0000, -1.0500] - ) - m_a2 = np.array( - [-0.2576, -0.7730, -0.2515, -0.6654, -0.3566, -0.3670, 0.0211, 0.0289] - ) - m_a3 = np.array( - [-0.2690, 1.4148, 0.8952, -0.2672, -2.5000, 1.0078, 0.5025, 0.4260] - ) - m_a4 = np.array( - [-1.4366, 1.1016, 0.0156, 0.7117, 2.3250, 1.4051, -0.5119, 0.3590] - ) - m_b1 = np.array( - [-0.7670, -0.2054, 0.2782, 0.7234, 0.2937, 0.2875, -0.3000, -0.3250] - ) - m_b2 = np.array( - [0.0007, 0.0367, -0.1812, -0.6219, 0.0496, -0.5328, 0.1922, 0.1156] - ) - m_b3 = np.array( - [1.2734, -3.9128, -4.5000, -5.6812, -5.6812, -3.8500, 0.7023, 0.7781] - ) - m_b4 = np.array( - [-0.1233, 0.9156, 1.1766, 2.6297, 1.8415, 3.3750, -1.6317, 0.0025] - ) - m_c1 = np.array( - [2.8000, 6.9750, 24.7219, 33.3389, 21.0000, 14.0000, 19.0000, 31.0625] - ) - m_c2 = np.array( - [ - 0.6004, - 0.1774, - -13.0812, - -18.3000, - -4.7656, - -0.9999, - -5.0000, - -14.5000, - ] - ) - m_c3 = np.array( - [ - 1.2375, - 6.4477, - -37.7000, - -62.2500, - -21.5906, - -7.1406, - 1.2438, - -46.1148, - ] - ) - m_c4 = np.array( - [1.0000, -0.1239, 34.8438, 52.0781, 7.2492, 7.5469, -1.9094, 55.3750] - ) - m_d1 = np.array( - [1.8734, -1.5798, -5.0000, -3.5000, -3.5000, -3.4000, -4.0000, -7.2312] - ) - m_d2 = np.array( - [0.6297, -0.5081, 1.5218, 0.0016, -0.1554, -0.1078, 0.0250, 0.4050] - ) - m_d3 = np.array( - [0.9738, -1.7812, 3.9229, 1.1477, 1.4062, -1.0750, 0.3844, 13.3500] - ) - m_d4 = np.array( - [0.2809, 0.1080, -2.6204, 0.1062, 0.3988, 1.5702, 0.2656, 0.6234] - ) - m_e1 = np.array( - [0.0356, 0.2624, -0.0156, 0.4659, 0.0032, -0.0672, 1.0468, 1.5000] - ) - m_e2 = np.array( - [-0.1246, 0.0672, 0.1597, -0.3296, 0.0766, 0.4016, -0.3788, -0.6426] - ) - m_e3 = np.array( - [-0.5718, -0.2190, 0.4199, -0.0876, -0.0656, 0.3017, -2.4517, 1.8564] - ) - m_e4 = np.array( - [0.9938, -0.4285, -0.5562, -0.0329, -0.1294, -0.4844, 1.4656, 0.5636] - ) - - acoeff = np.transpose(np.atleast_2d([m_a1, m_a2, m_a3, m_a4])) - bcoeff = np.transpose(np.atleast_2d([m_b1, m_b2, m_b3, m_b4])) - ccoeff = np.transpose(np.atleast_2d([m_c1, m_c2, m_c3, m_c4])) - dcoeff = np.transpose(np.atleast_2d([m_d1, m_d2, m_d3, m_d4])) - ecoeff = np.transpose(np.atleast_2d([m_e1, m_e2, m_e3, m_e4])) - - deg2rad = np.pi / 180 - rad2deg = 180 / np.pi - altitude = 90 - zen - zen = zen * deg2rad - azimuth = azimuth * deg2rad - altitude = altitude * deg2rad - Idh = radD - # Ibh = radI/sin(altitude) - Ibn = radI - - # Skyclearness - PerezClearness = ((Idh + Ibn) / (Idh + 1.041 * np.power(zen, 3))) / ( - 1 + 1.041 * np.power(zen, 3) - ) - # Extra terrestrial radiation - day_angle = jday * 2 * np.pi / 365 - # I0=1367*(1+0.033*np.cos((2*np.pi*jday)/365)) - I0 = 1367 * ( - 1.00011 - + 0.034221 * np.cos(day_angle) - + 0.00128 * np.sin(day_angle) - + 0.000719 * np.cos(2 * day_angle) - + 0.000077 * np.sin(2 * day_angle) - ) # New from robinsson - - # Optical air mass - # m=1/altitude; old - if altitude >= 10 * deg2rad: - AirMass = 1 / np.sin(altitude) - elif altitude < 0: # below equation becomes complex - AirMass = 1 / np.sin(altitude) + 0.50572 * np.power( - 180 * complex(altitude) / np.pi + 6.07995, -1.6364 - ) - else: - AirMass = 1 / np.sin(altitude) + 0.50572 * np.power( - 180 * altitude / np.pi + 6.07995, -1.6364 - ) - - # Skybrightness - # if altitude*rad2deg+6.07995>=0 - PerezBrightness = (AirMass * radD) / I0 - if Idh <= 10: - # m_a=0;m_b=0;m_c=0;m_d=0;m_e=0; - PerezBrightness = 0 - if altitude < 0: - print("Airmass") - print(AirMass) - print(PerezBrightness) - - # sky clearness bins - if PerezClearness < 1.065: - intClearness = 0 - if PerezClearness > 1.065 and PerezClearness < 1.230: - intClearness = 1 - if PerezClearness > 1.230 and PerezClearness < 1.500: - intClearness = 2 - if PerezClearness > 1.500 and PerezClearness < 1.950: - intClearness = 3 - if PerezClearness > 1.950 and PerezClearness < 2.800: - intClearness = 4 - if PerezClearness > 2.800 and PerezClearness < 4.500: - intClearness = 5 - if PerezClearness > 4.500 and PerezClearness < 6.200: - intClearness = 6 - if PerezClearness > 6.200: - intClearness = 7 - - m_a = ( - acoeff[intClearness, 0] - + acoeff[intClearness, 1] * zen - + PerezBrightness - * (acoeff[intClearness, 2] + acoeff[intClearness, 3] * zen) - ) - m_b = ( - bcoeff[intClearness, 0] - + bcoeff[intClearness, 1] * zen - + PerezBrightness - * (bcoeff[intClearness, 2] + bcoeff[intClearness, 3] * zen) - ) - m_e = ( - ecoeff[intClearness, 0] - + ecoeff[intClearness, 1] * zen - + PerezBrightness - * (ecoeff[intClearness, 2] + ecoeff[intClearness, 3] * zen) - ) - - if intClearness > 0: - m_c = ( - ccoeff[intClearness, 0] - + ccoeff[intClearness, 1] * zen - + PerezBrightness - * (ccoeff[intClearness, 2] + ccoeff[intClearness, 3] * zen) - ) - m_d = ( - dcoeff[intClearness, 0] - + dcoeff[intClearness, 1] * zen - + PerezBrightness - * (dcoeff[intClearness, 2] + dcoeff[intClearness, 3] * zen) - ) - else: - # different equations for c & d in clearness bin no. 1, from Robinsson - m_c = ( - np.exp( - np.power( - PerezBrightness - * ( - ccoeff[intClearness, 0] + ccoeff[intClearness, 1] * zen - ), - ccoeff[intClearness, 2], - ) - ) - - 1 - ) - m_d = ( - -np.exp( - PerezBrightness - * (dcoeff[intClearness, 0] + dcoeff[intClearness, 1] * zen) - ) - + dcoeff[intClearness, 2] - + PerezBrightness * dcoeff[intClearness, 3] * PerezBrightness - ) - - skyvaultalt = np.atleast_2d([]) - skyvaultazi = np.atleast_2d([]) - if patchchoice == 2: - # Creating skyvault at one degree intervals - skyvaultalt = np.ones([90, 361]) * 90 - skyvaultazi = np.empty((90, 361)) - for j in range(90): - skyvaultalt[j, :] = 91 - j - skyvaultazi[j, :] = list(range(361)) - - elif patchchoice == 1: - # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) - skyvaultaltint = [6, 18, 30, 42, 54, 66, 78] - skyvaultaziint = [12, 12, 15, 15, 20, 30, 60] - for j in range(7): - for k in range(1, int(360 / skyvaultaziint[j]) + 1): - skyvaultalt = np.append(skyvaultalt, skyvaultaltint[j]) - skyvaultazi = np.append(skyvaultazi, k * skyvaultaziint[j]) - - skyvaultalt = np.append(skyvaultalt, 90) - skyvaultazi = np.append(skyvaultazi, 360) - - skyvaultzen = (90 - skyvaultalt) * deg2rad - skyvaultalt = skyvaultalt * deg2rad - skyvaultazi = skyvaultazi * deg2rad - - # Angular distance from the sun from Robinsson - cosSkySunAngle = np.sin(skyvaultalt) * np.sin(altitude) + np.cos( - altitude - ) * np.cos(skyvaultalt) * np.cos(np.abs(skyvaultazi - azimuth)) - - # Main equation - lv = (1 + m_a * np.exp(m_b / np.cos(skyvaultzen))) * ( - ( - 1 - + m_c * np.exp(m_d * np.arccos(cosSkySunAngle)) - + m_e * cosSkySunAngle * cosSkySunAngle - ) - ) - - # Normalisation - lv = lv / np.sum(lv) - - # plotting - # axesm('stereo','Origin',[90 180],'MapLatLimit',[0 90],'Aspect','transverse') - # framem off; gridm on; mlabel off; plabel off;axis on; - # setm(gca,'MLabelParallel',-20) - # geoshow(skyvaultalt*rad2deg,skyvaultazi*rad2deg,lv,'DisplayType','texture'); - # colorbar - # set(gcf,'Color',[1 1 1]) - # pause(1) - - if patchchoice == 1: - # x = np.atleast_2d([]) - # lv = np.transpose(np.append(np.append(np.append(x, skyvaultalt*rad2deg), skyvaultazi*rad2deg), lv)) - x = np.transpose(np.atleast_2d(skyvaultalt * rad2deg)) - y = np.transpose(np.atleast_2d(skyvaultazi * rad2deg)) - z = np.transpose(np.atleast_2d(lv)) - lv = np.append(np.append(x, y, axis=1), z, axis=1) - return lv, PerezClearness, PerezBrightness diff --git a/functions/SEBEfiles/SEBE_2015a_calc_forprocessing.py b/functions/SEBEfiles/SEBE_2015a_calc_forprocessing.py index 93073cd..9b57e25 100644 --- a/functions/SEBEfiles/SEBE_2015a_calc_forprocessing.py +++ b/functions/SEBEfiles/SEBE_2015a_calc_forprocessing.py @@ -1,7 +1,6 @@ from builtins import range import numpy as np -import linecache -import sys +import torch from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_13 import ( shadowingfunction_wallheight_13, ) @@ -30,18 +29,19 @@ def SEBE_2015a_calc( usevegdem, feedback, wallmaxheight, + device ): # Parameters - deg2rad = np.pi / 180 - Knight = np.zeros((sizex, sizey)) - Energyyearroof = np.copy(Knight) + deg2rad = torch.pi / 180 + Knight = torch.zeros((sizex, sizey), device=device) + Energyyearroof = torch.clone(Knight) if usevegdem == 1: # amaxvalue vegmax = vegdem.max() amaxvalue = a.max() - a.min() - amaxvalue = np.maximum(amaxvalue, vegmax) + amaxvalue = torch.maximum(amaxvalue, vegmax) # Elevation vegdsms if buildingDEM includes ground heights vegdem = vegdem + a @@ -50,33 +50,33 @@ def SEBE_2015a_calc( vegdem2[vegdem2 == a] = 0 # % Bush separation - bush = np.logical_not((vegdem2 * vegdem)) * vegdem + bush = torch.logical_not((vegdem2 * vegdem)) * vegdem else: psi = 1 # Creating wallmatrix (1 meter interval) - wallcol, wallrow = np.where( - np.transpose(walls) > 0 + wallcol, wallrow = torch.where( + torch.transpose(walls) > 0 ) # row and col for each wall pixel - wallstot = np.floor(walls * (1 / voxelheight)) * voxelheight - # wallsections = np.floor(np.max(walls) * (1 / voxelheight)) # finding tallest wall - wallsections = np.floor(wallmaxheight * (1 / voxelheight)) - # feedback.setProgressText('np.max(walls):' + str(np.max(walls))) + wallstot = torch.floor(walls * (1 / voxelheight)) * voxelheight + # wallsections = torch.floor(torch.max(walls) * (1 / voxelheight)) # finding tallest wall + wallsections = torch.floor(wallmaxheight * (1 / voxelheight)) + # feedback.setProgressText('torch.max(walls):' + str(torch.max(walls))) # feedback.setProgressText('1 / voxelheight:' + str(1 / voxelheight)) # feedback.setProgressText('voxel:' + str(voxelheight)) # feedback.setProgressText('wallsections:' + str(wallsections)) - # feedback.setProgressText('np.shape(wallrow)[0]:' + str(np.shape(wallrow)[0])) - wallmatrix = np.zeros((np.shape(wallrow)[0], int(wallsections))) - Energyyearwall = np.copy(wallmatrix) + # feedback.setProgressText('torch.shape(wallrow)[0]:' + str(torch.shape(wallrow)[0])) + wallmatrix = torch.zeros((torch.shape(wallrow)[0], int(wallsections)), device=device) + Energyyearwall = torch.clone(wallmatrix) # Main loop - Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) - skyvaultaltint = np.array([6, 18, 30, 42, 54, 66, 78, 90]) - aziinterval = np.array([30, 30, 24, 24, 18, 12, 6, 1]) + skyvaultaltint = torch.tensor([6, 18, 30, 42, 54, 66, 78, 90], device=device) + aziinterval = torch.tensor([30, 30, 24, 24, 18, 12, 6, 1], device=device) if usevegdem == 1: - wallshve = np.zeros(np.shape(a)) - vegrow, vegcol = np.where(vegdem > 0) # row and col for each veg pixel - vegdata = np.zeros((np.shape(vegrow)[0], 3)) + wallshve = torch.zeros(torch.shape(a), device=device) + vegrow, vegcol = torch.where(vegdem > 0) # row and col for each veg pixel + vegdata = torch.zeros((torch.shape(vegrow)[0], 3), device=device) for i in range(0, vegrow.shape[0] - 1): vegdata[i, 0] = vegrow[i] + 1 vegdata[i, 1] = vegcol[i] + 1 @@ -96,22 +96,22 @@ def SEBE_2015a_calc( #################### SOLAR RADIATION POSITIONS ################### # Solar Incidence angle (Roofs) - suniroof = np.sin(slope) * np.cos( + suniroof = torch.sin(slope) * torch.cos( radmatI[index, 0] * deg2rad - ) * np.cos((radmatI[index, 1] * deg2rad) - aspect) + np.cos( + ) * torch.cos((radmatI[index, 1] * deg2rad) - aspect) + torch.cos( slope - ) * np.sin( + ) * torch.sin( (radmatI[index, 0] * deg2rad) ) suniroof[suniroof < 0] = 0 # Solar Incidence angle (Walls) - suniwall = np.abs( - np.sin(np.pi / 2) - * np.cos(radmatI[index, 0] * deg2rad) - * np.cos((radmatI[index, 1] * deg2rad) - dirwalls * deg2rad) - + np.cos(np.pi / 2) * np.sin((radmatI[index, 0] * deg2rad)) + suniwall = torch.abs( + torch.sin(torch.pi / 2) + * torch.cos(radmatI[index, 0] * deg2rad) + * torch.cos((radmatI[index, 1] * deg2rad) - dirwalls * deg2rad) + + torch.cos(torch.pi / 2) * torch.sin((radmatI[index, 0] * deg2rad)) ) # Shadow image @@ -130,7 +130,7 @@ def SEBE_2015a_calc( dirwalls * deg2rad, ) ) - shadow = np.copy(sh - (1.0 - vegsh) * (1.0 - psi)) + shadow = torch.clone(sh - (1.0 - vegsh) * (1.0 - psi)) else: sh, wallsh, wallsun, facesh, facesun = ( shadowingfunction_wallheight_13( @@ -142,41 +142,41 @@ def SEBE_2015a_calc( dirwalls * deg2rad, ) ) - shadow = np.copy(sh) + shadow = torch.clone(sh) # roof irradiance calculation # direct radiation if radmatI[index, 2] > 0: I = shadow * radmatI[index, 2] * suniroof else: - I = np.copy(Knight) + I = torch.clone(Knight) # roof diffuse and reflected radiation D = radmatD[index, 2] * shadow R = radmatR[index, 2] * (shadow * -1 + 1) - Energyyearroof = np.copy(Energyyearroof + D + R + I) + Energyyearroof = torch.clone(Energyyearroof + D + R + I) # WALL IRRADIANCE # direct radiation if radmatI[index, 2] > 0: Iw = radmatI[index, 2] * suniwall # wall else: - Iw = np.copy(Knight) + Iw = torch.clone(Knight) # wall diffuse and reflected radiation Dw = radmatD[index, 2] * facesun Rw = radmatR[index, 2] * facesun # for each wall level (voxelheight interval) - wallsun = np.floor(wallsun * (1 / voxelheight)) * voxelheight - wallsh = np.floor(wallsh * (1 / voxelheight)) * voxelheight + wallsun = torch.floor(wallsun * (1 / voxelheight)) * voxelheight + wallsh = torch.floor(wallsh * (1 / voxelheight)) * voxelheight if usevegdem == 1: - wallshve = np.floor(wallshve * (1 / voxelheight)) * voxelheight + wallshve = torch.floor(wallshve * (1 / voxelheight)) * voxelheight wallmatrix = wallmatrix * 0 - for p in range(np.shape(wallmatrix)[0]): + for p in range(torch.shape(wallmatrix)[0]): if wallsun[wallrow[p], wallcol[p]] > 0: # Sections in sun if ( wallsun[int(wallrow[p]), int(wallcol[p])] @@ -236,7 +236,7 @@ def SEBE_2015a_calc( 0 : int(wallsh[wallrow[p], wallcol[p]] / voxelheight), ] = Rw[wallrow[p], wallcol[p]] - Energyyearwall = Energyyearwall + np.copy(wallmatrix) + Energyyearwall = Energyyearwall + torch.clone(wallmatrix) index = index + 1 @@ -244,13 +244,13 @@ def SEBE_2015a_calc( # fix_print_with_import wallmatrixbol = (Energyyearwall > 0).astype(float) Energyyearwall = ( - Energyyearwall + (np.sum(radmatR[:, 2]) * albedo) / 2 + Energyyearwall + (torch.sum(radmatR[:, 2]) * albedo) / 2 ) * wallmatrixbol Energyyearroof /= 1000 Energyyearwall /= 1000 - Energyyearwall = np.transpose( - np.vstack((wallrow + 1, wallcol + 1, np.transpose(Energyyearwall))) + Energyyearwall = torch.transpose( + torch.vstack((wallrow + 1, wallcol + 1, torch.transpose(Energyyearwall))) ) # adding 1 to wallrow and wallcol so that the tests pass seberesult = { diff --git a/functions/SEBEfiles/importdata.py b/functions/SEBEfiles/importdata.py index 1e67a01..0b6bd39 100644 --- a/functions/SEBEfiles/importdata.py +++ b/functions/SEBEfiles/importdata.py @@ -47,9 +47,10 @@ import os from PIL import Image import numpy as np +import torch -def importdata(*args): +def importdata(*args, device): """ Imports data. @@ -135,7 +136,7 @@ def importdata(*args): delimiter = None headerRows = 0 img = Image.open(fileName) - output["cdata"] = np.array(img) + output["cdata"] = torch.tensor(img, device=device) output["colormap"] = ( img.mode ) # TODO: check if this method is euaivalent @@ -196,9 +197,9 @@ def print_usage(): raise IOError("Insufficient/bad arguments for importdata()") -def importdata_ascii(fileName, delimiter, headerRows): +def importdata_ascii(fileName, delimiter, headerRows, device): output = dict() - output["data"] = np.array([]) + output["data"] = torch.tensor([], device=device) output["textdata"] = list() # Read file into string and count the number of header rows @@ -254,7 +255,7 @@ def importdata_ascii(fileName, delimiter, headerRows): # print "headerRows py:", headerRows # Go through the data and put it in either output.data or output.textdata depending on if it is numeric or not. output["data"] = ( - np.empty((len(fileContentRows) - headerRows, dataColumns)) * np.nan + torch.from_numpy(np.empty((len(fileContentRows) - headerRows, dataColumns)), device=device) * torch.nan ) for i, line in enumerate(fileContentRows[headerRows:]): # Only use the row if it contains anything other than white-space characters. diff --git a/functions/SEBEfiles/sunmapcreator_2015a.py b/functions/SEBEfiles/sunmapcreator_2015a.py index e64cad8..6b93a6a 100644 --- a/functions/SEBEfiles/sunmapcreator_2015a.py +++ b/functions/SEBEfiles/sunmapcreator_2015a.py @@ -1,7 +1,7 @@ from __future__ import division from __future__ import absolute_import from builtins import range -import numpy as np +import torch from ...util.SEBESOLWEIGCommonFiles.diffusefraction import diffusefraction from ...util.SEBESOLWEIGCommonFiles.Perez_v3 import Perez_v3 from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import ( @@ -11,7 +11,7 @@ def sunmapcreator_2015a( - met, altitude, azimuth, onlyglobal, output, jday, albedo, location, zen + met, altitude, azimuth, onlyglobal, output, jday, albedo, location, zen, device ): """ % This function creates a sun map based on hourly values of solar radiation. @@ -25,14 +25,10 @@ def sunmapcreator_2015a( :param albedo: :return: """ - np.seterr(over="raise") - np.seterr(invalid="raise") + # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) patch_option = 1 # 145 patches - # patch_option = 2 # 153 patches - # patch_option = 3 # 306 patches - # patch_option = 4 # 612 patches ( skyvaultalt, skyvaultazi, @@ -41,45 +37,26 @@ def sunmapcreator_2015a( aziinterval, skyvaultaziint, azistart, - ) = create_patches(patch_option) - - # skyvaultaltint = np.array([6, 18, 30, 42, 54, 66, 78, 90]) - # skyvaultaziint = np.array([12, 12, 15, 15, 20, 30, 60, 360]) - # aziinterval = np.array([30, 30, 24, 24, 18, 12, 6, 1]) - # azistart = np.array([0, 4, 2, 5, 8, 0, 10, 0]) - # annulino = np.array([0, 12, 24, 36, 48, 60, 72, 84, 90]) - # skyvaultazi = np.array([]) - # for j in range(8): - # for k in range(1, int(360/skyvaultaziint[j]) + 1): - # # skyvaultalt(index)=skyvaultaltint(j); - # skyvaultazi = np.append(skyvaultazi, k*skyvaultaziint[j] + azistart[j]) - # if skyvaultazi[-1] > 360: - # skyvaultazi[-1] = skyvaultazi[-1] - 360 - # index = index + 1 - - iangle2 = np.array([]) - Gyear = 0 - Dyear = 0 - Gmonth = np.zeros([1, 12]) - Dmonth = Gmonth + ) = create_patches(patch_option, device) + + iangle2 = torch.tensor([]) + for j in range(len(aziinterval)): - iangle2 = np.append( - iangle2, skyvaultaltint[j] * np.ones([1, aziinterval[j]]) + iangle2 = torch.append( + iangle2, skyvaultaltint[j] * torch.ones([1, aziinterval[j]], device=device) ) - radmatI = np.transpose( - np.vstack((iangle2, skyvaultazi, np.zeros((13, len(iangle2))))) + radmatI = torch.transpose( + torch.vstack((iangle2, skyvaultazi, torch.zeros((13, len(iangle2)), device=device))) ) - radmatD = np.transpose( - np.vstack((iangle2, skyvaultazi, np.zeros((13, len(iangle2))))) + radmatD = torch.transpose( + torch.vstack((iangle2, skyvaultazi, torch.zeros((13, len(iangle2)), device=device))) ) - radmatR = np.transpose( - np.vstack((iangle2, skyvaultazi, np.zeros((13, len(iangle2))))) + radmatR = torch.transpose( + torch.vstack((iangle2, skyvaultazi, torch.zeros((13, len(iangle2)), device=device))) ) iazimuth = skyvaultazi - # Ta = met[:, 11] - # RH = met[:, 10] # Main loop for i in range(len(met[:, 0])): @@ -92,8 +69,8 @@ def sunmapcreator_2015a( if ( met[i, 11] <= -999.00 or met[i, 10] <= -999.00 - or np.isnan(met[i, 11]) - or np.isnan(met[i, 10]) + or torch.isnan(met[i, 11]) + or torch.isnan(met[i, 10]) ): met[i, 11] = 15.0 met[i, 10] = 75.0 @@ -127,18 +104,16 @@ def sunmapcreator_2015a( patch_option, ) - distalt = np.abs(iangle2 - alt) - altlevel = distalt == (np.min(np.abs(distalt))) - distazi = np.abs(iazimuth - azi) - azipos = distazi[altlevel] == (np.min(distazi[altlevel])) - azipos2 = np.where(altlevel)[0][0] + np.where(azipos)[0][0] - # azipos2 = np.where(altlevel)[0] + np.where(azipos)[0] - # azipos2 = find(altlevel, 1)-1 + find(azipos, 1) + distalt = torch.abs(iangle2 - alt) + altlevel = distalt == (torch.min(torch.abs(distalt))) + distazi = torch.abs(iazimuth - azi) + azipos = distazi[altlevel] == (torch.min(distazi[altlevel])) + azipos2 = torch.where(altlevel)[0][0] + torch.where(azipos)[0][0] + radmatI[azipos2, 2] = radmatI[azipos2, 2] + I radmatD[:, 2] = radmatD[:, 2] + D * lv[:, 2] radmatR[:, 2] = radmatR[:, 2] + G * (1 / 145) * albedo - # Gyear=Gyear+(G*sin(altitude(i)*(pi/180))); - # Dyear=Dyear+D; + if output["energymonth"] == 1: radmatI[azipos2, met[i, 1] + 2] = ( @@ -150,51 +125,14 @@ def sunmapcreator_2015a( radmatR[:, met[i, 1] + 2] = ( radmatR[:, met[i, 1] + 2] + G * (1 / 145) * albedo ) - # Gmonth(met(i,2))=Gmonth(met(i,2))+(G*sin(altitude(i)*(pi/180))); - # Dmonth(met(i,2))=Dmonth(met(i,2))+D; - # plot(distazi),hold on - # plot(distalt,'r') - # plot(azipos2,15,'r*'),hold off - # pause(0.1); # Adjusting the numbers if multiple years is used - if np.shape(met)[0] > 8760: - multiyear = np.shape(met)[0] / 8760 + if torch.shape(met)[0] > 8760: + multiyear = torch.shape(met)[0] / 8760 radmatI[:, 2:15] = radmatI[:, 2:15] / multiyear radmatD[:, 2:15] = radmatD[:, 2:15] / multiyear radmatR[:, 2:15] = radmatR[:, 2:15] / multiyear - # Gyear=Gyear/multiyear; - # Dyear=Dyear/multiyear; - # Gmonth=Gmonth/multiyear; - # Dmonth=Gmonth/multiyear; - # # Plotting - # maxrad=max(radmat(:,3)); - # for i=1:length(radmat(:,1)) - # if radmat(i,3)>0 - # pp(radmat(i,2)*(pi/180),(radmat(i,1)*-1)+90,[0 90],'LineStyle','none','Marker','h',... - # 'LineColor',[(1-(radmat(i,3)/maxrad)) (1-(radmat(i,3)/maxrad)) (1-(radmat(i,3)/maxrad))],'NumRings',3) - # hold on - # end - # end - # set(gca,'YDir','reverse','FontSize',8,'View',[270 90],'XTickLabel','') - - ## Old skyvault - # noa=19;# No. of anglesteps minus 1 - # step=89/noa; - # iangle=[((step/2):step:89) 90]; - # [iazimuth aziinterval]=svf_angles_100121(iangle); - # iangle2=[]; - # Gyear=0; - # Dyear=0; - # Gmonth=zeros(1,12); - # Dmonth=Gmonth; - # for j=1:length(aziinterval) - # iangle2=[iangle2 iangle(j)*ones(1,aziinterval(j))]; - # end - # - # radmat=[iangle2;iazimuth;zeros(13,length(iangle2))]'; - return radmatI, radmatD, radmatR diff --git a/functions/SOLWEIGpython/Kside_veg_v2022a.py b/functions/SOLWEIGpython/Kside_veg_v2022a.py index 15ebcd0..11e379c 100644 --- a/functions/SOLWEIGpython/Kside_veg_v2022a.py +++ b/functions/SOLWEIGpython/Kside_veg_v2022a.py @@ -42,18 +42,21 @@ def Kside_veg_v2022a( device = ( altitude.device if isinstance(altitude, torch.Tensor) - else azimuth.device - if isinstance(azimuth, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "cpu") + else ( + azimuth.device + if isinstance(azimuth, torch.Tensor) + else torch.device("cuda" if torch.cuda.is_available() else "cpu") + ) ) - # New reflection equation 2012-05-25 vikttot = 4.4897 aziE = azimuth + t aziS = azimuth - 90 + t aziW = azimuth - 180 + t aziN = azimuth - 270 + t - deg2rad = torch.pi / 180 + deg2rad = torch.pi / 180.0 + deg2rad = torch.tensor(deg2rad) + KsideD = torch.zeros((rows, cols), device=device) Kref_sun = torch.zeros((rows, cols), device=device) Kref_sh = torch.zeros((rows, cols), device=device) @@ -84,69 +87,60 @@ def Kside_veg_v2022a( diffRadW = torch.zeros((rows, cols), device=device) diffRadN = torch.zeros((rows, cols), device=device) - ### Direct radiation ### - if cyl == 1: ### Kside with cylinder ### + if cyl == 1: KsideI = shadow * radI * torch.cos(altitude * deg2rad) - KeastI = 0 - KsouthI = 0 - KwestI = 0 - KnorthI = 0 - else: ### Kside with weights ### - if azimuth > (360 - t) or azimuth <= (180 - t): - KeastI = ( - radI - * shadow - * torch.cos(altitude * deg2rad) - * torch.sin(aziE * deg2rad) - ) - else: - KeastI = 0 - if azimuth > (90 - t) and azimuth <= (270 - t): - KsouthI = ( - radI - * shadow - * torch.cos(altitude * deg2rad) - * torch.sin(aziS * deg2rad) - ) - else: - KsouthI = 0 - if azimuth > (180 - t) and azimuth <= (360 - t): - KwestI = ( - radI - * shadow - * torch.cos(altitude * deg2rad) - * torch.sin(aziW * deg2rad) - ) - else: - KwestI = 0 - if azimuth <= (90 - t) or azimuth > (270 - t): - KnorthI = ( - radI - * shadow - * torch.cos(altitude * deg2rad) - * torch.sin(aziN * deg2rad) - ) - else: - KnorthI = 0 - + KeastI = torch.zeros((rows, cols), device=device) + KsouthI = torch.zeros((rows, cols), device=device) + KwestI = torch.zeros((rows, cols), device=device) + KnorthI = torch.zeros((rows, cols), device=device) + else: + KeastI = torch.where( + (azimuth > (360 - t)) | (azimuth <= (180 - t)), + radI + * shadow + * torch.cos(altitude * deg2rad) + * torch.sin(aziE * deg2rad), + torch.zeros((rows, cols), device=device), + ) + KsouthI = torch.where( + (azimuth > (90 - t)) & (azimuth <= (270 - t)), + radI + * shadow + * torch.cos(altitude * deg2rad) + * torch.sin(aziS * deg2rad), + torch.zeros((rows, cols), device=device), + ) + KwestI = torch.where( + (azimuth > (180 - t)) & (azimuth <= (360 - t)), + radI + * shadow + * torch.cos(altitude * deg2rad) + * torch.sin(aziW * deg2rad), + torch.zeros((rows, cols), device=device), + ) + KnorthI = torch.where( + (azimuth <= (90 - t)) | (azimuth > (270 - t)), + radI + * shadow + * torch.cos(altitude * deg2rad) + * torch.sin(aziN * deg2rad), + torch.zeros((rows, cols), device=device), + ) KsideI = shadow * 0 - ### Diffuse and reflected radiation ### - [viktveg, viktwall] = Kvikt_veg(svfE, svfEveg, vikttot) - svfviktbuvegE = viktwall + (viktveg) * (1 - psi) + viktveg, viktwall = Kvikt_veg(svfE, svfEveg, vikttot) + svfviktbuvegE = viktwall + (viktveg * (1 - psi)) - [viktveg, viktwall] = Kvikt_veg(svfS, svfSveg, vikttot) - svfviktbuvegS = viktwall + (viktveg) * (1 - psi) + viktveg, viktwall = Kvikt_veg(svfS, svfSveg, vikttot) + svfviktbuvegS = viktwall + (viktveg * (1 - psi)) - [viktveg, viktwall] = Kvikt_veg(svfW, svfWveg, vikttot) - svfviktbuvegW = viktwall + (viktveg) * (1 - psi) + viktveg, viktwall = Kvikt_veg(svfW, svfWveg, vikttot) + svfviktbuvegW = viktwall + (viktveg * (1 - psi)) - [viktveg, viktwall] = Kvikt_veg(svfN, svfNveg, vikttot) - svfviktbuvegN = viktwall + (viktveg) * (1 - psi) + viktveg, viktwall = Kvikt_veg(svfN, svfNveg, vikttot) + svfviktbuvegN = viktwall + (viktveg * (1 - psi)) - ### Anisotropic Diffuse Radiation after Perez et al. 1993 ### if anisotropic_diffuse == 1: - anisotropic_sky = True patch_altitude = lv[:, 0] @@ -154,26 +148,26 @@ def Kside_veg_v2022a( if anisotropic_sky: patch_luminance = lv[:, 2] else: - patch_luminance = torch.zeros((patch_altitude.shape[0]), device=device) - patch_luminance[:] = 1.0 / patch_luminance.shape[0] + patch_luminance = ( + torch.ones((patch_altitude.shape[0]), device=device) + / patch_altitude.shape[0] + ) - # Unique altitudes for patches skyalt, skyalt_c = torch.unique(patch_altitude, return_counts=True) - radTot = torch.zeros(1, device=device) - - # Calculation of steradian for each patch steradian = torch.zeros((patch_altitude.shape[0]), device=device) for i in range(patch_altitude.shape[0]): - # If there are more than one patch in a band if skyalt_c[skyalt == patch_altitude[i]] > 1: steradian[i] = ( (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad ) * ( - torch.sin((patch_altitude[i] + patch_altitude[0]) * deg2rad) - - torch.sin((patch_altitude[i] - patch_altitude[0]) * deg2rad) + torch.sin( + (patch_altitude[i] + patch_altitude[0]) * deg2rad + ) + - torch.sin( + (patch_altitude[i] - patch_altitude[0]) * deg2rad + ) ) - # If there is only one patch in band, i.e. 90 degrees else: steradian[i] = ( (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad @@ -188,33 +182,25 @@ def Kside_veg_v2022a( patch_luminance[i] * steradian[i] * torch.sin(patch_altitude[i] * deg2rad) - ) # Radiance fraction normalization + ) - lumChi = ( - patch_luminance * radD - ) / radTot # Radiance fraction normalization + lumChi = (patch_luminance * radD) / radTot if cyl == 1: for idx in range(patch_azimuth.shape[0]): - # Angle of incidence, torch.cos(0) because cylinder - always perpendicular - anglIncC = torch.cos(patch_altitude[idx] * deg2rad) * torch.cos( - 0 - ) # * torch.sin(torch.pi / 2) \ - # + torch.sin(patch_altitude[idx] * deg2rad) * torch.cos(torch.pi / 2) - # Diffuse vertical radiation + anglIncC = torch.cos( + patch_altitude[idx] * deg2rad + ) * torch.cos(torch.tensor(0.0)) KsideD += ( diffsh[:, :, idx] * lumChi[idx] * anglIncC * steradian[idx] ) - # Shortwave reflected on sunlit surfaces - # sunlit_surface = ((albedo * radG) / torch.pi) sunlit_surface = ( - albedo * (radI * torch.cos(altitude * deg2rad)) + (radD * 0.5) + albedo * (radI * torch.cos(altitude * deg2rad)) + + (radD * 0.5) ) / torch.pi - # Shortwave reflected on shaded surfaces and vegetation shaded_surface = (albedo * radD * 0.5) / torch.pi - # Shortwave radiation reflected on vegetation - based on diffuse shortwave radiation temp_vegsh = (vegshmat[:, :, idx] == 0) | ( vbshvegshmat[:, :, idx] == 0 ) @@ -225,18 +211,15 @@ def Kside_veg_v2022a( * torch.cos(patch_altitude[idx] * deg2rad) ) - # Shortwave radiation reflected on buildings (shaded and sunlit) - based on global and diffuse shortwave radiation temp_vbsh = (1 - shmat[:, :, idx]) * vbshvegshmat[:, :, idx] - temp_sh = temp_vbsh == 1 # & (vbshvegshmat[:,:,idx] == 1) - - sunlit_patches, shaded_patches = ( - sunlit_shaded_patches.shaded_or_sunlit( - altitude, - azimuth, - patch_altitude[idx], - patch_azimuth[idx], - asvf, - ) + temp_sh = temp_vbsh == 1 + + sunlit_patches, shaded_patches = shaded_or_sunlit( + altitude, + azimuth, + patch_altitude[idx], + patch_azimuth[idx], + asvf, ) Kref_sun += ( sunlit_surface @@ -260,74 +243,58 @@ def Kside_veg_v2022a( Knorth = KupN * 0.5 Ksouth = KupS * 0.5 - # Keast = (albedo * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) + KupE) * 0.5 - # Ksouth = (albedo * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) + KupS) * 0.5 - # Kwest = (albedo * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + KupW) * 0.5 - # Knorth = (albedo * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + KupN) * 0.5 - else: # Box - diffRadE = torch.zeros((rows, cols), device=device) - diffRadS = torch.zeros((rows, cols), device=device) - diffRadW = torch.zeros((rows, cols), device=device) - diffRadN = torch.zeros((rows, cols), device=device) - + else: for idx in range(patch_azimuth.shape[0]): if (patch_azimuth[idx] > 360) or (patch_azimuth[idx] <= 180): - anglIncE = torch.cos(patch_altitude[idx] * deg2rad) * torch.cos( - (90 - patch_azimuth[idx] + t) * deg2rad - ) # * torch.sin(torch.pi / 2) \ - # + torch.sin(patch_altitude[idx] * deg2rad) * torch.cos(torch.pi / 2) + anglIncE = torch.cos( + patch_altitude[idx] * deg2rad + ) * torch.cos((90 - patch_azimuth[idx] + t) * deg2rad) diffRadE += ( diffsh[:, :, idx] * lumChi[idx] * anglIncE * steradian[idx] - ) # * 0.5 + ) if (patch_azimuth[idx] > 90) and (patch_azimuth[idx] <= 270): - anglIncS = torch.cos(patch_altitude[idx] * deg2rad) * torch.cos( - (180 - patch_azimuth[idx] + t) * deg2rad - ) # * torch.sin(torch.pi / 2) \ - # + torch.sin(patch_altitude[idx] * deg2rad) * torch.cos(torch.pi / 2) + anglIncS = torch.cos( + patch_altitude[idx] * deg2rad + ) * torch.cos((180 - patch_azimuth[idx] + t) * deg2rad) diffRadS += ( diffsh[:, :, idx] * lumChi[idx] * anglIncS * steradian[idx] - ) # * 0.5 + ) if (patch_azimuth[idx] > 180) and (patch_azimuth[idx] <= 360): - anglIncW = torch.cos(patch_altitude[idx] * deg2rad) * torch.cos( - (270 - patch_azimuth[idx] + t) * deg2rad - ) # * torch.sin(torch.pi / 2) \ - # + torch.sin(patch_altitude[idx] * deg2rad) * torch.cos(torch.pi / 2) + anglIncW = torch.cos( + patch_altitude[idx] * deg2rad + ) * torch.cos((270 - patch_azimuth[idx] + t) * deg2rad) diffRadW += ( diffsh[:, :, idx] * lumChi[idx] * anglIncW * steradian[idx] - ) # * 0.5 + ) if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] <= 90): - anglIncN = torch.cos(patch_altitude[idx] * deg2rad) * torch.cos( - (0 - patch_azimuth[idx] + t) * deg2rad - ) # * torch.sin(torch.pi / 2) \ - # + torch.sin(patch_altitude[idx] * deg2rad) * torch.cos(torch.pi / 2) + anglIncN = torch.cos( + patch_altitude[idx] * deg2rad + ) * torch.cos((0 - patch_azimuth[idx] + t) * deg2rad) diffRadN += ( diffsh[:, :, idx] * lumChi[idx] * anglIncN * steradian[idx] - ) # * 0.5 + ) - # Shortwave reflected on sunlit surfaces - # sunlit_surface = ((albedo * radG) / torch.pi) sunlit_surface = ( - albedo * (radI * torch.cos(altitude * deg2rad)) + (radD * 0.5) + albedo * (radI * torch.cos(altitude * deg2rad)) + + (radD * 0.5) ) / torch.pi - # Shortwave reflected on shaded surfaces and vegetation shaded_surface = (albedo * radD * 0.5) / torch.pi - # Shortwave radiation reflected on vegetation - based on diffuse shortwave radiation temp_vegsh = (vegshmat[:, :, idx] == 0) | ( vbshvegshmat[:, :, idx] == 0 ) @@ -371,20 +338,17 @@ def Kside_veg_v2022a( * torch.cos((0 - patch_azimuth[idx] + t) * deg2rad) ) - # Shortwave radiation reflected on buildings (shaded and sunlit) - based on global and diffuse shortwave radiation temp_vbsh = (1 - shmat[:, :, idx]) * vbshvegshmat[:, :, idx] - temp_sh = temp_vbsh == 1 # & (vbshvegshmat[:,:,idx] == 1) + temp_sh = temp_vbsh == 1 azimuth_difference = torch.abs(azimuth - patch_azimuth[idx]) if (azimuth_difference > 90) and (azimuth_difference < 270): - sunlit_patches, shaded_patches = ( - sunlit_shaded_patches.shaded_or_sunlit( - altitude, - azimuth, - patch_altitude[idx], - patch_azimuth[idx], - asvf, - ) + sunlit_patches, shaded_patches = shaded_or_sunlit( + altitude, + azimuth, + patch_altitude[idx], + patch_azimuth[idx], + asvf, ) Kref_sun += ( sunlit_surface @@ -410,7 +374,9 @@ def Kside_veg_v2022a( * steradian[idx] * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos((90 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos( + (90 - patch_azimuth[idx] + t) * deg2rad + ) ) Kref_sh_e += ( shaded_surface @@ -418,7 +384,9 @@ def Kside_veg_v2022a( * steradian[idx] * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos((90 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos( + (90 - patch_azimuth[idx] + t) * deg2rad + ) ) if (patch_azimuth[idx] > 90) and ( patch_azimuth[idx] < 270 @@ -429,7 +397,9 @@ def Kside_veg_v2022a( * steradian[idx] * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos((180 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos( + (180 - patch_azimuth[idx] + t) * deg2rad + ) ) Kref_sh_s += ( shaded_surface @@ -437,7 +407,9 @@ def Kside_veg_v2022a( * steradian[idx] * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos((180 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos( + (180 - patch_azimuth[idx] + t) * deg2rad + ) ) if (patch_azimuth[idx] > 180) and ( patch_azimuth[idx] < 360 @@ -448,7 +420,9 @@ def Kside_veg_v2022a( * steradian[idx] * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos((270 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos( + (270 - patch_azimuth[idx] + t) * deg2rad + ) ) Kref_sh_w += ( shaded_surface @@ -456,7 +430,9 @@ def Kside_veg_v2022a( * steradian[idx] * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos((270 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos( + (270 - patch_azimuth[idx] + t) * deg2rad + ) ) if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] < 90): Kref_sun_n += ( @@ -491,7 +467,9 @@ def Kside_veg_v2022a( * steradian[idx] * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos((90 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos( + (90 - patch_azimuth[idx] + t) * deg2rad + ) ) if (patch_azimuth[idx] > 90) and ( patch_azimuth[idx] < 270 @@ -501,7 +479,9 @@ def Kside_veg_v2022a( * steradian[idx] * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos((180 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos( + (180 - patch_azimuth[idx] + t) * deg2rad + ) ) if (patch_azimuth[idx] > 180) and ( patch_azimuth[idx] < 360 @@ -511,7 +491,9 @@ def Kside_veg_v2022a( * steradian[idx] * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos((270 - patch_azimuth[idx] + t) * deg2rad) + * torch.cos( + (270 - patch_azimuth[idx] + t) * deg2rad + ) ) if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] < 90): Kref_sh_n += ( @@ -585,4 +567,3 @@ def Kside_veg_v2022a( Knorth = KnorthI + KnorthDG return Keast, Ksouth, Kwest, Knorth, KsideI, KsideD, Kside - # return Keast, Ksouth, Kwest, Knorth, KsideI, KsideD, Kref_sh, Kref_sun, Kref_veg, Kside diff --git a/functions/SOLWEIGpython/Kup_veg_2015a.py b/functions/SOLWEIGpython/Kup_veg_2015a.py index ed55af3..2b1b9b8 100644 --- a/functions/SOLWEIGpython/Kup_veg_2015a.py +++ b/functions/SOLWEIGpython/Kup_veg_2015a.py @@ -1,6 +1,7 @@ import numpy as np import torch + def Kup_veg_2015a( radI, radD, diff --git a/functions/SOLWEIGpython/Lside_veg.py b/functions/SOLWEIGpython/Lside_veg.py index a250b82..b54d1a3 100644 --- a/functions/SOLWEIGpython/Lside_veg.py +++ b/functions/SOLWEIGpython/Lside_veg.py @@ -3,6 +3,7 @@ from .Lvikt_veg import Lvikt_veg import torch + def Lside_veg_v2022a( svfS, svfW, @@ -38,9 +39,11 @@ def Lside_veg_v2022a( device = ( Ldown.device if isinstance(Ldown, torch.Tensor) - else Ta.device - if isinstance(Ta, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "cpu") + else ( + Ta.device + if isinstance(Ta, torch.Tensor) + else torch.device("cuda" if torch.cuda.is_available() else "cpu") + ) ) # Convert all SVF inputs to the same device @@ -90,7 +93,10 @@ def Lside_veg_v2022a( Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * torch.sin(aziE * (torch.pi / 180))) ** 4) + * ( + (Ta + 273.15 + Tw * torch.sin(aziE * (torch.pi / 180))) + ** 4 + ) * viktwall * (1 - F_sh) * torch.cos(betasun) @@ -133,7 +139,10 @@ def Lside_veg_v2022a( Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * torch.sin(aziS * (torch.pi / 180))) ** 4) + * ( + (Ta + 273.15 + Tw * torch.sin(aziS * (torch.pi / 180))) + ** 4 + ) * viktwall * (1 - F_sh) * torch.cos(betasun) @@ -176,7 +185,10 @@ def Lside_veg_v2022a( Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * torch.sin(aziW * (torch.pi / 180))) ** 4) + * ( + (Ta + 273.15 + Tw * torch.sin(aziW * (torch.pi / 180))) + ** 4 + ) * viktwall * (1 - F_sh) * torch.cos(betasun) @@ -219,7 +231,10 @@ def Lside_veg_v2022a( Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * torch.sin(aziN * (torch.pi / 180))) ** 4) + * ( + (Ta + 273.15 + Tw * torch.sin(aziN * (torch.pi / 180))) + ** 4 + ) * viktwall * (1 - F_sh) * torch.cos(betasun) @@ -282,9 +297,11 @@ def Lside_veg_v2026( device = ( Ldown.device if isinstance(Ldown, torch.Tensor) - else Ta.device - if isinstance(Ta, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "cpu") + else ( + Ta.device + if isinstance(Ta, torch.Tensor) + else torch.device("cuda" if torch.cuda.is_available() else "cpu") + ) ) # Convert all SVF inputs to the same device @@ -342,7 +359,10 @@ def Lside_veg_v2026( Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * torch.sin(aziE * (torch.pi / 180))) ** 4) + * ( + (Ta + 273.15 + Tw * torch.sin(aziE * (torch.pi / 180))) + ** 4 + ) * viktwall * (1 - F_sh) * torch.cos(betasun) @@ -379,7 +399,10 @@ def Lside_veg_v2026( Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * torch.sin(aziS * (torch.pi / 180))) ** 4) + * ( + (Ta + 273.15 + Tw * torch.sin(aziS * (torch.pi / 180))) + ** 4 + ) * viktwall * (1 - F_sh) * torch.cos(betasun) @@ -416,7 +439,10 @@ def Lside_veg_v2026( Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * torch.sin(aziW * (torch.pi / 180))) ** 4) + * ( + (Ta + 273.15 + Tw * torch.sin(aziW * (torch.pi / 180))) + ** 4 + ) * viktwall * (1 - F_sh) * torch.cos(betasun) @@ -453,7 +479,10 @@ def Lside_veg_v2026( Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * torch.sin(aziN * (torch.pi / 180))) ** 4) + * ( + (Ta + 273.15 + Tw * torch.sin(aziN * (torch.pi / 180))) + ** 4 + ) * viktwall * (1 - F_sh) * torch.cos(betasun) diff --git a/functions/SOLWEIGpython/Lside_veg_v2015a.py b/functions/SOLWEIGpython/Lside_veg_v2015a.py index 86baa47..7723f8d 100644 --- a/functions/SOLWEIGpython/Lside_veg_v2015a.py +++ b/functions/SOLWEIGpython/Lside_veg_v2015a.py @@ -37,9 +37,11 @@ def Lside_veg_v2015a( device = ( Ldown.device if isinstance(Ldown, torch.Tensor) - else Ta.device - if isinstance(Ta, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "cpu") + else ( + Ta.device + if isinstance(Ta, torch.Tensor) + else torch.device("cuda" if torch.cuda.is_available() else "cpu") + ) ) # This m-file is the current one that estimates L from the four cardinal points 20100414 @@ -76,7 +78,10 @@ def Lside_veg_v2015a( Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * torch.sin(aziE * (torch.pi / 180))) ** 4) + * ( + (Ta + 273.15 + Tw * torch.sin(aziE * (torch.pi / 180))) + ** 4 + ) * viktwall * (1 - F_sh) * torch.cos(betasun) @@ -114,7 +119,10 @@ def Lside_veg_v2015a( Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * torch.sin(aziS * (torch.pi / 180))) ** 4) + * ( + (Ta + 273.15 + Tw * torch.sin(aziS * (torch.pi / 180))) + ** 4 + ) * viktwall * (1 - F_sh) * torch.cos(betasun) @@ -152,7 +160,10 @@ def Lside_veg_v2015a( Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * torch.sin(aziW * (torch.pi / 180))) ** 4) + * ( + (Ta + 273.15 + Tw * torch.sin(aziW * (torch.pi / 180))) + ** 4 + ) * viktwall * (1 - F_sh) * torch.cos(betasun) @@ -190,7 +201,10 @@ def Lside_veg_v2015a( Lwallsun = ( SBC * ewall - * ((Ta + 273.15 + Tw * torch.sin(aziN * (torch.pi / 180))) ** 4) + * ( + (Ta + 273.15 + Tw * torch.sin(aziN * (torch.pi / 180))) + ** 4 + ) * viktwall * (1 - F_sh) * torch.cos(betasun) diff --git a/functions/SOLWEIGpython/Lside_veg_v2022a.py b/functions/SOLWEIGpython/Lside_veg_v2022a.py deleted file mode 100644 index 836b692..0000000 --- a/functions/SOLWEIGpython/Lside_veg_v2022a.py +++ /dev/null @@ -1,230 +0,0 @@ -from __future__ import absolute_import -import numpy as np -from .Lvikt_veg import Lvikt_veg - - -def Lside_veg_v2022a( - svfS, - svfW, - svfN, - svfE, - svfEveg, - svfSveg, - svfWveg, - svfNveg, - svfEaveg, - svfSaveg, - svfWaveg, - svfNaveg, - azimuth, - altitude, - Ta, - Tw, - SBC, - ewall, - Ldown, - esky, - t, - F_sh, - CI, - LupE, - LupS, - LupW, - LupN, - anisotropic_longwave, -): - - # This m-file is the current one that estimates L from the four cardinal points 20100414 - - # Building height angle from svf - svfalfaE = np.arcsin(np.exp((np.log(1 - svfE)) / 2)) - svfalfaS = np.arcsin(np.exp((np.log(1 - svfS)) / 2)) - svfalfaW = np.arcsin(np.exp((np.log(1 - svfW)) / 2)) - svfalfaN = np.arcsin(np.exp((np.log(1 - svfN)) / 2)) - - vikttot = 4.4897 - aziW = azimuth + t - aziN = azimuth - 90 + t - aziE = azimuth - 180 + t - aziS = azimuth - 270 + t - - F_sh = 2 * F_sh - 1 # (cylindric_wedge scaled 0-1) - - c = 1 - CI - Lsky_allsky = esky * SBC * ((Ta + 273.15) ** 4) * (1 - c) + c * SBC * ( - (Ta + 273.15) ** 4 - ) - - ## Least - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( - svfE, svfEveg, svfEaveg, vikttot - ) - - if altitude > 0: # daytime - alfaB = np.arctan(svfalfaE) - betaB = np.arctan(np.tan((svfalfaE) * F_sh)) - betasun = ((alfaB - betaB) / 2) + betaB - # betasun = np.arctan(0.5*np.tan(svfalfaE)*(1+F_sh)) #TODO This should be considered in future versions - if (azimuth > (180 - t)) and (azimuth <= (360 - t)): - Lwallsun = ( - SBC - * ewall - * ((Ta + 273.15 + Tw * np.sin(aziE * (np.pi / 180))) ** 4) - * viktwall - * (1 - F_sh) - * np.cos(betasun) - * 0.5 - ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) - else: - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - else: # nighttime - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - - # Longwave from ground (see Lcyl_v2022a for remaining fluxes) - if anisotropic_longwave == 1: - Lground = LupE * 0.5 - Least = Lground - else: - Lsky = ((svfE + svfEveg - 1) * Lsky_allsky) * viktsky * 0.5 - Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 - Lground = LupE * 0.5 - Lrefl = (Ldown + LupE) * (viktrefl) * (1 - ewall) * 0.5 - Least = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl - - # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky - - ## Lsouth - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( - svfS, svfSveg, svfSaveg, vikttot - ) - - if altitude > 0: # daytime - alfaB = np.arctan(svfalfaS) - betaB = np.arctan(np.tan((svfalfaS) * F_sh)) - betasun = ((alfaB - betaB) / 2) + betaB - # betasun = np.arctan(0.5*np.tan(svfalfaS)*(1+F_sh)) - if (azimuth <= (90 - t)) or (azimuth > (270 - t)): - Lwallsun = ( - SBC - * ewall - * ((Ta + 273.15 + Tw * np.sin(aziS * (np.pi / 180))) ** 4) - * viktwall - * (1 - F_sh) - * np.cos(betasun) - * 0.5 - ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) - else: - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - else: # nighttime - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - - # Longwave from ground (see Lcyl_v2022a for remaining fluxes) - if anisotropic_longwave == 1: - Lground = LupS * 0.5 - Lsouth = Lground - else: - Lsky = ((svfS + svfSveg - 1) * Lsky_allsky) * viktsky * 0.5 - Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 - Lground = LupS * 0.5 - Lrefl = (Ldown + LupS) * (viktrefl) * (1 - ewall) * 0.5 - Lsouth = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl - - # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky - - ## Lwest - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( - svfW, svfWveg, svfWaveg, vikttot - ) - - if altitude > 0: # daytime - alfaB = np.arctan(svfalfaW) - betaB = np.arctan(np.tan((svfalfaW) * F_sh)) - betasun = ((alfaB - betaB) / 2) + betaB - # betasun = np.arctan(0.5*np.tan(svfalfaW)*(1+F_sh)) - if (azimuth > (360 - t)) or (azimuth <= (180 - t)): - Lwallsun = ( - SBC - * ewall - * ((Ta + 273.15 + Tw * np.sin(aziW * (np.pi / 180))) ** 4) - * viktwall - * (1 - F_sh) - * np.cos(betasun) - * 0.5 - ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) - else: - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - else: # nighttime - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - - # Longwave from ground (see Lcyl_v2022a for remaining fluxes) - if anisotropic_longwave == 1: - Lground = LupW * 0.5 - Lwest = Lground - else: - Lsky = ((svfW + svfWveg - 1) * Lsky_allsky) * viktsky * 0.5 - Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 - Lground = LupW * 0.5 - Lrefl = (Ldown + LupW) * (viktrefl) * (1 - ewall) * 0.5 - Lwest = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl - - # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky - - ## Lnorth - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( - svfN, svfNveg, svfNaveg, vikttot - ) - - if altitude > 0: # daytime - alfaB = np.arctan(svfalfaN) - betaB = np.arctan(np.tan((svfalfaN) * F_sh)) - betasun = ((alfaB - betaB) / 2) + betaB - # betasun = np.arctan(0.5*np.tan(svfalfaN)*(1+F_sh)) - if (azimuth > (90 - t)) and (azimuth <= (270 - t)): - Lwallsun = ( - SBC - * ewall - * ((Ta + 273.15 + Tw * np.sin(aziN * (np.pi / 180))) ** 4) - * viktwall - * (1 - F_sh) - * np.cos(betasun) - * 0.5 - ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) - else: - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - else: # nighttime - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - - # Longwave from ground (see Lcyl_v2022a for remaining fluxes) - if anisotropic_longwave == 1: - Lground = LupN * 0.5 - Lnorth = Lground - else: - Lsky = ((svfN + svfNveg - 1) * Lsky_allsky) * viktsky * 0.5 - Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 - Lground = LupN * 0.5 - Lrefl = (Ldown + LupN) * (viktrefl) * (1 - ewall) * 0.5 - Lnorth = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl - - # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky - - return Least, Lsouth, Lwest, Lnorth diff --git a/functions/SOLWEIGpython/Lvikt_veg.py b/functions/SOLWEIGpython/Lvikt_veg.py index fe3cf73..5e7087a 100644 --- a/functions/SOLWEIGpython/Lvikt_veg.py +++ b/functions/SOLWEIGpython/Lvikt_veg.py @@ -1,5 +1,6 @@ import torch + def Lvikt_veg(svf, svfveg, svfaveg, vikttot): device = None if isinstance(svf, torch.Tensor): diff --git a/functions/SOLWEIGpython/Perez_v3_moved.py b/functions/SOLWEIGpython/Perez_v3_moved.py deleted file mode 100644 index abbb2c4..0000000 --- a/functions/SOLWEIGpython/Perez_v3_moved.py +++ /dev/null @@ -1,351 +0,0 @@ -from __future__ import division -import numpy as np - - -def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice): - """ - This function calculates distribution of luminance on the skyvault based on - Perez luminince distribution model. - - Created by: - Fredrik Lindberg 20120527, fredrikl@gvc.gu.se - Gothenburg University, Sweden - Urban Climte Group - - Input parameters: - - zen: Zenith angle of the Sun (in degrees) - - azimuth: Azimuth angle of the Sun (in degrees) - - radD: Horizontal diffuse radiation (W m-2) - - radI: Direct radiation perpendicular to the Sun beam (W m-2) - - jday: Day of year - - Output parameters: - - lv: Relative luminance map (same dimensions as theta. gamma) - - - acoeff=[1.353 -0.258 -0.269 -1.437 - -1.222 -0.773 1.415 1.102 - -1.100 -0.252 0.895 0.016 - -0.585 -0.665 -0.267 0.712 - -0.600 -0.347 -2.500 2.323 - -1.016 -0.367 1.008 1.405 - -1.000 0.021 0.503 -0.512 - -1.050 0.029 0.426 0.359]; - - bcoeff=[-0.767 0.001 1.273 -0.123 - -0.205 0.037 -3.913 0.916 - 0.278 -0.181 -4.500 1.177 - 0.723 -0.622 -5.681 2.630 - 0.294 0.049 -5.681 1.842 - 0.288 -0.533 -3.850 3.375 - -0.300 0.192 0.702 -1.632 - -0.325 0.116 0.778 0.003]; - - ccoeff=[2.800 0.600 1.238 1.000 - 6.975 0.177 6.448 -0.124 - 24.22 -13.08 -37.70 34.84 - 33.34 -18.30 -62.25 52.08 - 21.00 -4.766 -21.59 7.249 - 14.00 -0.999 -7.14 7.547 - 19.00 -5.000 1.243 -1.91 - 31.06 -14.50 -46.11 55.37]; - - dcoeff=[1.874 0.630 0.974 0.281 - -1.580 -0.508 -1.781 0.108 - -5.00 1.522 3.923 -2.62 - -3.50 0.002 1.148 0.106 - -3.50 -0.155 1.406 0.399 - -3.40 -0.108 -1.075 1.57 - -4.00 0.025 0.384 0.266 - -7.23 0.405 13.35 0.623]; - - ecoeff=[0.035 -0.125 -0.572 0.994 - 0.262 0.067 -0.219 -0.428 - -0.016 0.160 0.420 -0.556 - 0.466 -0.33 -0.088 -0.033 - 0.003 0.077 -0.066 -0.129 - -0.067 0.402 0.302 -0.484 - 1.047 -0.379 -2.452 1.466 - 1.500 -0.643 1.856 0.564]; - - :param zen: - :param azimuth: - :param radD: - :param radI: - :param jday: - :param patchchoice: - :return: - """ - - m_a1 = np.array( - [1.3525, -1.2219, -1.1000, -0.5484, -0.6000, -1.0156, -1.0000, -1.0500] - ) - m_a2 = np.array( - [-0.2576, -0.7730, -0.2515, -0.6654, -0.3566, -0.3670, 0.0211, 0.0289] - ) - m_a3 = np.array( - [-0.2690, 1.4148, 0.8952, -0.2672, -2.5000, 1.0078, 0.5025, 0.4260] - ) - m_a4 = np.array( - [-1.4366, 1.1016, 0.0156, 0.7117, 2.3250, 1.4051, -0.5119, 0.3590] - ) - m_b1 = np.array( - [-0.7670, -0.2054, 0.2782, 0.7234, 0.2937, 0.2875, -0.3000, -0.3250] - ) - m_b2 = np.array( - [0.0007, 0.0367, -0.1812, -0.6219, 0.0496, -0.5328, 0.1922, 0.1156] - ) - m_b3 = np.array( - [1.2734, -3.9128, -4.5000, -5.6812, -5.6812, -3.8500, 0.7023, 0.7781] - ) - m_b4 = np.array( - [-0.1233, 0.9156, 1.1766, 2.6297, 1.8415, 3.3750, -1.6317, 0.0025] - ) - m_c1 = np.array( - [2.8000, 6.9750, 24.7219, 33.3389, 21.0000, 14.0000, 19.0000, 31.0625] - ) - m_c2 = np.array( - [ - 0.6004, - 0.1774, - -13.0812, - -18.3000, - -4.7656, - -0.9999, - -5.0000, - -14.5000, - ] - ) - m_c3 = np.array( - [ - 1.2375, - 6.4477, - -37.7000, - -62.2500, - -21.5906, - -7.1406, - 1.2438, - -46.1148, - ] - ) - m_c4 = np.array( - [1.0000, -0.1239, 34.8438, 52.0781, 7.2492, 7.5469, -1.9094, 55.3750] - ) - m_d1 = np.array( - [1.8734, -1.5798, -5.0000, -3.5000, -3.5000, -3.4000, -4.0000, -7.2312] - ) - m_d2 = np.array( - [0.6297, -0.5081, 1.5218, 0.0016, -0.1554, -0.1078, 0.0250, 0.4050] - ) - m_d3 = np.array( - [0.9738, -1.7812, 3.9229, 1.1477, 1.4062, -1.0750, 0.3844, 13.3500] - ) - m_d4 = np.array( - [0.2809, 0.1080, -2.6204, 0.1062, 0.3988, 1.5702, 0.2656, 0.6234] - ) - m_e1 = np.array( - [0.0356, 0.2624, -0.0156, 0.4659, 0.0032, -0.0672, 1.0468, 1.5000] - ) - m_e2 = np.array( - [-0.1246, 0.0672, 0.1597, -0.3296, 0.0766, 0.4016, -0.3788, -0.6426] - ) - m_e3 = np.array( - [-0.5718, -0.2190, 0.4199, -0.0876, -0.0656, 0.3017, -2.4517, 1.8564] - ) - m_e4 = np.array( - [0.9938, -0.4285, -0.5562, -0.0329, -0.1294, -0.4844, 1.4656, 0.5636] - ) - - acoeff = np.transpose(np.atleast_2d([m_a1, m_a2, m_a3, m_a4])) - bcoeff = np.transpose(np.atleast_2d([m_b1, m_b2, m_b3, m_b4])) - ccoeff = np.transpose(np.atleast_2d([m_c1, m_c2, m_c3, m_c4])) - dcoeff = np.transpose(np.atleast_2d([m_d1, m_d2, m_d3, m_d4])) - ecoeff = np.transpose(np.atleast_2d([m_e1, m_e2, m_e3, m_e4])) - - deg2rad = np.pi / 180 - rad2deg = 180 / np.pi - altitude = 90 - zen - zen = zen * deg2rad - azimuth = azimuth * deg2rad - altitude = altitude * deg2rad - Idh = radD - # Ibh = radI/sin(altitude) - Ibn = radI - - # Skyclearness - PerezClearness = ((Idh + Ibn) / (Idh + 1.041 * np.power(zen, 3))) / ( - 1 + 1.041 * np.power(zen, 3) - ) - # Extra terrestrial radiation - day_angle = jday * 2 * np.pi / 365 - # I0=1367*(1+0.033*np.cos((2*np.pi*jday)/365)) - I0 = 1367 * ( - 1.00011 - + 0.034221 * np.cos(day_angle) - + 0.00128 * np.sin(day_angle) - + 0.000719 * np.cos(2 * day_angle) - + 0.000077 * np.sin(2 * day_angle) - ) # New from robinsson - - # Optical air mass - # m=1/altitude; old - if altitude >= 10 * deg2rad: - AirMass = 1 / np.sin(altitude) - elif altitude < 0: # below equation becomes complex - AirMass = 1 / np.sin(altitude) + 0.50572 * np.power( - 180 * complex(altitude) / np.pi + 6.07995, -1.6364 - ) - else: - AirMass = 1 / np.sin(altitude) + 0.50572 * np.power( - 180 * altitude / np.pi + 6.07995, -1.6364 - ) - - # Skybrightness - # if altitude*rad2deg+6.07995>=0 - PerezBrightness = (AirMass * radD) / I0 - if Idh <= 10: - # m_a=0;m_b=0;m_c=0;m_d=0;m_e=0; - PerezBrightness = 0 - # if altitude < 0: - # print("Airmass") - # print(AirMass) - # print(PerezBrightness) - # sky clearness bins - if PerezClearness < 1.065: - intClearness = 0 - if PerezClearness > 1.065 and PerezClearness < 1.230: - intClearness = 1 - if PerezClearness > 1.230 and PerezClearness < 1.500: - intClearness = 2 - if PerezClearness > 1.500 and PerezClearness < 1.950: - intClearness = 3 - if PerezClearness > 1.950 and PerezClearness < 2.800: - intClearness = 4 - if PerezClearness > 2.800 and PerezClearness < 4.500: - intClearness = 5 - if PerezClearness > 4.500 and PerezClearness < 6.200: - intClearness = 6 - if PerezClearness > 6.200: - intClearness = 7 - - m_a = ( - acoeff[intClearness, 0] - + acoeff[intClearness, 1] * zen - + PerezBrightness - * (acoeff[intClearness, 2] + acoeff[intClearness, 3] * zen) - ) - m_b = ( - bcoeff[intClearness, 0] - + bcoeff[intClearness, 1] * zen - + PerezBrightness - * (bcoeff[intClearness, 2] + bcoeff[intClearness, 3] * zen) - ) - m_e = ( - ecoeff[intClearness, 0] - + ecoeff[intClearness, 1] * zen - + PerezBrightness - * (ecoeff[intClearness, 2] + ecoeff[intClearness, 3] * zen) - ) - - if intClearness > 0: - m_c = ( - ccoeff[intClearness, 0] - + ccoeff[intClearness, 1] * zen - + PerezBrightness - * (ccoeff[intClearness, 2] + ccoeff[intClearness, 3] * zen) - ) - m_d = ( - dcoeff[intClearness, 0] - + dcoeff[intClearness, 1] * zen - + PerezBrightness - * (dcoeff[intClearness, 2] + dcoeff[intClearness, 3] * zen) - ) - else: - # different equations for c & d in clearness bin no. 1, from Robinsson - m_c = ( - np.exp( - np.power( - PerezBrightness - * ( - ccoeff[intClearness, 0] + ccoeff[intClearness, 1] * zen - ), - ccoeff[intClearness, 2], - ) - ) - - 1 - ) - m_d = ( - -np.exp( - PerezBrightness - * (dcoeff[intClearness, 0] + dcoeff[intClearness, 1] * zen) - ) - + dcoeff[intClearness, 2] - + PerezBrightness * dcoeff[intClearness, 3] * PerezBrightness - ) - - # print 'a = ', m_a - # print 'b = ', m_b - # print 'e = ', m_e - # print 'c = ', m_c - # print 'd = ', m_d - - skyvaultalt = np.atleast_2d([]) - skyvaultazi = np.atleast_2d([]) - if patchchoice == 2: - # Creating skyvault at one degree intervals - skyvaultalt = np.ones([90, 361]) * 90 - skyvaultazi = np.empty((90, 361)) - for j in range(90): - skyvaultalt[j, :] = 91 - j - skyvaultazi[j, :] = range(361) - - elif patchchoice == 1: - # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) - skyvaultaltint = [6, 18, 30, 42, 54, 66, 78] - skyvaultaziint = [12, 12, 15, 15, 20, 30, 60] - for j in range(7): - for k in range(1, int(360 / skyvaultaziint[j]) + 1): - skyvaultalt = np.append(skyvaultalt, skyvaultaltint[j]) - skyvaultazi = np.append(skyvaultazi, k * skyvaultaziint[j]) - - skyvaultalt = np.append(skyvaultalt, 90) - skyvaultazi = np.append(skyvaultazi, 360) - - skyvaultzen = (90 - skyvaultalt) * deg2rad - skyvaultalt = skyvaultalt * deg2rad - skyvaultazi = skyvaultazi * deg2rad - - # Angular distance from the sun from Robinsson - cosSkySunAngle = np.sin(skyvaultalt) * np.sin(altitude) + np.cos( - altitude - ) * np.cos(skyvaultalt) * np.cos(np.abs(skyvaultazi - azimuth)) - - # Main equation - lv = (1 + m_a * np.exp(m_b / np.cos(skyvaultzen))) * ( - ( - 1 - + m_c * np.exp(m_d * np.arccos(cosSkySunAngle)) - + m_e * cosSkySunAngle * cosSkySunAngle - ) - ) - - # Normalisation - lv = lv / np.sum(lv) - - # plotting - # axesm('stereo','Origin',[90 180],'MapLatLimit',[0 90],'Aspect','transverse') - # framem off; gridm on; mlabel off; plabel off;axis on; - # setm(gca,'MLabelParallel',-20) - # geoshow(skyvaultalt*rad2deg,skyvaultazi*rad2deg,lv,'DisplayType','texture'); - # colorbar - # set(gcf,'Color',[1 1 1]) - # pause(1) - - if patchchoice == 1: - # x = np.atleast_2d([]) - # lv = np.transpose(np.append(np.append(np.append(x, skyvaultalt*rad2deg), skyvaultazi*rad2deg), lv)) - x = np.transpose(np.atleast_2d(skyvaultalt * rad2deg)) - y = np.transpose(np.atleast_2d(skyvaultazi * rad2deg)) - z = np.transpose(np.atleast_2d(lv)) - lv = np.append(np.append(x, y, axis=1), z, axis=1) - return lv, PerezClearness, PerezBrightness diff --git a/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py b/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py index adcecaf..de979e0 100644 --- a/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py +++ b/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py @@ -245,7 +245,14 @@ def Solweig_2025a_calc( zenDeg = zen * (180 / np.pi) # Relative luminance lv, pc_, pb_ = Perez_v3( - zenDeg, azimuth, radD, radI, jday, patchchoice, patch_option + zenDeg, + azimuth, + radD, + radI, + jday, + patchchoice, + patch_option, + "cpu", ) # Total relative luminance from sky, i.e. from each patch, into each cell aniLum = np.zeros((rows, cols)) diff --git a/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py index 93280c2..4844b7d 100644 --- a/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py +++ b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py @@ -3,8 +3,6 @@ """ from __future__ import absolute_import -import numpy as np -import matplotlib.pyplot as plt from .daylen import daylen from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import ( clearnessindex_2013b, @@ -208,7 +206,6 @@ def Solweig_2026a_calc( TgOut1 = old Ts model diffsh, ani = Used in anisotrpic models (Wallenberg et al. 2019, 2022) """ - # # # Core program start # # # # Instrument offset in degrees @@ -222,9 +219,11 @@ def Solweig_2026a_calc( device = ( Tg.device if isinstance(Tg, torch.Tensor) - else Ta.device - if isinstance(Ta, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "cpu") + else ( + Ta.device + if isinstance(Ta, torch.Tensor) + else torch.device("cuda" if torch.cuda.is_available() else "cpu") + ) ) # Find sunrise decimal hour - new from 2014a @@ -265,7 +264,14 @@ def Solweig_2026a_calc( zenDeg = zen * (180 / torch.pi) # Relative luminance lv, pc_, pb_ = Perez_v3( - zenDeg, azimuth, radD, radI, jday, patchchoice, patch_option + zenDeg, + azimuth, + radD, + radI, + jday, + patchchoice, + patch_option, + device, ) # Total relative luminance from sky, i.e. from each patch, into each cell aniLum = torch.zeros((rows, cols), device=device) @@ -539,7 +545,7 @@ def Solweig_2026a_calc( gvfalbnoshW, gvfalbnoshN, ) - + Keast, Ksouth, Kwest, Knorth, KsideI, KsideD, Kside = Kside_veg_v2022a( radI, radD, @@ -775,10 +781,12 @@ def Solweig_2026a_calc( if "lv" not in locals(): # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) skyvaultalt, skyvaultazi, _, _, _, _, _ = create_patches( - patch_option + patch_option, device ) - patch_emissivities = torch.zeros(skyvaultalt.shape[0], device=device) + patch_emissivities = torch.zeros( + skyvaultalt.shape[0], device=device + ) x = torch.transpose(torch.atleast_2d(skyvaultalt)) y = torch.transpose(torch.atleast_2d(skyvaultazi)) diff --git a/functions/SOLWEIGpython/Solweig_run.py b/functions/SOLWEIGpython/Solweig_run.py index 023a473..7e9ebb5 100644 --- a/functions/SOLWEIGpython/Solweig_run.py +++ b/functions/SOLWEIGpython/Solweig_run.py @@ -36,7 +36,6 @@ from shutil import copyfile import torch - # imports from osgeo/qgis dependency try: from osgeo import gdal @@ -74,7 +73,6 @@ # from ...util.SEBESOLWEIGCommonFiles.create_patches import create_patches - def solweig_run(configPath, feedback): """ Input: @@ -82,15 +80,12 @@ def solweig_run(configPath, feedback): feedback : To communicate with qgis gui. Set to None if standalone """ - # Load config file configDict = read_solweig_config(configPath) # Load parameters settings for SOLWEIG with open(configDict["para_json_path"], "r") as jsn: param = json.load(jsn) - - # reading variables from config and parameters that is not yet presented cyl = int(configDict["cyl"]) @@ -100,11 +95,11 @@ def solweig_run(configPath, feedback): elvis = 0.0 absK = param["Tmrt_params"]["Value"]["absK"] absL = param["Tmrt_params"]["Value"]["absL"] - + # --- Load on CPU or GPU config device = torch.device("cpu") print("passe") - + if configDict["calculation_mode"] == "gpu" and torch.cuda.is_available(): device = torch.device("cuda") print("using gpu") @@ -162,22 +157,26 @@ def solweig_run(configPath, feedback): usevegdem = int(configDict["usevegdem"]) if usevegdem == 1: if standAlone == 0: - vegdsm = torch.from_numpy(( - gdal.Open(configDict["filepath_cdsm"]) - .ReadAsArray() - .astype(float) - )).to(device) + vegdsm = torch.from_numpy( + ( + gdal.Open(configDict["filepath_cdsm"]) + .ReadAsArray() + .astype(float) + ) + ).to(device) else: vegdsm, _, _ = common.load_raster( configDict["filepath_cdsm"], bbox=None ) if configDict["filepath_tdsm"] != "": if standAlone == 0: - vegdsm2 = torch.from_numpy(( - gdal.Open(configDict["filepath_tdsm"]) - .ReadAsArray() - .astype(float) - )).to(device) + vegdsm2 = torch.from_numpy( + ( + gdal.Open(configDict["filepath_tdsm"]) + .ReadAsArray() + .astype(float) + ) + ).to(device) else: vegdsm2, _, _ = common.load_raster( configDict["filepath_tdsm"], bbox=None @@ -192,11 +191,13 @@ def solweig_run(configPath, feedback): landcover = int(configDict["landcover"]) if landcover == 1: if standAlone == 0: - lcgrid =torch.from_numpy(( - gdal.Open(configDict["filepath_lc"]) - .ReadAsArray() - .astype(float) - )).to(device) + lcgrid = torch.from_numpy( + ( + gdal.Open(configDict["filepath_lc"]) + .ReadAsArray() + .astype(float) + ) + ).to(device) else: lcgrid, _, _ = common.load_raster( configDict["filepath_lc"], bbox=None @@ -211,7 +212,9 @@ def solweig_run(configPath, feedback): gdal_dem = gdal.Open( configDict["filepath_dem"] ) # .ReadAsArray().astype(float) - dem = torch.from_numpy(gdal_dem.ReadAsArray().astype(float)).to(device) + dem = torch.from_numpy(gdal_dem.ReadAsArray().astype(float)).to( + device + ) nd = gdal_dem.GetRasterBand(1).GetNoDataValue() else: dem, _, _ = common.load_raster( @@ -233,31 +236,41 @@ def solweig_run(configPath, feedback): zip.close() if standAlone == 0: - svf = torch.from_numpy(( - gdal.Open(configDict["working_dir"] + "/svf.tif") - .ReadAsArray() - .astype(float) - )).to(device) - svfN = torch.from_numpy(( - gdal.Open(configDict["working_dir"] + "/svfN.tif") - .ReadAsArray() - .astype(float) - )).to(device) - svfS = torch.from_numpy(( - gdal.Open(configDict["working_dir"] + "/svfS.tif") - .ReadAsArray() - .astype(float) - )).to(device) - svfE = torch.from_numpy(( - gdal.Open(configDict["working_dir"] + "/svfE.tif") - .ReadAsArray() - .astype(float) - )).to(device) - svfW = torch.from_numpy(( - gdal.Open(configDict["working_dir"] + "/svfW.tif") - .ReadAsArray() - .astype(float) - )).to(device) + svf = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svf.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfN = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfN.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfS = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfS.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfE = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfE.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfW = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfW.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) else: svf, _, _ = common.load_raster( configDict["working_dir"] + "/svf.tif", bbox=None @@ -277,57 +290,77 @@ def solweig_run(configPath, feedback): if usevegdem == 1: if standAlone == 0: - svfveg = torch.from_numpy(( - gdal.Open(configDict["working_dir"] + "/svfveg.tif") - .ReadAsArray() - .astype(float) - )).to(device) - svfNveg = torch.from_numpy(( - gdal.Open(configDict["working_dir"] + "/svfNveg.tif") - .ReadAsArray() - .astype(float) - )).to(device) - svfSveg = torch.from_numpy(( - gdal.Open(configDict["working_dir"] + "/svfSveg.tif") - .ReadAsArray() - .astype(float) - )).to(device) - svfEveg = torch.from_numpy(( - gdal.Open(configDict["working_dir"] + "/svfEveg.tif") - .ReadAsArray() - .astype(float) - )).to(device) - svfWveg = torch.from_numpy(( - gdal.Open(configDict["working_dir"] + "/svfWveg.tif") - .ReadAsArray() - .astype(float) - )).to(device) + svfveg = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfveg.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfNveg = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfNveg.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfSveg = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfSveg.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfEveg = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfEveg.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfWveg = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfWveg.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) - svfaveg = torch.from_numpy(( - gdal.Open(configDict["working_dir"] + "/svfaveg.tif") - .ReadAsArray() - .astype(float) - )).to(device) - svfNaveg = torch.from_numpy(( - gdal.Open(configDict["working_dir"] + "/svfNaveg.tif") - .ReadAsArray() - .astype(float) - )).to(device) - svfSaveg = torch.from_numpy(( - gdal.Open(configDict["working_dir"] + "/svfSaveg.tif") - .ReadAsArray() - .astype(float) - )).to(device) - svfEaveg = torch.from_numpy(( - gdal.Open(configDict["working_dir"] + "/svfEaveg.tif") - .ReadAsArray() - .astype(float) - )).to(device) - svfWaveg = torch.from_numpy(( - gdal.Open(configDict["working_dir"] + "/svfWaveg.tif") - .ReadAsArray() - .astype(float) - )).to(device) + svfaveg = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfaveg.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfNaveg = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfNaveg.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfSaveg = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfSaveg.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfEaveg = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfEaveg.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfWaveg = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfWaveg.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) else: svfveg, _, _ = common.load_raster( configDict["working_dir"] + "/svfveg.tif", bbox=None @@ -378,12 +411,12 @@ def solweig_run(configPath, feedback): svfalfa = torch.arcsin(torch.exp((torch.log((1.0 - tmp)) / 2.0))) if standAlone == 0: - wallheight = torch.from_numpy(( - gdal.Open(configDict["filepath_wh"]).ReadAsArray().astype(float) - )).to(device) - wallaspect = torch.from_numpy(( - gdal.Open(configDict["filepath_wa"]).ReadAsArray().astype(float) - )).to(device) + wallheight = torch.from_numpy( + (gdal.Open(configDict["filepath_wh"]).ReadAsArray().astype(float)) + ).to(device) + wallaspect = torch.from_numpy( + (gdal.Open(configDict["filepath_wa"]).ReadAsArray().astype(float)) + ).to(device) else: wallheight, _, _ = common.load_raster( configDict["filepath_wh"], bbox=None @@ -397,9 +430,11 @@ def solweig_run(configPath, feedback): delim = " " Twater = [] - metdata = torch.from_numpy(np.loadtxt( - configDict["input_met"], skiprows=headernum, delimiter=delim - )).to(device) + metdata = torch.from_numpy( + np.loadtxt( + configDict["input_met"], skiprows=headernum, delimiter=delim + ) + ).to(device) location = {"longitude": lon, "latitude": lat, "altitude": alt} YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax = ( @@ -474,7 +509,15 @@ def solweig_run(configPath, feedback): configDict["output_dir"] + "/POI_" + str(poiname[k]) + ".txt" ) np.savetxt( - data_out, poi_save.cpu().numpy() if isinstance(poi_save, torch.Tensor) else poi_save, delimiter=" ", header=header, comments="" + data_out, + ( + poi_save.cpu().numpy() + if isinstance(poi_save, torch.Tensor) + else poi_save + ), + delimiter=" ", + header=header, + comments="", ) # print(poisxy) # Num format for POI output @@ -495,13 +538,17 @@ def solweig_run(configPath, feedback): if param["Tmrt_params"]["Value"]["posture"] == "Standing": Fside = param["Posture"]["Standing"]["Value"]["Fside"] Fup = param["Posture"]["Standing"]["Value"]["Fup"] - height = torch.tensor(param["Posture"]["Standing"]["Value"]["height"]).to(device) + height = torch.tensor( + param["Posture"]["Standing"]["Value"]["height"] + ).to(device) Fcyl = param["Posture"]["Standing"]["Value"]["Fcyl"] pos = 1 else: Fside = param["Posture"]["Sitting"]["Value"]["Fside"] Fup = param["Posture"]["Sitting"]["Value"]["Fup"] - height = torch.tensor(param["Posture"]["Sitting"]["Value"]["height"]).to(device) + height = torch.tensor( + param["Posture"]["Sitting"]["Value"]["height"] + ).to(device) Fcyl = param["Posture"]["Sitting"]["Value"]["Fcyl"] pos = 0 @@ -901,8 +948,7 @@ def solweig_run(configPath, feedback): + pd.to_timedelta(hours[1].item(), unit="h") + pd.to_timedelta(minu[1].item(), unit="m") ) - - + timeStep = (second_timestep - first_timestep).seconds ( @@ -1153,7 +1199,15 @@ def solweig_run(configPath, feedback): ) # f_handle = file(data_out, 'a') f_handle = open(data_out, "ab") - np.savetxt(f_handle, poi_save.cpu().numpy() if isinstance(poi_save, torch.Tensor) else poi_save, fmt=numformat) + np.savetxt( + f_handle, + ( + poi_save.cpu().numpy() + if isinstance(poi_save, torch.Tensor) + else poi_save + ), + fmt=numformat, + ) f_handle.close() # If wall temperature parameterization scheme is in use @@ -1196,7 +1250,9 @@ def solweig_run(configPath, feedback): ).to(device) # temp_all = torch.concatenate([temp_wall]) # wall_data = torch.zeros((1, 7 + temp_wall.shape[0])) - wall_data = torch.zeros((1, 7 + temp_all.shape[0])).to(device) + wall_data = torch.zeros((1, 7 + temp_all.shape[0])).to( + device + ) # Part of file name (wallid), i.e. WOI_wallid.txt data_out = ( configDict["output_dir"] @@ -1241,7 +1297,15 @@ def solweig_run(configPath, feedback): ) # Open file, add data, save f_handle = open(data_out, "ab") - np.savetxt(f_handle, wall_data.cpu().numpy() if isinstance(wall_data, torch.Tensor) else wall_data, fmt=woi_numformat) + np.savetxt( + f_handle, + ( + wall_data.cpu().numpy() + if isinstance(wall_data, torch.Tensor) + else wall_data + ), + fmt=woi_numformat, + ) f_handle.close() # Save wall temperature/radiation as NetCDF TODO: fix for standAlone? @@ -1460,7 +1524,11 @@ def solweig_run(configPath, feedback): # print(settingsData) np.savetxt( configDict["output_dir"] + "/treeplantersettings.txt", - settingsData.cpu().numpy() if isinstance(settingsData, torch.Tensor) else settingsData, + ( + settingsData.cpu().numpy() + if isinstance(settingsData, torch.Tensor) + else settingsData + ), fmt=settingsFmt, header=settingsHeader, delimiter=" ", @@ -1476,7 +1544,9 @@ def solweig_run(configPath, feedback): ) # fix average Tmrt instead of sum, 20191022 if standAlone == 0: saveraster( - gdal_dsm, configDict["output_dir"] + "/Tmrt_average.tif", tmrtplot.detach().cpu().numpy() + gdal_dsm, + configDict["output_dir"] + "/Tmrt_average.tif", + tmrtplot.detach().cpu().numpy(), ) else: common.save_raster( diff --git a/functions/SOLWEIGpython/cylindric_wedge.py b/functions/SOLWEIGpython/cylindric_wedge.py index 1abd030..c876d88 100644 --- a/functions/SOLWEIGpython/cylindric_wedge.py +++ b/functions/SOLWEIGpython/cylindric_wedge.py @@ -1,17 +1,17 @@ -import numpy as np import torch def cylindric_wedge(zen, svfalfa, rows, cols): - # Fraction of sunlit walls based on sun altitude and svf wieghted building angles # input: # sun zenith angle "beta" # svf related angle "alfa" - device = svfalfa.device if isinstance(svfalfa, torch.Tensor) else torch.device( - "cuda" if torch.cuda.is_available() else "cpu" + device = ( + svfalfa.device + if isinstance(svfalfa, torch.Tensor) + else torch.device("cuda" if torch.cuda.is_available() else "cpu") ) beta = zen # alfa=svfalfa @@ -39,9 +39,9 @@ def cylindric_wedge(zen, svfalfa, rows, cols): A = torch.zeros((rows, cols), device=device) # A(length(svfalfa),length(svfalfa))=0; - A[xa < 0] = (torch.sin(phi[xa < 0]) - phi[xa < 0] * torch.cos(phi[xa < 0])) / ( - 1 - torch.cos(phi[xa < 0]) - ) + A[xa < 0] = ( + torch.sin(phi[xa < 0]) - phi[xa < 0] * torch.cos(phi[xa < 0]) + ) / (1 - torch.cos(phi[xa < 0])) ukil = torch.zeros((rows, cols), device=device) # ukil(length(svfalfa),length(svfalfa))=0 @@ -75,8 +75,10 @@ def cylindric_wedge_voxel(zen, svfalfa): ba = 1.0 / torch.tan(svfalfa) hkil = 2.0 * ba * ha - device = svfalfa.device if isinstance(svfalfa, torch.Tensor) else torch.device( - "cuda" if torch.cuda.is_available() else "cpu" + device = ( + svfalfa.device + if isinstance(svfalfa, torch.Tensor) + else torch.device("cuda" if torch.cuda.is_available() else "cpu") ) qa = torch.zeros((svfalfa.shape[0]), device=device) @@ -93,9 +95,9 @@ def cylindric_wedge_voxel(zen, svfalfa): A = torch.zeros((svfalfa.shape[0]), device=device) # A(length(svfalfa),length(svfalfa))=0; - A[xa < 0] = (torch.sin(phi[xa < 0]) - phi[xa < 0] * torch.cos(phi[xa < 0])) / ( - 1 - torch.cos(phi[xa < 0]) - ) + A[xa < 0] = ( + torch.sin(phi[xa < 0]) - phi[xa < 0] * torch.cos(phi[xa < 0]) + ) / (1 - torch.cos(phi[xa < 0])) ukil = torch.zeros((svfalfa.shape[0]), device=device) # ukil(length(svfalfa),length(svfalfa))=0 diff --git a/functions/SOLWEIGpython/daylen.py b/functions/SOLWEIGpython/daylen.py index 075bec2..6d23938 100644 --- a/functions/SOLWEIGpython/daylen.py +++ b/functions/SOLWEIGpython/daylen.py @@ -7,8 +7,10 @@ def daylen(DOY, XLAT): # Sun angles. SOC limited for latitudes above polar circles. # Calculate daylength, sunrise and sunset (Eqn. 17) - device = DOY.device if isinstance(DOY, torch.Tensor) else torch.device( - "cuda" if torch.cuda.is_available() else "cpu" + device = ( + DOY.device + if isinstance(DOY, torch.Tensor) + else torch.device("cuda" if torch.cuda.is_available() else "cpu") ) if not isinstance(XLAT, torch.Tensor): XLAT = torch.tensor(XLAT, device=device) diff --git a/functions/SOLWEIGpython/ground_surface.py b/functions/SOLWEIGpython/ground_surface.py index 92621c7..903259f 100644 --- a/functions/SOLWEIGpython/ground_surface.py +++ b/functions/SOLWEIGpython/ground_surface.py @@ -35,7 +35,9 @@ def saturated_vp(T): return slope, qs -def initiate_groundScheme(lc_grid, solweig_parameters, day, Ta, location, device): +def initiate_groundScheme( + lc_grid, solweig_parameters, day, Ta, location, device +): """ Setup the maps used in the ground scheme calculations depending on the landcover @@ -128,7 +130,9 @@ def initiate_groundScheme(lc_grid, solweig_parameters, day, Ta, location, device + 1 / ratio_Tg * torch.sin(2 * torch.pi / 365.25 * day + phi_Tg) - * torch.sign(torch.tensor(location["latitude"], device=device)) + * torch.sign( + torch.tensor(location["latitude"], device=device) + ) ) + 4 ) @@ -151,7 +155,9 @@ def initiate_groundScheme(lc_grid, solweig_parameters, day, Ta, location, device + 1 / ratio_Tg * torch.sin(2 * torch.pi / 365.25 * day + phi_Tg) - * torch.sign(torch.tensor(location["latitude"], device=device)) + * torch.sign( + torch.tensor(location["latitude"], device=device) + ) ) + 4 ) @@ -184,7 +190,9 @@ def initiate_groundScheme(lc_grid, solweig_parameters, day, Ta, location, device + 1 / ratio_Tg * torch.sin(2 * torch.pi / 365.25 * day + phi_Tg) - * torch.sign(torch.tensor(location["latitude"], device=device)) + * torch.sign( + torch.tensor(location["latitude"], device=device) + ) ) + 2 ) @@ -265,7 +273,6 @@ def surfaceTemperature_calc( :G: Ground heat flux for the current timestep """ - # Store the past ground surface temperature Tg_stored = Tg @@ -326,7 +333,12 @@ def surfaceTemperature_calc( lamb = 2.260e6 # Latent heat of vaporisation rho = 1000 # Density of water (kg.m-3) Rn_water = ( - Kdown * (1 - alb) * (beta + (1 - beta) * (1 - torch.exp(torch.tensor(-1, device=Tg.device)))) + Kdown + * (1 - alb) + * ( + beta + + (1 - beta) * (1 - torch.exp(torch.tensor(-1, device=Tg.device))) + ) + Ldown - Lup ) # Net radiation for the top water layer beta described the transmitted rad @@ -383,7 +395,9 @@ def outgoingLongwave_calc( # Assessment of the distance from a pixel at which most of the radiation are received (cf view factor Lambert) device = Tg.device - factor = torch.tensor(0.99, device=device) # Percentage of radiation accounted for + factor = torch.tensor( + 0.99, device=device + ) # Percentage of radiation accounted for zs = 1.1 # in m r_max = zs * torch.sqrt( factor / (1 - factor) @@ -488,9 +502,17 @@ def outgoingLongwave_calc( # Scale so that the grid is at least translated from 1px if abs(dx) > abs(dy): dx = -r * torch.sign(torch.cos(azimuth)) - dy = -r * abs(torch.tan(azimuth)) * torch.sign(torch.sin(azimuth)) + dy = ( + -r + * abs(torch.tan(azimuth)) + * torch.sign(torch.sin(azimuth)) + ) else: - dx = -r / abs(torch.tan(azimuth)) * torch.sign(torch.cos(azimuth)) + dx = ( + -r + / abs(torch.tan(azimuth)) + * torch.sign(torch.cos(azimuth)) + ) dy = -r * torch.sign(torch.sin(azimuth)) # Select the interested part of the initial raster and the translated one from their four corners and @@ -607,7 +629,9 @@ def outgoingLongwave_calc( dphi = torch.arctan((r + step) / zs) - torch.arctan(r / zs) dtrigo = zs / torch.sqrt(r**2 + zs**2) * r / torch.sqrt( r**2 + zs**2 - ) - zs / torch.sqrt((r + step) ** 2 + zs**2) * (r + step) / torch.sqrt( + ) - zs / torch.sqrt((r + step) ** 2 + zs**2) * ( + r + step + ) / torch.sqrt( (r + step) ** 2 + zs**2 ) diff --git a/functions/URock/Obstacles.py b/functions/URock/Obstacles.py index 8464078..613d072 100644 --- a/functions/URock/Obstacles.py +++ b/functions/URock/Obstacles.py @@ -226,7 +226,7 @@ def createsBlocks( ON a.{ID_FIELD_BLOCK}=b.{ID_FIELD_BLOCK} WHERE a.{HEIGHT_FIELD}>={height_i} GROUP BY a.{ID_FIELD_BLOCK})') WHERE ST_ISEMPTY({GEOM_FIELD}) IS FALSE - """# nosec B608 + """ # nosec B608 for height_i in df_listOfHeight ] # nosec B608 cursor.execute(f""" diff --git a/functions/dailyshading.py b/functions/dailyshading.py index 50f1c18..24ae0c0 100644 --- a/functions/dailyshading.py +++ b/functions/dailyshading.py @@ -192,7 +192,12 @@ def dailyshading( else: if usevegdem == 0: sh = shadow.shadowingfunctionglobalradiation( - dsm, azi[i], alt[i], scale, feedback, 0, + dsm, + azi[i], + alt[i], + scale, + feedback, + 0, ) # shtot = shtot + sh else: diff --git a/functions/svf_for_voxels.py b/functions/svf_for_voxels.py index 3b7f9cf..b8a69e6 100644 --- a/functions/svf_for_voxels.py +++ b/functions/svf_for_voxels.py @@ -1,4 +1,5 @@ import torch + try: from sklearn.cluster import KMeans except: @@ -12,6 +13,8 @@ def _to_tensor(x, device, dtype=torch.float32): if x is None: return None return torch.tensor(x, dtype=dtype, device=device) + + from ..functions import svf_functions as svf from ..functions import wallalgorithms as wa @@ -20,86 +23,101 @@ def wallscheme_prepare( dsm, scale, pixel_resolution, feedback, device=torch.device("cpu") ): dsm = _to_tensor(dsm, device) - total = 100.0 / (int(dsm.shape[0] * dsm.shape[1])) + + # Existing UMEP wall and aspect calculations walls = wa.findwalls_sp( - dsm, 2, torch.tensor([[1, 1, 1], [1, 0, 1], [1, 1, 1]], device=dsm.device) + dsm, + 2, + torch.tensor([[1, 1, 1], [1, 0, 1], [1, 1, 1]], device=dsm.device), ) walls_copy = torch.clone(walls) aspect = wa.filter1Goodwin_as_aspect_v3( walls_copy, scale, dsm, feedback, 100 ) - # Copy to keep exact height values walls_exact = walls.clone() - - # Rounding wall heights (ceil or round?) - # walls_round = torch.round(walls).astype(int) walls_round = torch.ceil(walls).int() - # create wall IDs + # 1. Vectorized identification of wall pixels wall_rows, wall_cols = torch.where(walls_round > 0) - voxel_height = list() - wall2d_id = list() - wall_height = list() - wall_height_exact = list() - y_position = list() - x_position = list() - index = 1 + num_walls = wall_rows.shape[0] + + # 2. Assign Wall IDs (1-indexed) vectorially uniqueWallIDs = torch.zeros((dsm.shape[0], dsm.shape[1]), device=device) - for i in torch.arange(wall_rows.shape[0]): - uniqueWallIDs[wall_rows[i], wall_cols[i]] = index - number_of_voxels = int( - walls_round[wall_rows[i], wall_cols[i]] / pixel_resolution - ) - # temp_aspect = wallAspect[wall_rows[i], wall_cols[i]] - voxel_index = 1 - for j in range(1, number_of_voxels + 1): - # wall_id.append(voxel_index) - wall2d_id.append(index) - voxel_height.append(j * pixel_resolution) - wall_height.append(walls_round[wall_rows[i], wall_cols[i]]) - wall_height_exact.append(walls_exact[wall_rows[i], wall_cols[i]]) - y_position.append(wall_rows[i]) - x_position.append(wall_cols[i]) - # wall_aspect.append(temp_aspect) - - voxel_index += 1 - - index += 1 - - wall2d_id.append(0) - voxel_height.append(0) - wall_height.append(0) - wall_height_exact.append(0) - y_position.append(0) - x_position.append(0) - - wall_dict = {} - for A, B in zip(wall2d_id, wall_height_exact): - wall_dict[A] = B - - # saveraster(dataSet, output_uniquewallid, uniqueWallIDs) - - # Unique IDs for each voxel - voxelId_list = torch.arange(1, wall2d_id.__len__() + 1, device=device) - - # Table with unique voxel ID, height of voxel, total height of wall, unique ID of wall (based on 2D-location in raster) and y and x coordinates + wall_indices = torch.arange(1, num_walls + 1, device=device) + uniqueWallIDs[wall_rows, wall_cols] = wall_indices.float() + + # 3. Calculate number of voxels for every wall pixel at once + # Extract wall attributes only for the valid wall locations + walls_round_extracted = walls_round[wall_rows, wall_cols] + walls_exact_extracted = walls_exact[wall_rows, wall_cols] + + number_of_voxels = (walls_round_extracted / pixel_resolution).int() + + # 4. Repeat wall properties based on their respective voxel counts + # This replaces both the 'i' and 'j' loops entirely + wall2d_id_tensor = torch.repeat_interleave(wall_indices, number_of_voxels) + wall_height_tensor = torch.repeat_interleave( + walls_round_extracted, number_of_voxels + ) + wall_height_exact_tensor = torch.repeat_interleave( + walls_exact_extracted, number_of_voxels + ) + y_position_tensor = torch.repeat_interleave(wall_rows, number_of_voxels) + x_position_tensor = torch.repeat_interleave(wall_cols, number_of_voxels) + + # 5. Generate voxel heights using a cumulative sequence sequence + # This handles the equivalent of `j * pixel_resolution` vectorially + # We create a 1-based local sequence for each repeated segment + # e.g., if number_of_voxels is [2, 3], we generate [1, 2, 1, 2, 3] + v_ids = torch.arange(wall2d_id_tensor.shape[0], device=device) + # Find the starting index of each repeated wall sequence + cum_voxels = torch.cumsum(number_of_voxels, dim=0) + start_indices = torch.cat( + [torch.tensor([0], device=device), cum_voxels[:-1]] + ) + shift = torch.repeat_interleave(start_indices, number_of_voxels) + + local_voxel_index = v_ids - shift + 1 + voxel_height_tensor = local_voxel_index * pixel_resolution + + # 6. Append the final trailing zero-row (mimicking original code logic) + zero_val = torch.tensor([0], device=device) + + wall2d_id_tensor = torch.cat([wall2d_id_tensor, zero_val]) + voxel_height_tensor = torch.cat([voxel_height_tensor, zero_val.float()]) + wall_height_tensor = torch.cat([wall_height_tensor, zero_val]) + wall_height_exact_tensor = torch.cat( + [wall_height_exact_tensor, zero_val.float()] + ) + y_position_tensor = torch.cat([y_position_tensor, zero_val]) + x_position_tensor = torch.cat([x_position_tensor, zero_val]) + + # 7. Construct outputs + voxelId_list = torch.arange( + 1, wall2d_id_tensor.shape[0] + 1, device=device + ) + voxelTable = torch.column_stack( - [ - t if isinstance(t, torch.Tensor) else torch.tensor(t, device=device) - for t in [ - voxelId_list, - voxel_height, - wall_height, - wall_height_exact, - wall2d_id, - y_position, - x_position, - ] + voxelId_list.float(), + voxel_height_tensor, + wall_height_tensor.float(), + wall_height_exact_tensor, + wall2d_id_tensor.float(), + y_position_tensor.float(), + x_position_tensor.float(), ] ) + # 8. Construct dictionary mapping (Kept native Python as requested by return type) + # We do this from the extracted wall tensors to avoid looping over the voxel table + wall_dict = dict( + zip(wall_indices.tolist(), walls_exact_extracted.tolist()) + ) + wall_dict[0] = 0.0 + + # Convert specific lists to match original return signature formats if downstream requires it return ( voxelTable, voxelId_list, @@ -107,8 +125,8 @@ def wallscheme_prepare( walls, aspect, uniqueWallIDs, - wall2d_id, - voxel_height, + wall2d_id_tensor.tolist(), + voxel_height_tensor.tolist(), ) @@ -157,7 +175,9 @@ def svf_for_voxels( # Counter to feedback current iteration counter = 1 # How many iterations are required to calculate svf for all voxels - loop_range = torch.arange(svf_height, maxWallHeight + svf_height, svf_height) + loop_range = torch.arange( + svf_height, maxWallHeight + svf_height, svf_height + ) # Loop for svf calculations of all voxel heights for i in loop_range: @@ -213,7 +233,9 @@ def svf_for_voxels( svftotal = svfbu - (1 - svfveg) * (1 - trans) # Get svf for each voxel - voxel_y = torch.where(voxelTable[:, 1] == i + svf_height) # +svf_height) + voxel_y = torch.where( + voxelTable[:, 1] == i + svf_height + ) # +svf_height) for temp_y in voxel_y[0]: svf_array[temp_y] = svftotal[ int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) @@ -287,7 +309,6 @@ def svf_kmeans( svfaveg_array = _to_tensor(svfaveg_array, device) svf_height_array = _to_tensor(svf_height_array, device) - # Calculate where there are buildings and not. Used to elevate dem. ground = dsm - dem # Ground == 1 = ground @@ -307,7 +328,9 @@ def svf_kmeans( labels = kmeans.fit_predict(data_reshaped) # Reshape the labels back into a torch tensor on the selected device - kmeans_clusters = torch.from_numpy(labels.reshape(dsm.shape[0], dsm.shape[1])).to(device) + kmeans_clusters = torch.from_numpy( + labels.reshape(dsm.shape[0], dsm.shape[1]) + ).to(device) # Remove cluster representing ground areas, i.e. where dsm - dem = 0 cluster_range = torch.arange(clusters, device=device) @@ -391,7 +414,9 @@ def svf_kmeans( svftotal = svfbu - (1 - svfveg) * (1 - trans) # Get svf for each voxel - voxel_y = torch.where(voxelTable[:, 1] == i + svf_height) # +svf_height) + voxel_y = torch.where( + voxelTable[:, 1] == i + svf_height + ) # +svf_height) for temp_y in voxel_y[0]: svf_array[temp_y] = svftotal[ int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) diff --git a/functions/svf_functions.py b/functions/svf_functions.py index adb0a85..c245218 100644 --- a/functions/svf_functions.py +++ b/functions/svf_functions.py @@ -15,6 +15,7 @@ def _sync_if_cuda(device): if device is not None and device.type == "cuda": torch.cuda.synchronize() + # from ..functions.wallalgorithms import findwalls from ..functions import wallalgorithms as wa from ..functions import svf_for_voxels as svfv @@ -90,7 +91,8 @@ def svf_angles_100121(device): azi19, azi20, ) - ), device=device + ), + device=device, ) aziinterval = torch.tensor( torch.hstack( @@ -116,7 +118,8 @@ def svf_angles_100121(device): 8.0, 1.0, ) - ), device=device + ), + device=device, ) angleresult = {"iazimuth": iazimuth, "aziinterval": aziinterval} @@ -136,8 +139,12 @@ def svfForProcessing153( device=torch.device("cpu"), ): if device is None: - device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") - + device = ( + torch.device("cuda") + if torch.cuda.is_available() + else torch.device("cpu") + ) + dsm = _to_tensor(dsm, device) vegdem = _to_tensor(vegdem, device) vegdem2 = _to_tensor(vegdem2, device) @@ -190,7 +197,7 @@ def svfForProcessing153( aziinterval, skyvaultaziint, azistart, - ) = create_patches(patch_option) + ) = create_patches(patch_option, device) skyvaultalt = _to_tensor(skyvaultalt, device) skyvaultazi = _to_tensor(skyvaultazi, device) @@ -205,8 +212,12 @@ def svfForProcessing153( ) iazimuth = torch.zeros(int(torch.sum(aziinterval).item()), device=device) - shmat = torch.zeros((rows, cols, int(torch.sum(aziinterval).item())), device=device) - vegshmat = torch.zeros((rows, cols, int(torch.sum(aziinterval).item())), device=device) + shmat = torch.zeros( + (rows, cols, int(torch.sum(aziinterval).item())), device=device + ) + vegshmat = torch.zeros( + (rows, cols, int(torch.sum(aziinterval).item())), device=device + ) vbshvegshmat = torch.zeros( (rows, cols, int(torch.sum(aziinterval).item())), device=device ) @@ -258,8 +269,12 @@ def svfForProcessing153( if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") break - altitude = torch.tensor(float(skyvaultaltint[int(i)].item()), device=device) - azimuth = torch.tensor(float(iazimuth[int(index)].item()), device=device) + altitude = torch.tensor( + float(skyvaultaltint[int(i)].item()), device=device + ) + azimuth = torch.tensor( + float(iazimuth[int(index)].item()), device=device + ) # Casting shadow if wallScheme: @@ -299,10 +314,14 @@ def svfForProcessing153( ) ) vegsh = torch.ones( - (sh.shape[0], sh.shape[1]), device=device, dtype=torch.float32 + (sh.shape[0], sh.shape[1]), + device=device, + dtype=torch.float32, ) vbshvegsh = torch.ones( - (sh.shape[0], sh.shape[1]), device=device, dtype=torch.float32 + (sh.shape[0], sh.shape[1]), + device=device, + dtype=torch.float32, ) vegshmat[:, :, index] = vegsh vbshvegshmat[:, :, index] = vbshvegsh @@ -323,7 +342,9 @@ def svfForProcessing153( ) vegsh = torch.tensor(shadowresult["vegsh"], device=device) - vbshvegsh = torch.tensor(shadowresult["vbshvegsh"], device=device) + vbshvegsh = torch.tensor( + shadowresult["vbshvegsh"], device=device + ) vegshmat[:, :, index] = vegsh vbshvegshmat[:, :, index] = vbshvegsh sh = torch.tensor(shadowresult["sh"], device=device) @@ -332,7 +353,6 @@ def svfForProcessing153( dsm, azimuth, altitude, scale, feedback, 1, device ) - shmat[:, :, index] = sh # Wall temperature scheme, i.e. finding out which voxel is seen from each pixel, where direction is patch azimuth and altitude @@ -363,7 +383,7 @@ def svfForProcessing153( int(annulino[int(i)].item()) + 1, int(annulino[int(i + 1.0)].item()) + 1, ): - + weight = annulus_weight(k, aziinterval[i], device) * sh svf = svf + weight weight = annulus_weight(k, aziintervalaniso[i], device) * sh @@ -470,7 +490,11 @@ def svfForProcessing655( device=torch.device("cpu"), ): if device is None: - device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") + device = ( + torch.device("cuda") + if torch.cuda.is_available() + else torch.device("cpu") + ) dsm = _to_tensor(dsm, device) vegdem = _to_tensor(vegdem, device) vegdem2 = _to_tensor(vegdem2, device) @@ -548,14 +572,15 @@ def svfForProcessing655( ) vegsh = torch.tensor(shadowresult["vegsh"], device=device) - vbshvegsh = torch.tensor(shadowresult["vbshvegsh"], device=device) + vbshvegsh = torch.tensor( + shadowresult["vbshvegsh"], device=device + ) sh = torch.tensor(shadowresult["sh"], device=device) else: sh = shadow.shadowingfunctionglobalradiation( dsm, azimuth, altitude, scale, feedback, 1, device ) - # Calculate svfs for k in range( int(annulino[int(i)].item()) + 1, diff --git a/functions/wallalgorithms.py b/functions/wallalgorithms.py index d393ebf..9a95ae2 100644 --- a/functions/wallalgorithms.py +++ b/functions/wallalgorithms.py @@ -4,7 +4,6 @@ __author__ = "xlinfr" import math -import numpy as np import torch import torch.nn.functional as F @@ -13,69 +12,55 @@ import scipy.ndimage as sc from scipy.ndimage import maximum_filter - -def findwalls_sp(arr_dsm, walllimit, footprint=None): +def findwalls_sp(arr_dsm, walllimit, device, footprint=None): """ - This function identifies walls based on a DSM and a wall height limit. - - Parameters: - arr_dsm (Tensor or array-like): Digital Surface Model - walllimit (float): Wall height limit - footprint (Tensor, optional): Structuring element for the maximum filter. - Defaults to a diamond shape (cardinal neighbors). + Identifie les murs de manière ultra-optimisée en mémoire (sans F.unfold). """ - # Ensure input is a PyTorch tensor and preserve its device (CPU or GPU) + # 1. S'assurer que l'entrée est un tenseur PyTorch if isinstance(arr_dsm, torch.Tensor): dsm_tensor = arr_dsm else: - dsm_tensor = torch.tensor(arr_dsm) + dsm_tensor = torch.tensor(arr_dsm, device=device) - # 1. Padding: 'edge' mode in NumPy becomes 'replicate' in PyTorch - # PyTorch requires at least a 3D tensor for 'replicate', so we unsqueeze(0) - padded_a = dsm_tensor.unsqueeze(0) - padded_a = F.pad(padded_a, pad=(1, 1, 1, 1), mode="replicate") - padded_a = padded_a.squeeze(0) - - # 2. Maximum Filter using Max Pooling - # Default footprint is a 3x3 diamond shape (cardinal points) + # 2. Définir le footprint par défaut (forme de diamant / points cardinaux) if footprint is None or footprint is False: - # For max_pool2d, we simulate the footprint by looking at a 3x3 window. - # To strictly replicate a diamond footprint in pure PyTorch, we can use - # a 2D morphological dilation with a custom kernel: - kernel = torch.tensor([[0., 1., 0.], - [1., 1., 1.], # Including center for max filter - [0., 1., 0.]], device=dsm_tensor.device) - - # Reshape padded_a to (Batch=1, Channel=1, H, W) for 2D convolutions - x = padded_a.unsqueeze(0).unsqueeze(0) - - # We use unfolds or a specialized max_pool, but since your footprint has 0s, - # the cleanest PyTorch way is to mask the 3x3 windows: - windows = F.unfold(x, kernel_size=3, padding=0) # Shape: (1, 9, L) - # Multiply by kernel flattened, replacing 0s with -inf so they are ignored in max - kernel_flat = kernel.flatten().unsqueeze(1) - windows = windows + torch.where(kernel_flat == 1, 0.0, float('-inf')) - - # Take the maximum over the 9 neighbors - max_neighbors = windows.max(dim=1)[0] # Shape: (1, L) - - # Reshape back to original DSM size (col, row) - max_neighbors = max_neighbors.view(dsm_tensor.shape) + footprint = torch.tensor([ + [0, 1, 0], + [1, 1, 1], + [0, 1, 0] + ], device=device) else: - # If a custom footprint is provided, you can apply a similar unfold method - x = padded_a.unsqueeze(0).unsqueeze(0) - windows = F.unfold(x, kernel_size=footprint.shape, padding=0) - footprint_flat = footprint.to(dsm_tensor.device).flatten().unsqueeze(1) - windows = windows + torch.where(footprint_flat == 1, 0.0, float('-inf')) - max_neighbors = windows.max(dim=1)[0].view(dsm_tensor.shape) - - # 3. Identify wall pixels + footprint = footprint.to(device=device) + + fh, fw = footprint.shape + pad_h, pad_w = fh // 2, fw // 2 + + # Padding adaptatif basé sur la taille du filtre + padded_a = dsm_tensor.unsqueeze(0).unsqueeze(0) + padded_a = F.pad(padded_a, pad=(pad_w, pad_w, pad_h, pad_h), mode="replicate") + padded_a = padded_a.squeeze(0).squeeze(0) + + # Initialisation de la matrice des maximums avec une valeur minimale (-infini) + max_neighbors = torch.full_like(dsm_tensor, float('-inf')) + + # Trouver les coordonnées où le filtre est actif (égal à 1) + y_indices, x_indices = torch.where(footprint == 1) + + # Utilisation du glissement par vue (0 copie mémoire) + H, W = dsm_tensor.shape + for dy, dx in zip(y_indices, x_indices): + # Cette ligne crée une "vue" virtuelle sans allouer de RAM + shifted_view = padded_a[dy : dy + H, dx : dx + W] + # Comparaison élément par élément optimisée + max_neighbors = torch.maximum(max_neighbors, shifted_view) + + # 3. Identification des pixels de murs walls = max_neighbors - dsm_tensor - # Apply wall height limit + # Appliquer la limite de hauteur des murs walls[walls < walllimit] = 0 - # 4. Set the outermost boundaries to zero + # 4. Remise à zéro des bordures extérieures walls[0, :] = 0 walls[-1, :] = 0 walls[:, 0] = 0 @@ -92,8 +77,7 @@ def findwalls(a, walllimit, feedback, total): # fredrikl@gvc.gu.se # 20150625 - col = a.shape[0] - row = a.shape[1] + col, row = a.shape walls = torch.zeros((col, row)) domain = torch.tensor([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) index = 0 @@ -118,141 +102,216 @@ def findwalls(a, walllimit, feedback, total): return walls -def filter1Goodwin_as_aspect_v3(walls_for_dir, scale, a, feedback, total): - """ - tThis function applies the filter processing presented in Goodwin et al (2010) but instead for removing - linear fetures it calculates wall aspect based on a wall pixels grid, a dsm (a) and a scale factor +def filter1Goodwin_as_aspect_v3( + walls_for_dir, scale, a, feedback, total, device, tile_size=2048 +): + """Applies the Goodwin et al. - Fredrik Lindberg, 2012-02-14 - fredrikl@gvc.gu.se + (2010) filter processing to calculate wall aspect based on a wall pixel + grid, a DSM (a), and a scale factor. - Translated: 2015-09-15 + Optimized for GPU using 2D Convolutions to eliminate nested pixel loops + and CPU-GPU transfer bottlenecks. - :param walls: - :param scale: - :param a: - :return: dirwalls + :param walls_for_dir: Tensor (H, W), Binary grid of walls + :param scale: Float, Scale factor determining filter size + :param a: Tensor (H, W), Digital Surface Model (DSM) + :param feedback: Optional, QGIS/Processing feedback object for progress + :param total: Float, Progress step multiplier + :return: Tensor (H, W), Wall aspect directions """ - - walls = walls_for_dir.clone().to(a.device) - - row = a.shape[0] - col = a.shape[1] - + row, col = a.shape + + # 1. Compute kernel footprint based on scale factor filtersize = torch.floor((scale + 0.0000000001) * 9) if filtersize <= 2: filtersize = 3 - else: - if filtersize != 9: - if filtersize % 2 == 0: - filtersize = filtersize + 1 + elif filtersize != 9 and filtersize % 2 == 0: + filtersize = filtersize + 1 - filthalveceil = int(torch.ceil(filtersize / 2.0)) - filthalvefloor = int(torch.floor(filtersize / 2.0)) + filtersize = int(filtersize) + filthalveceil = int(torch.ceil(torch.tensor(filtersize) / 2.0)) + filthalvefloor = int(torch.floor(torch.tensor(filtersize) / 2.0)) + n = filtersize - 1 - filtmatrix = torch.zeros((int(filtersize), int(filtersize))) - buildfilt = torch.zeros((int(filtersize), int(filtersize))) + # Initialize base structural elements on CPU for rotation + filtmatrix = torch.zeros((filtersize, filtersize)) + buildfilt = torch.zeros((filtersize, filtersize)) filtmatrix[:, filthalveceil - 1] = 1 - n = filtmatrix.shape[0] - 1 buildfilt[filthalveceil - 1, 0:filthalvefloor] = 1 - buildfilt[filthalveceil - 1, filthalveceil : int(filtersize)] = 2 - - y = torch.zeros((row, col), device=a.device) # final direction - z = torch.zeros((row, col), device=a.device) # temporary direction - x = torch.zeros((row, col), device=a.device) # building side - walls[walls > 0] = 1 - - for h in range( - 0, 180 - ): # =0:1:180 #%increased resolution to 1 deg 20140911 - if feedback is not None: - feedback.setProgress(int(h * total)) - if feedback.isCanceled(): - feedback.setProgressText("Calculation cancelled") - break - filtmatrix1temp = sc.rotate( - filtmatrix.cpu().numpy() - if filtmatrix.device.type == "cuda" - else filtmatrix.numpy(), - h, - order=1, - reshape=False, - mode="nearest", - ) # bilinear - filtmatrix1 = torch.round( - torch.from_numpy(filtmatrix1temp).to(walls.device) - ) - # filtmatrix1temp = sc.imrotate(filtmatrix, h, 'bilinear') - # filtmatrix1 = torch.round(filtmatrix1temp / 255.) - # filtmatrixbuildtemp = sc.imrotate(buildfilt, h, 'nearest') - filtmatrixbuildtemp = sc.rotate( - buildfilt.cpu().numpy() - if buildfilt.device.type == "cuda" - else buildfilt.numpy(), - h, - order=0, - reshape=False, - mode="nearest", - ) # Nearest neighbor - # filtmatrixbuild = torch.round(filtmatrixbuildtemp / 127.) - filtmatrixbuild = torch.round( - torch.from_numpy(filtmatrixbuildtemp).to(walls.device) - ) - index = 270 - h - if h == 150: - filtmatrixbuild[:, n] = 0 - if h == 30: - filtmatrixbuild[:, n] = 0 - if index == 225: - # n = filtmatrix.shape[0] - 1 # length(filtmatrix); - filtmatrix1[0, 0] = 1 - filtmatrix1[n, n] = 1 - if index == 135: - # n = filtmatrix.shape[0] - 1 # length(filtmatrix); - filtmatrix1[0, n] = 1 - filtmatrix1[n, 0] = 1 - - for i in range( - int(filthalveceil) - 1, row - int(filthalveceil) - 1 - ): # i=filthalveceil:sizey-filthalveceil - for j in range( - int(filthalveceil) - 1, col - int(filthalveceil) - 1 - ): # (j=filthalveceil:sizex-filthalveceil - if walls[i, j] == 1: - wallscut = ( - walls[ - i - filthalvefloor : i + filthalvefloor + 1, - j - filthalvefloor : j + filthalvefloor + 1, - ] - * filtmatrix1 - ) - dsmcut = a[ - i - filthalvefloor : i + filthalvefloor + 1, - j - filthalvefloor : j + filthalvefloor + 1, - ] - if z[i, j] < wallscut.sum(): # sum(sum(wallscut)) - z[i, j] = wallscut.sum() # sum(sum(wallscut)); - if torch.sum(dsmcut[filtmatrixbuild == 1]) > torch.sum( - dsmcut[filtmatrixbuild == 2] - ): - x[i, j] = 1 - else: - x[i, j] = 2 - - y[i, j] = index - - y[(x == 1)] = y[(x == 1)] - 180 - y[(y < 0)] = y[(y < 0)] + 360 - + buildfilt[filthalveceil - 1, filthalveceil:filtersize] = 2 + + filtmatrix_list = [] + buildfilt1_list = [] + buildfilt2_list = [] + + # 2. Pre-calculate all 180 directional filters on CPU + with torch.no_grad(): + for h in range(180): + filtmatrix1temp = sc.rotate( + filtmatrix.numpy(), h, order=1, reshape=False, mode="nearest" + ) + filtmatrix1 = torch.round(torch.from_numpy(filtmatrix1temp)) + + filtmatrixbuildtemp = sc.rotate( + buildfilt.numpy(), h, order=0, reshape=False, mode="nearest" + ) + filtmatrixbuild = torch.round(torch.from_numpy(filtmatrixbuildtemp)) + + index = 270 - h + if h in (150, 30): + filtmatrixbuild[:, n] = 0 + if index == 225: + filtmatrix1[0, 0] = 1 + filtmatrix1[n, n] = 1 + if index == 135: + filtmatrix1[0, n] = 1 + filtmatrix1[n, 0] = 1 + + filtmatrix_list.append(filtmatrix1.unsqueeze(0).unsqueeze(0)) + buildfilt1_list.append( + (filtmatrixbuild == 1).float().unsqueeze(0).unsqueeze(0) + ) + buildfilt2_list.append( + (filtmatrixbuild == 2).float().unsqueeze(0).unsqueeze(0) + ) + + # Stacking kernels on CPU first - Now perfectly defined! + all_kernels_walls = torch.cat(filtmatrix_list, dim=0) + all_kernels_dsm1 = torch.cat(buildfilt1_list, dim=0) + all_kernels_dsm2 = torch.cat(buildfilt2_list, dim=0) + + # 3. Setup global output allocation arrays + final_y = torch.zeros((row, col), device=device) + final_x = torch.zeros((row, col), device=device) + + walls_binary = (walls_for_dir > 0).float().to(device) + a_device = a.float().to(device) + + # Calculate total tiles for feedback tracking + num_tiles_y = math.ceil(row / tile_size) + num_tiles_x = math.ceil(col / tile_size) + total_tiles = num_tiles_y * num_tiles_x + tile_count = 0 + + # 4. Loop through spatial tiles + for r_start in range(0, row, tile_size): + r_end = min(r_start + tile_size, row) + + for c_start in range(0, col, tile_size): + c_end = min(c_start + tile_size, col) + + if feedback is not None and feedback.isCanceled(): + return final_y + + # Determine padded coordinates (the halo zone) + pad_top = min(r_start, filthalvefloor) + pad_bottom = min(row - r_end, filthalvefloor) + pad_left = min(c_start, filthalvefloor) + pad_right = min(col - c_end, filthalvefloor) + + # Slice tile out with padding included + tile_a = a_device[ + (r_start - pad_top):(r_end + pad_bottom), + (c_start - pad_left):(c_end + pad_right) + ].unsqueeze(0).unsqueeze(0) + + tile_walls = walls_binary[ + (r_start - pad_top):(r_end + pad_bottom), + (c_start - pad_left):(c_end + pad_right) + ].unsqueeze(0).unsqueeze(0) + + # Local allocation sizes for this specific tile (including pads) + tile_rows, tile_cols = tile_a.shape[2], tile_a.shape[3] + + # Running Maximum Setup for this tile + z_max = torch.full((tile_rows, tile_cols), -1.0, device=device) + h_best = torch.zeros((tile_rows, tile_cols), dtype=torch.long, device=device) + dsm_best1 = torch.zeros((tile_rows, tile_cols), device=device) + dsm_best2 = torch.zeros((tile_rows, tile_cols), device=device) + + # 5. Process angles in small chunks inside this single spatial tile + chunk_size = 10 + for idx in range(0, 180, chunk_size): + end_idx = min(idx + chunk_size, 180) + + k_walls = all_kernels_walls[idx:end_idx].to(device) + k_dsm1 = all_kernels_dsm1[idx:end_idx].to(device) + k_dsm2 = all_kernels_dsm2[idx:end_idx].to(device) + + # Notice: padding=0 because we manually padded our spatial tiles! + walls_conv = F.conv2d(tile_walls, k_walls, padding=0).squeeze(0) + dsm_conv1 = F.conv2d(tile_a, k_dsm1, padding=0).squeeze(0) + dsm_conv2 = F.conv2d(tile_a, k_dsm2, padding=0).squeeze(0) + + # Account for spatial shrinkage since F.conv2d with padding=0 trims edges + c_rows, c_cols = walls_conv.shape[1], walls_conv.shape[2] + + # Align running arrays dynamically to conv output window + z_max_crop = z_max[filthalvefloor:filthalvefloor+c_rows, filthalvefloor:filthalvefloor+c_cols] + + chunk_max, chunk_h_local = torch.max(walls_conv, dim=0) + is_new_max = chunk_max >= z_max_crop + + if is_new_max.any(): + # Update local trackers + z_max[filthalvefloor:filthalvefloor+c_rows, filthalvefloor:filthalvefloor+c_cols] = torch.where(is_new_max, chunk_max, z_max_crop) + + chunk_h_absolute = chunk_h_local + idx + h_best_crop = h_best[filthalvefloor:filthalvefloor+c_rows, filthalvefloor:filthalvefloor+c_cols] + h_best[filthalvefloor:filthalvefloor+c_rows, filthalvefloor:filthalvefloor+c_cols] = torch.where(is_new_max, chunk_h_absolute, h_best_crop) + + h_local_unsqueeze = chunk_h_local.unsqueeze(0) + chunk_dsm1 = torch.gather(dsm_conv1, dim=0, index=h_local_unsqueeze).squeeze(0) + chunk_dsm2 = torch.gather(dsm_conv2, dim=0, index=h_local_unsqueeze).squeeze(0) + + dsm_best1_crop = dsm_best1[filthalvefloor:filthalvefloor+c_rows, filthalvefloor:filthalvefloor+c_cols] + dsm_best2_crop = dsm_best2[filthalvefloor:filthalvefloor+c_rows, filthalvefloor:filthalvefloor+c_cols] + + dsm_best1[filthalvefloor:filthalvefloor+c_rows, filthalvefloor:filthalvefloor+c_cols] = torch.where(is_new_max, chunk_dsm1, dsm_best1_crop) + dsm_best2[filthalvefloor:filthalvefloor+c_rows, filthalvefloor:filthalvefloor+c_cols] = torch.where(is_new_max, chunk_dsm2, dsm_best2_crop) + + # Un-pad results to extract the pure valid window of this tile + tile_y = 270.0 - h_best.float() + tile_x = torch.where(dsm_best1 > dsm_best2, 1, 2) + + valid_tile_y = tile_y[pad_top : tile_rows - pad_bottom, pad_left : tile_cols - pad_right] + valid_tile_x = tile_x[pad_top : tile_rows - pad_bottom, pad_left : tile_cols - pad_right] + + # Write back cleanly into the global array without overlap seams + final_y[r_start:r_end, c_start:c_end] = valid_tile_y + final_x[r_start:r_end, c_start:c_end] = valid_tile_x + + # Progress handling + tile_count += 1 + if feedback is not None: + feedback.setProgress(int((tile_count / total_tiles) * total * 0.9)) + + # 6. Global Post-processing calculations + border_mask = torch.zeros((row, col), dtype=torch.bool, device=device) + start = filthalveceil - 1 + end_row = row - filthalveceil - 1 + end_col = col - filthalveceil - 1 + border_mask[start:end_row, start:end_col] = True + + valid_mask = (walls_binary == 1) & border_mask + final_y = torch.where(valid_mask, final_y, torch.zeros_like(final_y)) + final_x = torch.where(valid_mask, final_x, torch.zeros_like(final_x)) + + final_y[final_x == 1] = final_y[final_x == 1] - 180 + final_y[final_y < 0] = final_y[final_y < 0] + 360 + + # Incorporate derivative fallback values for flat results grad, asp = get_ders(a, scale) + asp_device = torch.from_numpy(asp).to(device) if not isinstance(asp, torch.Tensor) else asp.to(device) + + final_y = final_y + ((walls_binary == 1) * 1) * ((final_y == 0) * 1) * (asp_device / (math.pi / 180.0)) - y = y + ((walls == 1) * 1) * ((y == 0) * 1) * (asp / (math.pi / 180.0)) - - dirwalls = y - - return dirwalls + if feedback is not None: + feedback.setProgress(int(total)) + return final_y def cart2pol(x, y, units="deg"): radius = torch.sqrt(x**2 + y**2) diff --git a/preprocessor/skyviewfactor_algorithm.py b/preprocessor/skyviewfactor_algorithm.py index b125b3f..a4aad1b 100644 --- a/preprocessor/skyviewfactor_algorithm.py +++ b/preprocessor/skyviewfactor_algorithm.py @@ -44,7 +44,7 @@ # from processing.gui.wrappers import WidgetWrapper from qgis.PyQt.QtGui import QIcon -from osgeo import gdal, osr +from osgeo import gdal from osgeo.gdalconst import * import os import numpy as np @@ -54,7 +54,6 @@ import zipfile import sys from ..util import misc -from qgis.PyQt.QtWidgets import QDateEdit, QTimeEdit from ..functions import svf_functions as svf from ..functions import svf_for_voxels as svfv @@ -127,18 +126,16 @@ def initAlgorithm(self, config): maxValue=99.9, ) ) - + # Wall parameterization self.addParameter( QgsProcessingParameterBoolean( self.USE_GPU, - self.tr( - "Use GPU" - ), + self.tr("Use GPU"), defaultValue=False, ), ) - + self.addParameter( QgsProcessingParameterBoolean( self.ANISO, @@ -286,7 +283,7 @@ def processAlgorithm(self, parameters, context, feedback): if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): os.mkdir(outputDir) - + device = torch.device("cpu") if use_gpu and torch.cuda.is_available(): device = torch.device("cuda") @@ -383,30 +380,31 @@ def processAlgorithm(self, parameters, context, feedback): vegdsm = torch.zeros([rows, cols], device=device) vegdsm2 = 0.0 usevegdem = 0 - - if aniso == 1: feedback.setProgressText("Calculating SVF using 153 iterations") - ret = svf.svfForProcessing153( - dsm, - vegdsm, - vegdsm2, - scale, - usevegdem, - pixel_resolution, - wallScheme, - dem, - feedback, - device=device, - ) + with torch.no_grad(): + + ret = svf.svfForProcessing153( + dsm, + vegdsm, + vegdsm2, + scale, + usevegdem, + pixel_resolution, + wallScheme, + dem, + feedback, + device=device, + ) else: feedback.setProgressText("Calculating SVF using 655 iterations") - ret = svf.svfForProcessing655( - dsm, vegdsm, vegdsm2, scale, usevegdem, feedback, device=device - ) - + with torch.no_grad(): + + ret = svf.svfForProcessing655( + dsm, vegdsm, vegdsm2, scale, usevegdem, feedback, device=device + ) # print('Time to finish first SVF calculation = ' + str(run_time)) if wallScheme == 1: @@ -427,7 +425,9 @@ def processAlgorithm(self, parameters, context, feedback): svftotal = svfbu - (1 - svfveg) * (1 - trans) # Lägg till loop för att lägga till i tabellen svf_array = torch.zeros((voxelTable.shape[0]), device=device) - svf_height_array = torch.zeros((voxelTable.shape[0]), device=device) + svf_height_array = torch.zeros( + (voxelTable.shape[0]), device=device + ) svfbu_array = torch.zeros((voxelTable.shape[0]), device=device) svfveg_array = torch.zeros((voxelTable.shape[0]), device=device) svfaveg_array = torch.zeros((voxelTable.shape[0]), device=device) @@ -448,32 +448,34 @@ def processAlgorithm(self, parameters, context, feedback): svf_height_array[temp_y] = svf_height if kmeans: - voxelTable, cluster_heights = svfv.svf_kmeans( - dsm, - dem, - vegdsm, - vegdsm2, - wallHeights, - transVeg, - scale, - usevegdem, - pixel_resolution, - voxelTable, - clusters, - svf_height, - svf_array, - svfbu_array, - svfveg_array, - svfaveg_array, - svf_height_array, - feedback, - device=device, - ) + with torch.no_grad(): + + voxelTable, cluster_heights = svfv.svf_kmeans( + dsm, + dem, + vegdsm, + vegdsm2, + wallHeights, + transVeg, + scale, + usevegdem, + pixel_resolution, + voxelTable, + clusters, + svf_height, + svf_array, + svfbu_array, + svfveg_array, + svfaveg_array, + svf_height_array, + feedback, + device=device, + ) - # Interpolate for voxels where SVF has not been calculated - voxelTable = svfv.interpolate_svf( - voxelTable, cluster_heights, kmeans - ) + # Interpolate for voxels where SVF has not been calculated + voxelTable = svfv.interpolate_svf( + voxelTable, cluster_heights, kmeans + ) # Loop for exact SVF at heights (increase DEM) # if demlayer: @@ -481,25 +483,28 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText( "Calculating SVF for wall surface temperature parameterization" ) - voxelTable = svfv.svf_for_voxels( - dsm, - dem, - vegdsm, - vegdsm2, - transVeg, - scale, - usevegdem, - pixel_resolution, - voxelTable, - svf_height, - svf_array, - svfbu_array, - svfveg_array, - svfaveg_array, - svf_height_array, - feedback, - device=device, - ) + + with torch.no_grad(): + + voxelTable = svfv.svf_for_voxels( + dsm, + dem, + vegdsm, + vegdsm2, + transVeg, + scale, + usevegdem, + pixel_resolution, + voxelTable, + svf_height, + svf_array, + svfbu_array, + svfveg_array, + svfaveg_array, + svf_height_array, + feedback, + device=device, + ) # Remove rows where svfbu, sfveg and svfaveg is zero if usevegdem == 1: @@ -532,11 +537,31 @@ def processAlgorithm(self, parameters, context, feedback): svfbuW = ret["svfW"] svfbuN = ret["svfN"] - misc.saveraster(gdal_dsm, outputDir + "/" + "svf.tif", svfbu.cpu().detach().numpy()) - misc.saveraster(gdal_dsm, outputDir + "/" + "svfE.tif", svfbuE.cpu().detach().numpy()) - misc.saveraster(gdal_dsm, outputDir + "/" + "svfS.tif", svfbuS.cpu().detach().numpy()) - misc.saveraster(gdal_dsm, outputDir + "/" + "svfW.tif", svfbuW.cpu().detach().numpy()) - misc.saveraster(gdal_dsm, outputDir + "/" + "svfN.tif", svfbuN.cpu().detach().numpy()) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svf.tif", + svfbu.cpu().detach().numpy(), + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfE.tif", + svfbuE.cpu().detach().numpy(), + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfS.tif", + svfbuS.cpu().detach().numpy(), + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfW.tif", + svfbuW.cpu().detach().numpy(), + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfN.tif", + svfbuN.cpu().detach().numpy(), + ) if os.path.isfile(outputDir + "/" + "svfs.zip"): os.remove(outputDir + "/" + "svfs.zip") @@ -571,34 +596,54 @@ def processAlgorithm(self, parameters, context, feedback): svfNaveg = ret["svfNaveg"] misc.saveraster( - gdal_dsm, outputDir + "/" + "svfveg.tif", svfveg.cpu().detach().numpy() + gdal_dsm, + outputDir + "/" + "svfveg.tif", + svfveg.cpu().detach().numpy(), ) misc.saveraster( - gdal_dsm, outputDir + "/" + "svfEveg.tif", svfEveg.cpu().detach().numpy() + gdal_dsm, + outputDir + "/" + "svfEveg.tif", + svfEveg.cpu().detach().numpy(), ) misc.saveraster( - gdal_dsm, outputDir + "/" + "svfSveg.tif", svfSveg.cpu().detach().numpy() + gdal_dsm, + outputDir + "/" + "svfSveg.tif", + svfSveg.cpu().detach().numpy(), ) misc.saveraster( - gdal_dsm, outputDir + "/" + "svfWveg.tif", svfWveg.cpu().detach().numpy() + gdal_dsm, + outputDir + "/" + "svfWveg.tif", + svfWveg.cpu().detach().numpy(), ) misc.saveraster( - gdal_dsm, outputDir + "/" + "svfNveg.tif", svfNveg.cpu().detach().numpy() + gdal_dsm, + outputDir + "/" + "svfNveg.tif", + svfNveg.cpu().detach().numpy(), ) misc.saveraster( - gdal_dsm, outputDir + "/" + "svfaveg.tif", svfaveg.cpu().detach().numpy() + gdal_dsm, + outputDir + "/" + "svfaveg.tif", + svfaveg.cpu().detach().numpy(), ) misc.saveraster( - gdal_dsm, outputDir + "/" + "svfEaveg.tif", svfEaveg.cpu().detach().numpy() + gdal_dsm, + outputDir + "/" + "svfEaveg.tif", + svfEaveg.cpu().detach().numpy(), ) misc.saveraster( - gdal_dsm, outputDir + "/" + "svfSaveg.tif", svfSaveg.cpu().detach().numpy() + gdal_dsm, + outputDir + "/" + "svfSaveg.tif", + svfSaveg.cpu().detach().numpy(), ) misc.saveraster( - gdal_dsm, outputDir + "/" + "svfWaveg.tif", svfWaveg.cpu().detach().numpy() + gdal_dsm, + outputDir + "/" + "svfWaveg.tif", + svfWaveg.cpu().detach().numpy(), ) misc.saveraster( - gdal_dsm, outputDir + "/" + "svfNaveg.tif", svfNaveg.cpu().detach().numpy() + gdal_dsm, + outputDir + "/" + "svfNaveg.tif", + svfNaveg.cpu().detach().numpy(), ) zippo = zipfile.ZipFile(outputDir + "/" + "svfs.zip", "a") @@ -628,7 +673,9 @@ def processAlgorithm(self, parameters, context, feedback): trans = transVeg / 100.0 svftotal = svfbu - (1 - svfveg) * (1 - trans) - misc.saveraster(gdal_dsm, filename, svftotal.cpu().detach().numpy()) + misc.saveraster( + gdal_dsm, filename, svftotal.cpu().detach().numpy() + ) # Save shadow images for SOLWEIG 2019a if aniso == 1: diff --git a/preprocessor/wall_heightaspect_algorithm.py b/preprocessor/wall_heightaspect_algorithm.py index 598e8d2..afc8dcc 100644 --- a/preprocessor/wall_heightaspect_algorithm.py +++ b/preprocessor/wall_heightaspect_algorithm.py @@ -33,47 +33,32 @@ from qgis.PyQt.QtCore import QCoreApplication, QVariant from qgis.core import ( QgsProcessingAlgorithm, + QgsProcessingParameterBoolean, QgsProcessingParameterNumber, QgsProcessingParameterRasterLayer, QgsProcessingParameterRasterDestination, ) from osgeo import gdal -from qgis.PyQt.QtWidgets import QAction, QFileDialog, QMessageBox from osgeo.gdalconst import * -import numpy as np +import torch import os from ..functions import wallalgorithms as wa from qgis.PyQt.QtGui import QIcon import inspect from pathlib import Path from ..util.misc import saverasternd +import os -# def saverasternd(gdal_data, filename, raster): -# rows = gdal_data.RasterYSize -# cols = gdal_data.RasterXSize - -# outDs = gdal.GetDriverByName("GTiff").Create(filename, cols, rows, int(1), GDT_Float32) -# outBand = outDs.GetRasterBand(1) - -# # write the data -# outBand.WriteArray(raster, 0, 0) -# # flush data to disk, set the NoData value and calculate stats -# outBand.FlushCache() -# # outBand.SetNoDataValue(-9999) - -# # georeference the image and set the projection -# outDs.SetGeoTransform(gdal_data.GetGeoTransform()) -# outDs.SetProjection(gdal_data.GetProjection()) class ProcessingWallHeightAscpetAlgorithm(QgsProcessingAlgorithm): INPUT_LIMIT = "INPUT_LIMIT" INPUT = "INPUT" + USE_GPU = "USE_GPU" OUTPUT_HEIGHT = "OUTPUT_HEIGHT" OUTPUT_ASPECT = "OUTPUT_ASPECT" - # ASPECT_BOOL = 'ASPECT_BOOL' def initAlgorithm(self, config): @@ -85,9 +70,7 @@ def initAlgorithm(self, config): False, ) ) - # self.addParameter(QgsProcessingParameterBoolean(self.ASPECT_BOOL, - # self.tr("Calculate wall aspect"), - # defaultValue=True)) + self.addParameter( QgsProcessingParameterNumber( self.INPUT_LIMIT, @@ -97,6 +80,15 @@ def initAlgorithm(self, config): minValue=0.0, ) ) + self.addParameter( + QgsProcessingParameterBoolean( + self.USE_GPU, + self.tr("Use GPU if available"), + None, + False, + ) + ) + self.addParameter( QgsProcessingParameterRasterDestination( self.OUTPUT_HEIGHT, @@ -115,6 +107,8 @@ def initAlgorithm(self, config): ) def processAlgorithm(self, parameters, context, feedback): + torch.set_num_threads(max(1, os.cpu_count())) + torch.set_num_interop_threads(max(1, os.cpu_count())) outputFileHeight = self.parameterAsOutputLayer( parameters, self.OUTPUT_HEIGHT, context ) @@ -126,6 +120,7 @@ def processAlgorithm(self, parameters, context, feedback): walllimit = self.parameterAsDouble( parameters, self.INPUT_LIMIT, context ) + use_gpu = self.parameterAsBool(parameters, self.USE_GPU, context) cmd_folder = Path( os.path.split(inspect.getfile(inspect.currentframe()))[0] @@ -133,8 +128,10 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText(str(cmd_folder)) feedback.setProgressText(str(cmd_folder.parent)) - # feedback.setProgressText(str(parameters["INPUT"])) # this prints to the processing log tab - # QgsMessageLog.logMessage("Testing", "umep", level=Qgis.Info) # This prints to a umep tab + device = torch.device("cpu") + if use_gpu and torch.cuda.is_available(): + device = torch.device("cuda") + feedback.setProgressText("GPU detected and will be used for calculations.") provider = dsmin.dataProvider() filepath_dsm = str(provider.dataSourceUri()) @@ -143,21 +140,20 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText("Calculating wall height") total = 100.0 / (int(dsm.shape[0] * dsm.shape[1])) - # walls = wa.findwalls(dsm, walllimit, feedback, total) - walls = wa.findwalls_sp(dsm, walllimit, False) + walls = wa.findwalls_sp(dsm, walllimit, device, False) - wallssave = np.copy(walls) + wallssave = walls # feedback.setProgressText(outputFileHeight) - saverasternd(gdal_dsm, outputFileHeight, wallssave) + saverasternd(gdal_dsm, outputFileHeight, wallssave.cpu().detach().numpy()) if outputFileAspect: total = 100.0 / 180.0 # outputFileAspect = self.parameterAsOutputLayer(parameters, self.OUTPUT_ASPECT, context) feedback.setProgressText("Calculating wall aspect") dirwalls = wa.filter1Goodwin_as_aspect_v3( - walls, 1, dsm, feedback, total + walls, torch.tensor(1, device=device), torch.tensor(dsm, device=device), feedback, torch.tensor(total, device=device), device ) - saverasternd(gdal_dsm, outputFileAspect, dirwalls) + saverasternd(gdal_dsm, outputFileAspect, dirwalls.cpu().detach().numpy()) else: feedback.setProgressText("Wall aspect not calculated") diff --git a/processor/sebe_algorithm.py b/processor/sebe_algorithm.py index 3412d94..0bcb04e 100644 --- a/processor/sebe_algorithm.py +++ b/processor/sebe_algorithm.py @@ -30,8 +30,7 @@ __revision__ = "$Format:%H$" -from qgis.PyQt.QtCore import QDate, QTime, Qt -from qgis.PyQt.QtWidgets import QDateEdit, QTimeEdit + from qgis.PyQt.QtCore import QCoreApplication, QVariant from qgis.core import ( QgsProcessingAlgorithm, @@ -47,6 +46,7 @@ ) from processing.gui.wrappers import WidgetWrapper import numpy as np +import torch from osgeo import gdal, osr from osgeo.gdalconst import * import os @@ -86,6 +86,7 @@ class ProcessingSEBEAlgorithm(QgsProcessingAlgorithm): IRR_FILE = "IRR_FILE" OUTPUT_DIR = "OUTPUT_DIR" OUTPUT_ROOF = "OUTPUT_ROOF" + USE_GPU = "USE8GPU" def initAlgorithm(self, config): self.addParameter( @@ -184,6 +185,13 @@ def initAlgorithm(self, config): defaultValue=False, ) ) + self.addParameter( + QgsProcessingParameterBoolean( + self.USE_GPU, + self.tr("Use GPU"), + defaultValue=False, + ) + ) self.addParameter( QgsProcessingParameterFileDestination( self.IRR_FILE, @@ -234,6 +242,8 @@ def processAlgorithm(self, parameters, context, feedback): albedo = self.parameterAsDouble(parameters, self.ALBEDO, context) inputMet = self.parameterAsString(parameters, self.INPUT_MET, context) saveskyirr = self.parameterAsBool(parameters, self.SAVESKYIRR, context) + use_gpu = self.parameterAsBool(parameters, self.USE_GPU, context) + irrFile = self.parameterAsFileOutput( parameters, self.IRR_FILE, context ) @@ -244,6 +254,11 @@ def processAlgorithm(self, parameters, context, feedback): if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): os.mkdir(outputDir) + + device = torch.device("cpu") + if use_gpu and torch.cuda.is_available(): + device = torch.device("cuda") + provider = dsmlayer.dataProvider() filepath_dsm = str(provider.dataSourceUri()) @@ -256,7 +271,7 @@ def processAlgorithm(self, parameters, context, feedback): nd = self.gdal_dsm.GetRasterBand(1).GetNoDataValue() self.dsm[self.dsm == nd] = 0.0 if self.dsm.min() < 0: - self.dsm = self.dsm + np.abs(self.dsm.min()) + self.dsm = self.dsm + torch.abs(self.dsm.min()) # response to issue #104 self.sorted_utclist @@ -402,9 +417,9 @@ def processAlgorithm(self, parameters, context, feedback): delim = " " try: - self.metdata = np.loadtxt( + self.metdata = torch.from_numpy(np.loadtxt( inputMet, skiprows=headernum, delimiter=delim - ) + ), device=device) except: QgsProcessingException( "Error: Make sure format of meteorological file is correct. You can" @@ -412,7 +427,7 @@ def processAlgorithm(self, parameters, context, feedback): "the Pre-processor" ) - testwhere = np.where( + testwhere = torch.where( (self.metdata[:, 14] < 0.0) | (self.metdata[:, 14] > 1300.0) ) if testwhere[0].__len__() > 0: @@ -430,7 +445,7 @@ def processAlgorithm(self, parameters, context, feedback): "the Pre-processor" ) - alt = np.median(self.dsm) + alt = torch.median(self.dsm) if alt < 0: alt = 3 @@ -454,10 +469,11 @@ def processAlgorithm(self, parameters, context, feedback): albedo, location, zen, + device ) if saveskyirr: - metout = np.zeros((145, 4)) + metout = torch.zeros((145, 4), device=device) metout[:, 0] = radmatI[:, 0] metout[:, 1] = radmatI[:, 1] metout[:, 2] = radmatI[:, 2] @@ -512,6 +528,7 @@ def processAlgorithm(self, parameters, context, feedback): usevegdem, feedback, wallmaxheight, + device ) Energyyearroof = seberesult["Energyyearroof"] diff --git a/processor/solweig_algorithm.py b/processor/solweig_algorithm.py index 60ad81a..c196955 100644 --- a/processor/solweig_algorithm.py +++ b/processor/solweig_algorithm.py @@ -127,7 +127,6 @@ class ProcessingSOLWEIGAlgorithm(QgsProcessingAlgorithm): CYL = "CYL" USE_GPU = "USE_GPU" - # solweig groundmodel = "groundmodel" @@ -585,11 +584,13 @@ def initAlgorithm(self, config): shei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced ) self.addParameter(shei) - + self.addParameter( QgsProcessingParameterBoolean( self.USE_GPU, - self.tr("Use GPU for calculations (if not ticked, CPU will be used)"), + self.tr( + "Use GPU for calculations (if not ticked, CPU will be used)" + ), defaultValue=False, optional=False, ) @@ -638,7 +639,7 @@ def initAlgorithm(self, config): defaultValue=False, ) ) - + self.addParameter( QgsProcessingParameterBoolean( self.OUTPUT_TREEPLANTER, @@ -694,9 +695,7 @@ def processAlgorithm(self, parameters, context, feedback): walayer = self.parameterAsRasterLayer( parameters, self.INPUT_ASPECT, context ) - - trunkr = self.parameterAsDouble( parameters, self.INPUT_THEIGHT, context ) @@ -785,7 +784,7 @@ def processAlgorithm(self, parameters, context, feedback): outputLdown = self.parameterAsBool( parameters, self.OUTPUT_LDOWN, context ) - + gpu_bool = self.parameterAsBool(parameters, self.USE_GPU, context) outputTreeplanter = self.parameterAsBool( @@ -805,12 +804,11 @@ def processAlgorithm(self, parameters, context, feedback): saveBuild = True outputKdiff = True # outputSstr = True - + calculation_mode = "cpu" if gpu_bool: calculation_mode = "gpu" - if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): os.mkdir(outputDir) diff --git a/util/SEBESOLWEIGCommonFiles/Perez_v3.py b/util/SEBESOLWEIGCommonFiles/Perez_v3.py index 2ca4c4e..e9ef8a2 100644 --- a/util/SEBESOLWEIGCommonFiles/Perez_v3.py +++ b/util/SEBESOLWEIGCommonFiles/Perez_v3.py @@ -1,9 +1,11 @@ from __future__ import division -import numpy as np from ...util.SEBESOLWEIGCommonFiles.create_patches import create_patches +import torch -def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice, patch_option): +def Perez_v3( + zen, azimuth, radD, radI, jday, patchchoice, patch_option, device +): """ This function calculates distribution of luminance on the skyvault based on Perez luminince distribution model. @@ -78,34 +80,54 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice, patch_option): :return: """ - m_a1 = np.array( - [1.3525, -1.2219, -1.1000, -0.5484, -0.6000, -1.0156, -1.0000, -1.0500] + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + + m_a1 = torch.tensor( + [ + 1.3525, + -1.2219, + -1.1000, + -0.5484, + -0.6000, + -1.0156, + -1.0000, + -1.0500, + ], + device=device, ) - m_a2 = np.array( - [-0.2576, -0.7730, -0.2515, -0.6654, -0.3566, -0.3670, 0.0211, 0.0289] + m_a2 = torch.tensor( + [-0.2576, -0.7730, -0.2515, -0.6654, -0.3566, -0.3670, 0.0211, 0.0289], + device=device, ) - m_a3 = np.array( - [-0.2690, 1.4148, 0.8952, -0.2672, -2.5000, 1.0078, 0.5025, 0.4260] + m_a3 = torch.tensor( + [-0.2690, 1.4148, 0.8952, -0.2672, -2.5000, 1.0078, 0.5025, 0.4260], + device=device, ) - m_a4 = np.array( - [-1.4366, 1.1016, 0.0156, 0.7117, 2.3250, 1.4051, -0.5119, 0.3590] + m_a4 = torch.tensor( + [-1.4366, 1.1016, 0.0156, 0.7117, 2.3250, 1.4051, -0.5119, 0.3590], + device=device, ) - m_b1 = np.array( - [-0.7670, -0.2054, 0.2782, 0.7234, 0.2937, 0.2875, -0.3000, -0.3250] + m_b1 = torch.tensor( + [-0.7670, -0.2054, 0.2782, 0.7234, 0.2937, 0.2875, -0.3000, -0.3250], + device=device, ) - m_b2 = np.array( - [0.0007, 0.0367, -0.1812, -0.6219, 0.0496, -0.5328, 0.1922, 0.1156] + m_b2 = torch.tensor( + [0.0007, 0.0367, -0.1812, -0.6219, 0.0496, -0.5328, 0.1922, 0.1156], + device=device, ) - m_b3 = np.array( - [1.2734, -3.9128, -4.5000, -5.6812, -5.6812, -3.8500, 0.7023, 0.7781] + m_b3 = torch.tensor( + [1.2734, -3.9128, -4.5000, -5.6812, -5.6812, -3.8500, 0.7023, 0.7781], + device=device, ) - m_b4 = np.array( - [-0.1233, 0.9156, 1.1766, 2.6297, 1.8415, 3.3750, -1.6317, 0.0025] + m_b4 = torch.tensor( + [-0.1233, 0.9156, 1.1766, 2.6297, 1.8415, 3.3750, -1.6317, 0.0025], + device=device, ) - m_c1 = np.array( - [2.8000, 6.9750, 24.7219, 33.3389, 21.0000, 14.0000, 19.0000, 31.0625] + m_c1 = torch.tensor( + [2.8000, 6.9750, 24.7219, 33.3389, 21.0000, 14.0000, 19.0000, 31.0625], + device=device, ) - m_c2 = np.array( + m_c2 = torch.tensor( [ 0.6004, 0.1774, @@ -115,9 +137,10 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice, patch_option): -0.9999, -5.0000, -14.5000, - ] + ], + device=device, ) - m_c3 = np.array( + m_c3 = torch.tensor( [ 1.2375, 6.4477, @@ -127,106 +150,113 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice, patch_option): -7.1406, 1.2438, -46.1148, - ] + ], + device=device, ) - m_c4 = np.array( - [1.0000, -0.1239, 34.8438, 52.0781, 7.2492, 7.5469, -1.9094, 55.3750] + m_c4 = torch.tensor( + [1.0000, -0.1239, 34.8438, 52.0781, 7.2492, 7.5469, -1.9094, 55.3750], + device=device, ) - m_d1 = np.array( - [1.8734, -1.5798, -5.0000, -3.5000, -3.5000, -3.4000, -4.0000, -7.2312] + m_d1 = torch.tensor( + [ + 1.8734, + -1.5798, + -5.0000, + -3.5000, + -3.5000, + -3.4000, + -4.0000, + -7.2312, + ], + device=device, ) - m_d2 = np.array( - [0.6297, -0.5081, 1.5218, 0.0016, -0.1554, -0.1078, 0.0250, 0.4050] + m_d2 = torch.tensor( + [0.6297, -0.5081, 1.5218, 0.0016, -0.1554, -0.1078, 0.0250, 0.4050], + device=device, ) - m_d3 = np.array( - [0.9738, -1.7812, 3.9229, 1.1477, 1.4062, -1.0750, 0.3844, 13.3500] + m_d3 = torch.tensor( + [0.9738, -1.7812, 3.9229, 1.1477, 1.4062, -1.0750, 0.3844, 13.3500], + device=device, ) - m_d4 = np.array( - [0.2809, 0.1080, -2.6204, 0.1062, 0.3988, 1.5702, 0.2656, 0.6234] + m_d4 = torch.tensor( + [0.2809, 0.1080, -2.6204, 0.1062, 0.3988, 1.5702, 0.2656, 0.6234], + device=device, ) - m_e1 = np.array( - [0.0356, 0.2624, -0.0156, 0.4659, 0.0032, -0.0672, 1.0468, 1.5000] + m_e1 = torch.tensor( + [0.0356, 0.2624, -0.0156, 0.4659, 0.0032, -0.0672, 1.0468, 1.5000], + device=device, ) - m_e2 = np.array( - [-0.1246, 0.0672, 0.1597, -0.3296, 0.0766, 0.4016, -0.3788, -0.6426] + m_e2 = torch.tensor( + [-0.1246, 0.0672, 0.1597, -0.3296, 0.0766, 0.4016, -0.3788, -0.6426], + device=device, ) - m_e3 = np.array( - [-0.5718, -0.2190, 0.4199, -0.0876, -0.0656, 0.3017, -2.4517, 1.8564] + m_e3 = torch.tensor( + [-0.5718, -0.2190, 0.4199, -0.0876, -0.0656, 0.3017, -2.4517, 1.8564], + device=device, ) - m_e4 = np.array( - [0.9938, -0.4285, -0.5562, -0.0329, -0.1294, -0.4844, 1.4656, 0.5636] + m_e4 = torch.tensor( + [0.9938, -0.4285, -0.5562, -0.0329, -0.1294, -0.4844, 1.4656, 0.5636], + device=device, ) - acoeff = np.transpose(np.atleast_2d([m_a1, m_a2, m_a3, m_a4])) - bcoeff = np.transpose(np.atleast_2d([m_b1, m_b2, m_b3, m_b4])) - ccoeff = np.transpose(np.atleast_2d([m_c1, m_c2, m_c3, m_c4])) - dcoeff = np.transpose(np.atleast_2d([m_d1, m_d2, m_d3, m_d4])) - ecoeff = np.transpose(np.atleast_2d([m_e1, m_e2, m_e3, m_e4])) + acoeff = torch.stack([m_a1, m_a2, m_a3, m_a4], dim=1) + bcoeff = torch.stack([m_b1, m_b2, m_b3, m_b4], dim=1) + ccoeff = torch.stack([m_c1, m_c2, m_c3, m_c4], dim=1) + dcoeff = torch.stack([m_d1, m_d2, m_d3, m_d4], dim=1) + ecoeff = torch.stack([m_e1, m_e2, m_e3, m_e4], dim=1) - deg2rad = np.pi / 180 - rad2deg = 180 / np.pi + deg2rad = torch.tensor(torch.pi / 180, device=device).clone().detach() + rad2deg = torch.tensor(180 / torch.pi, device=device).clone().detach() altitude = 90 - zen - zen = zen * deg2rad - azimuth = azimuth * deg2rad - altitude = altitude * deg2rad + zen = torch.tensor(zen, device=device) * deg2rad + azimuth = torch.tensor(azimuth, device=device) * deg2rad + altitude = torch.tensor(altitude, device=device) * deg2rad Idh = radD - # Ibh = radI/sin(altitude) Ibn = radI - # Skyclearness - PerezClearness = ((Idh + Ibn) / (Idh + 1.041 * np.power(zen, 3))) / ( - 1 + 1.041 * np.power(zen, 3) + PerezClearness = ((Idh + Ibn) / (Idh + 1.041 * torch.pow(zen, 3))) / ( + 1 + 1.041 * torch.pow(zen, 3) ) - # Extra terrestrial radiation - day_angle = jday * 2 * np.pi / 365 - # I0=1367*(1+0.033*np.cos((2*np.pi*jday)/365)) + + day_angle = jday * 2 * torch.pi / 365 I0 = 1367 * ( 1.00011 - + 0.034221 * np.cos(day_angle) - + 0.00128 * np.sin(day_angle) - + 0.000719 * np.cos(2 * day_angle) - + 0.000077 * np.sin(2 * day_angle) - ) # New from robinsson + + 0.034221 * torch.cos(torch.tensor(day_angle)) + + 0.00128 * torch.sin(torch.tensor(day_angle)) + + 0.000719 * torch.cos(2 * torch.tensor(day_angle)) + + 0.000077 * torch.sin(2 * torch.tensor(day_angle)) + ) - # Optical air mass - # m=1/altitude; old if altitude >= 10 * deg2rad: - AirMass = 1 / np.sin(altitude) - elif altitude < 0: # below equation becomes complex - AirMass = 1 / np.sin(altitude) + 0.50572 * np.power( - 180 * complex(altitude) / np.pi + 6.07995, -1.6364 + AirMass = 1 / torch.sin(altitude) + elif altitude < 0: + AirMass = 1 / torch.sin(altitude) + 0.50572 * torch.pow( + 180 * torch.complex(altitude, 0) / torch.pi + 6.07995, -1.6364 ) else: - AirMass = 1 / np.sin(altitude) + 0.50572 * np.power( - 180 * altitude / np.pi + 6.07995, -1.6364 + AirMass = 1 / torch.sin(altitude) + 0.50572 * torch.pow( + 180 * altitude / torch.pi + 6.07995, -1.6364 ) - # Skybrightness - # if altitude*rad2deg+6.07995>=0 - PerezBrightness = (AirMass * radD) / I0 + PerezBrightness = (AirMass * Idh) / I0 if Idh <= 10: - # m_a=0;m_b=0;m_c=0;m_d=0;m_e=0; - PerezBrightness = 0 - # if altitude < 0: - # print("Airmass") - # print(AirMass) - # print(PerezBrightness) - # sky clearness bins + PerezBrightness = torch.tensor(0.0, device=device) + if PerezClearness < 1.065: intClearness = 0 - if PerezClearness > 1.065 and PerezClearness < 1.230: + elif PerezClearness < 1.230: intClearness = 1 - if PerezClearness > 1.230 and PerezClearness < 1.500: + elif PerezClearness < 1.500: intClearness = 2 - if PerezClearness > 1.500 and PerezClearness < 1.950: + elif PerezClearness < 1.950: intClearness = 3 - if PerezClearness > 1.950 and PerezClearness < 2.800: + elif PerezClearness < 2.800: intClearness = 4 - if PerezClearness > 2.800 and PerezClearness < 4.500: + elif PerezClearness < 4.500: intClearness = 5 - if PerezClearness > 4.500 and PerezClearness < 6.200: + elif PerezClearness < 6.200: intClearness = 6 - if PerezClearness > 6.200: + else: intClearness = 7 m_a = ( @@ -262,10 +292,9 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice, patch_option): * (dcoeff[intClearness, 2] + dcoeff[intClearness, 3] * zen) ) else: - # different equations for c & d in clearness bin no. 1, from Robinsson m_c = ( - np.exp( - np.power( + torch.exp( + torch.pow( PerezBrightness * ( ccoeff[intClearness, 0] + ccoeff[intClearness, 1] * zen @@ -276,7 +305,7 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice, patch_option): - 1 ) m_d = ( - -np.exp( + -torch.exp( PerezBrightness * (dcoeff[intClearness, 0] + dcoeff[intClearness, 1] * zen) ) @@ -284,61 +313,39 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice, patch_option): + PerezBrightness * dcoeff[intClearness, 3] * PerezBrightness ) - # print 'a = ', m_a - # print 'b = ', m_b - # print 'e = ', m_e - # print 'c = ', m_c - # print 'd = ', m_d - if patchchoice == 2: - skyvaultalt = np.atleast_2d([]) - skyvaultazi = np.atleast_2d([]) - # Creating skyvault at one degree intervals - skyvaultalt = np.ones([90, 361]) * 90 - skyvaultazi = np.empty((90, 361)) + skyvaultalt = torch.zeros((90, 361), device=device) + skyvaultazi = torch.zeros((90, 361), device=device) for j in range(90): skyvaultalt[j, :] = 91 - j - skyvaultazi[j, :] = range(361) + skyvaultazi[j, :] = torch.arange(361) elif patchchoice == 1: - # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) - skyvaultalt, skyvaultazi, _, _, _, _, _ = create_patches(patch_option) + skyvaultalt, skyvaultazi, _, _, _, _, _ = create_patches( + patch_option, device, device + ) skyvaultzen = (90 - skyvaultalt) * deg2rad skyvaultalt = skyvaultalt * deg2rad skyvaultazi = skyvaultazi * deg2rad - # Angular distance from the sun from Robinsson - cosSkySunAngle = np.sin(skyvaultalt) * np.sin(altitude) + np.cos( + cosSkySunAngle = torch.sin(skyvaultalt) * torch.sin(altitude) + torch.cos( altitude - ) * np.cos(skyvaultalt) * np.cos(np.abs(skyvaultazi - azimuth)) + ) * torch.cos(skyvaultalt) * torch.cos(torch.abs(skyvaultazi - azimuth)) - # Main equation - lv = (1 + m_a * np.exp(m_b / np.cos(skyvaultzen))) * ( + lv = (1 + m_a * torch.exp(m_b / torch.cos(skyvaultzen))) * ( ( 1 - + m_c * np.exp(m_d * np.arccos(cosSkySunAngle)) + + m_c * torch.exp(m_d * torch.arccos(cosSkySunAngle)) + m_e * cosSkySunAngle * cosSkySunAngle ) ) - # Normalisation - lv = lv / np.sum(lv) - - # plotting - # axesm('stereo','Origin',[90 180],'MapLatLimit',[0 90],'Aspect','transverse') - # framem off; gridm on; mlabel off; plabel off;axis on; - # setm(gca,'MLabelParallel',-20) - # geoshow(skyvaultalt*rad2deg,skyvaultazi*rad2deg,lv,'DisplayType','texture'); - # colorbar - # set(gcf,'Color',[1 1 1]) - # pause(1) + lv = lv / torch.sum(lv) if patchchoice == 1: - # x = np.atleast_2d([]) - # lv = np.transpose(np.append(np.append(np.append(x, skyvaultalt*rad2deg), skyvaultazi*rad2deg), lv)) - x = np.transpose(np.atleast_2d(skyvaultalt * rad2deg)) - y = np.transpose(np.atleast_2d(skyvaultazi * rad2deg)) - z = np.transpose(np.atleast_2d(lv)) - lv = np.append(np.append(x, y, axis=1), z, axis=1) + x = torch.transpose(torch.unsqueeze(skyvaultalt * rad2deg, 0), 0, 1) + y = torch.transpose(torch.unsqueeze(skyvaultazi * rad2deg, 0), 0, 1) + z = torch.transpose(torch.unsqueeze(lv, 0), 0, 1) + lv = torch.cat((x, y, z), dim=1) return lv, PerezClearness, PerezBrightness diff --git a/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload.py b/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload.py index cf01274..551e12b 100644 --- a/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload.py +++ b/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload.py @@ -4,7 +4,6 @@ from . import sun_position as sp # import sun_position as sp -import numpy as np import datetime import calendar import torch @@ -22,9 +21,11 @@ def Solweig_2015a_metdata_noload(inputdata, location, UTC): """ met = inputdata - device = met.device if isinstance(met, torch.Tensor) else torch.device("cpu") + device = ( + met.device if isinstance(met, torch.Tensor) else torch.device("cpu") + ) data_len = len(met[:, 0]) - dectime = met[:, 1] + met[:, 2] / 24 + met[:, 3] / (60 * 24.0) + dectime = torch.tensor(met[:, 1] + met[:, 2] / 24 + met[:, 3] / (60 * 24.0)) dectimemin = met[:, 3] / (60 * 24.0) if data_len == 1: halftimestepdec = 0 @@ -55,7 +56,9 @@ def Solweig_2015a_metdata_noload(inputdata, location, UTC): int(met[i, 1]) - 1 ) # Finding maximum altitude in 15 min intervals (20141027) - if (i == 0) or (torch.remainder(dectime[i], torch.floor(dectime[i])) == 0): + if (i == 0) or ( + torch.remainder(dectime[i], torch.floor(dectime[i])) == 0 + ): fifteen = 0.0 sunmaximum = -90.0 sunmax["zenith"] = 90.0 @@ -94,12 +97,18 @@ def Solweig_2015a_metdata_noload(inputdata, location, UTC): # day of year and check for leap year if calendar.isleap(time["year"]): - dayspermonth = torch.atleast_2d(torch.tensor( - [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], device=device) + dayspermonth = torch.atleast_2d( + torch.tensor( + [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], + device=device, + ) ) else: - dayspermonth = torch.atleast_2d(torch.tensor( - [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], device=device) + dayspermonth = torch.atleast_2d( + torch.tensor( + [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], + device=device, + ) ) # jday[0, i] = torch.sum(dayspermonth[0, 0:time['month']-1]) + time['day'] # bug when a new day 20191015 YYYY[0, i] = met[i, 0] diff --git a/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b.py b/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b.py index 004c507..eca194f 100644 --- a/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b.py +++ b/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b.py @@ -31,7 +31,9 @@ def clearnessindex_2013b(zen, jday, Ta, RH, radG, location, P): jday ) # irradiance differences due to Sun-Earth distances m = ( - 35.0 * torch.cos(zen) * ((1224.0 * (torch.cos(zen) ** 2) + 1) ** (-1 / 2.0)) + 35.0 + * torch.cos(zen) + * ((1224.0 * (torch.cos(zen) ** 2) + 1) ** (-1 / 2.0)) ) # optical air mass at p=1013 Trpg = ( 1.021 - 0.084 * (m * (0.000949 * p + 0.051)) ** 0.5 @@ -65,7 +67,9 @@ def clearnessindex_2013b(zen, jday, Ta, RH, radG, location, P): G = G[2] elif jday > 244 and jday <= 335: G = G[3] - device = zen.device if isinstance(zen, torch.Tensor) else torch.device("cpu") + device = ( + zen.device if isinstance(zen, torch.Tensor) else torch.device("cpu") + ) G = torch.tensor(G, device=device) # dewpoint calculation @@ -75,7 +79,9 @@ def clearnessindex_2013b(zen, jday, Ta, RH, radG, location, P): a2 - (((a2 * Ta) / (b2 + Ta)) + torch.log(RH)) ) Td = (Td * 1.8) + 32 # Dewpoint (F) - u = torch.exp(0.1133 - torch.log(G + 1) + 0.0393 * Td) # Precipitable water + u = torch.exp( + 0.1133 - torch.log(G + 1) + 0.0393 * Td + ) # Precipitable water Tw = 1 - 0.077 * ( (u * m) ** 0.3 ) # Transmission coefficient for water vapor diff --git a/util/SEBESOLWEIGCommonFiles/create_patches.py b/util/SEBESOLWEIGCommonFiles/create_patches.py index 85a26fc..02f0e0f 100644 --- a/util/SEBESOLWEIGCommonFiles/create_patches.py +++ b/util/SEBESOLWEIGCommonFiles/create_patches.py @@ -1,55 +1,73 @@ -import numpy as np +import torch -def create_patches(patch_option): - deg2rad = np.pi / 180 +def create_patches(patch_option, device): + + deg2rad = torch.pi / 180 # patch_option = 1 = 145 patches (Robinson & Stone, 2004) # patch_option = 2 = 153 patches (Wallenberg et al., 2022) # patch_option = 3 = 306 patches -> test # patch_option = 4 = 612 patches -> test - skyvaultalt = np.atleast_2d([]) - skyvaultazi = np.atleast_2d([]) + skyvaultalt = torch.atleast_2d([]) + skyvaultazi = torch.atleast_2d([]) # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) # Patch option 1, 145 patches, Original Robinson & Stone (2004) after Tregenza (1987)/Tregenza & Sharples (1993) if patch_option == 1: - annulino = np.array([0, 12, 24, 36, 48, 60, 72, 84, 90]) - skyvaultaltint = np.array( - [6, 18, 30, 42, 54, 66, 78, 90] + annulino = torch.tensor( + [0, 12, 24, 36, 48, 60, 72, 84, 90], device=device + ) + skyvaultaltint = torch.tensor( + [6, 18, 30, 42, 54, 66, 78, 90], device=device ) # Robinson & Stone (2004) - azistart = np.array([0, 4, 2, 5, 8, 0, 10, 0]) # Fredrik/Nils - patches_in_band = np.array( - [30, 30, 24, 24, 18, 12, 6, 1] + azistart = torch.tensor( + [0, 4, 2, 5, 8, 0, 10, 0], device=device + ) # Fredrik/Nils + patches_in_band = torch.tensor( + [30, 30, 24, 24, 18, 12, 6, 1], device=device ) # Robinson & Stone (2004) # Patch option 2, 153 patches, Wallenberg et al. (2022) elif patch_option == 2: - annulino = np.array([0, 12, 24, 36, 48, 60, 72, 84, 90]) - skyvaultaltint = np.array( - [6, 18, 30, 42, 54, 66, 78, 90] + annulino = torch.tensor( + [0, 12, 24, 36, 48, 60, 72, 84, 90], device=device + ) + skyvaultaltint = torch.tensor( + [6, 18, 30, 42, 54, 66, 78, 90], device=device ) # Robinson & Stone (2004) - azistart = np.array([0, 4, 2, 5, 8, 0, 10, 0]) # Fredrik/Nils - patches_in_band = np.array([31, 30, 28, 24, 19, 13, 7, 1]) # Nils + azistart = torch.tensor( + [0, 4, 2, 5, 8, 0, 10, 0], device=device + ) # Fredrik/Nils + patches_in_band = torch.tensor( + [31, 30, 28, 24, 19, 13, 7, 1], device=device + ) # Nils # Patch option 3, 306 patches, test elif patch_option == 3: - annulino = np.array([0, 12, 24, 36, 48, 60, 72, 84, 90]) - skyvaultaltint = np.array( - [6, 18, 30, 42, 54, 66, 78, 90] + annulino = torch.tensor( + [0, 12, 24, 36, 48, 60, 72, 84, 90], device=device + ) + skyvaultaltint = torch.tensor( + [6, 18, 30, 42, 54, 66, 78, 90], device=device ) # Robinson & Stone (2004) - azistart = np.array([0, 4, 2, 5, 8, 0, 10, 0]) # Fredrik/Nils - patches_in_band = np.array( - [31 * 2, 30 * 2, 28 * 2, 24 * 2, 19 * 2, 13 * 2, 7 * 2, 1] + azistart = torch.tensor( + [0, 4, 2, 5, 8, 0, 10, 0], device=device + ) # Fredrik/Nils + patches_in_band = torch.tensor( + [31 * 2, 30 * 2, 28 * 2, 24 * 2, 19 * 2, 13 * 2, 7 * 2, 1], + device=device, ) # Nils # Patch option 4, 612 patches, test elif patch_option == 4: - annulino = np.array( - [0, 4.5, 9, 15, 21, 27, 33, 39, 45, 51, 57, 63, 69, 75, 81, 90] + annulino = torch.tensor( + [0, 4.5, 9, 15, 21, 27, 33, 39, 45, 51, 57, 63, 69, 75, 81, 90], + device=device, ) # Nils - skyvaultaltint = np.array( - [3, 9, 15, 21, 27, 33, 39, 45, 51, 57, 63, 69, 75, 81, 90] + skyvaultaltint = torch.tensor( + [3, 9, 15, 21, 27, 33, 39, 45, 51, 57, 63, 69, 75, 81, 90], + device=device, ) # Nils - patches_in_band = np.array( + patches_in_band = torch.tensor( [ 31 * 2, 31 * 2, @@ -66,24 +84,21 @@ def create_patches(patch_option): 7 * 2, 7 * 2, 1, - ] + ], + device=device, ) # Nils - azistart = np.array( - [0, 0, 4, 4, 2, 2, 5, 5, 8, 8, 0, 0, 10, 10, 0] + azistart = torch.tensor( + [0, 0, 4, 4, 2, 2, 5, 5, 8, 8, 0, 0, 10, 10, 0], device=device ) # Nils - skyvaultaziint = np.array([360 / patches for patches in patches_in_band]) + skyvaultaziint = torch.tensor( + [360 / patches for patches in patches_in_band] + ) for j in range(0, skyvaultaltint.shape[0]): for k in range(0, patches_in_band[j]): - skyvaultalt = np.append(skyvaultalt, skyvaultaltint[j]) - skyvaultazi = np.append( - skyvaultazi, k * skyvaultaziint[j] + azistart[j] - ) - - # skyvaultzen = (90 - skyvaultalt) * deg2rad - # skyvaultalt = skyvaultalt * deg2rad - # skyvaultazi = skyvaultazi * deg2rad + skyvaultalt = skyvaultalt + (skyvaultaltint[j],) + skyvaultazi = skyvaultazi + (k * skyvaultaziint[j] + azistart[j],) return ( skyvaultalt, diff --git a/util/SEBESOLWEIGCommonFiles/diffusefraction.py b/util/SEBESOLWEIGCommonFiles/diffusefraction.py index e033db6..5c7970d 100644 --- a/util/SEBESOLWEIGCommonFiles/diffusefraction.py +++ b/util/SEBESOLWEIGCommonFiles/diffusefraction.py @@ -1,5 +1,4 @@ from __future__ import division -import numpy as np import torch @@ -45,7 +44,10 @@ def diffusefraction(radG, altitude, Kt, Ta, RH): ) else: radD = radG * ( - 0.426 * Kt - 0.256 * torch.sin(alfa) + 0.00349 * Ta + 0.0734 * RH + 0.426 * Kt + - 0.256 * torch.sin(alfa) + + 0.00349 * Ta + + 0.0734 * RH ) radI = (radG - radD) / (torch.sin(alfa)) diff --git a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py index 8ac7ae0..1d08ca6 100644 --- a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py +++ b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py @@ -1,47 +1,56 @@ # -*- coding: utf-8 -*- from __future__ import division -import numpy as np from math import radians +import torch +import torch.nn.functional as F -# from scipy.ndimage.filters import median_filter +def shade_on_walls(azimuth, aspect, walls, dsm, f, device): + """ + Calculates shadow heights and lighting states on building facades using vector geometry. + + This function determines wall segments that are self-shadowed (facing away from the sun) + using the cosine of the angle difference, tracks the down-ray building shadow volume, + and isolates sections of the walls that remain illuminated. + + Args: + azimuth (float): Sun azimuth angle in radians. + aspect (torch.Tensor): Aspect orientation of the walls in radians. + walls (torch.Tensor): Height profile of the pixels representing building walls. + dsm (torch.Tensor): Digital Surface Model (ground + building heights). + f (torch.Tensor): Maximum building shadow volume tracked during the ray-tracing loop. + device (torch.device): The PyTorch device (CPU/GPU) where computations occur. + + Returns: + tuple: A tuple containing: + - sh (torch.Tensor): Binary ground and roof shadow mask (1 = sun, 0 = shadow). + - wallsh (torch.Tensor): Height of the wall that is in shadow (meters). + - wallsun (torch.Tensor): Height of the wall that is in direct sun (meters). + - facesh (torch.Tensor): Binary mask of walls in self-shadow (1 = shadowed by self). + - facesun (torch.Tensor): Binary mask of walls facing the sun and not occluded. + """ + # If the cosine of the angle difference is <= 0, the facade faces away from the sun (self-shadow) + cos_incidence = torch.cos(aspect - azimuth) + facesh = (cos_incidence <= 0).float() * (walls > 0).float() -def shade_on_walls(azimuth, aspect, walls, dsm, f): - # wall shadows wall parameterization - wallbol = (walls > 0).astype(float) + sh = f - dsm # Shadow volume (height of cast shadow) - # Removing walls in shadow due to selfshadowing - azilow = azimuth - np.pi / 2 - azihigh = azimuth + np.pi / 2 + # Facades facing the sun (facesh == 0) that are valid walls + facesun = ((facesh == 0) & (walls > 0)).float() - if azilow >= 0 and azihigh < 2 * np.pi: # 90 to 270 (SHADOW) - facesh = ( - np.logical_or(aspect < azilow, aspect >= azihigh).astype(float) - - wallbol - + 1 - ) - elif azilow < 0 and azihigh <= 2 * np.pi: # 0 to 90 - azilow = azilow + 2 * np.pi - facesh = ( - np.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 - ) # (SHADOW) # check for the -1 - elif azilow > 0 and azihigh >= 2 * np.pi: # 270 to 360 - azihigh = azihigh - 2 * np.pi - facesh = ( - np.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 - ) # (SHADOW) - - sh = np.copy(f - dsm) # shadow volume - facesun = np.logical_and( - facesh + (walls > 0).astype(float) == 1, walls > 0 - ).astype(float) - wallsun = np.copy(walls - sh) - wallsun[wallsun < 0] = 0 - wallsun[facesh == 1] = 0 # Removing walls in "self"-shadow - wallsh = np.copy(walls - wallsun) - - sh = np.logical_not(np.logical_not(sh)).astype(float) - sh = sh * -1 + 1 + # Calculate the height of the wall segment receiving direct sunlight + wallsun = walls - sh + wallsun = torch.clamp(wallsun, min=0.0) + wallsun = torch.where( + facesh == 1, torch.tensor(0.0, device=device), wallsun + ) + + # The shadowed wall height is the total wall height minus the sunlit segment + wallsh = walls - wallsun + + # Transform the cast shadow volume into a binary mask (0 = shadow, 1 = sun) + sh = (sh > 0).float() + sh = 1.0 - sh return sh, wallsh, wallsun, facesh, facesun @@ -57,150 +66,98 @@ def shadowingfunction_wallheight_13( aspect_scheme=False, ): """ - This m.file calculates shadows on a DSM and shadow height on building - walls. - - INPUTS: - a = DSM - azimuth and altitude = sun position - scale= scale of DSM (1 meter pixels=1, 2 meter pixels=0.5) - walls= pixel row 'outside' buildings. will be calculated if empty - aspect = normal aspect of buildings walls - - OUTPUT: - sh=ground and roof shadow - wallsh = height of wall that is in shadow - wallsun = hieght of wall that is in sun - - Fredrik Lindberg 2012-03-19 - fredrikl@gvc.gu.se - - Utdate 2013-03-13 - bugfix for walls alinged with sun azimuths - - :param a: - :param azimuth: - :param altitude: - :param scale: - :param walls: - :param aspect: - :return: + Computes ground/roof shadows and shadow heights on building walls using a DSM. + + This function leverages hardware-accelerated grid sampling (`F.grid_sample`) in PyTorch + to simulate solar ray tracing. It avoids explicit multi-index pixel slicing or nested + loops, making it optimal for high-performance GPU execution. + + Args: + a (torch.Tensor): Digital Surface Model (DSM) matrix. + azimuth (float): Sun azimuth angle in degrees. + altitude (float): Sun altitude angle in degrees. + scale (float): Map scale modifier (e.g., 1 pixel = 1 meter -> 1.0; 2 meters -> 0.5). + walls (torch.Tensor): Extruded standalone wall pixel heights. + aspect (torch.Tensor): Surface normal orientation of the walls in radians. + walls_scheme (torch.Tensor, optional): Alternative building scheme layout. Defaults to False. + aspect_scheme (torch.Tensor, optional): Alternative building scheme wall orientation. Defaults to False. + + Returns: + tuple: Depending on whether `walls_scheme` is provided, returns: + - Standard: (sh, wallsh, wallsun, facesh, facesun) + - Scheme: (sh, wallsh, wallsun, facesh, facesun, shade_on_wall) """ + # Automatically detect the execution device (CPU or GPU) from the input tensor + device = a.device if isinstance(a, torch.Tensor) else torch.device("cpu") - if not walls.size: - """ - walls = ordfilt2(a,4,[0 1 0; 1 0 1; 0 1 0]) - walls = walls-a - walls[walls < 3]=0 - sizex = np.shape(a)[0] #might be wrong - sizey = np.shape(a)[1] - dirwalls = filter1Goodwin_as_aspect_v3(walls,sizex,sizey,scale,a); - aspect = dirwalls*np.pi/180 - """ - - # conversion - # degrees = np.pi/180 + # Convert angular sun coordinates to radians azimuth = radians(azimuth) altitude = radians(altitude) - # measure the size of the image - sizex = np.shape(a)[0] - sizey = np.shape(a)[1] - - # initialise parameters - f = np.copy(a) - dx = 0 - dy = 0 - dz = 0 - temp = np.zeros((sizex, sizey)) - wallbol = (walls > 0).astype(float) - - # other loop parameters - amaxvalue = np.max(a) - pibyfour = np.pi / 4 - threetimespibyfour = 3 * pibyfour - fivetimespibyfour = 5 * pibyfour - seventimespibyfour = 7 * pibyfour - sinazimuth = np.sin(azimuth) - cosazimuth = np.cos(azimuth) - tanazimuth = np.tan(azimuth) - signsinazimuth = np.sign(sinazimuth) - signcosazimuth = np.sign(cosazimuth) - dssin = np.abs(1 / sinazimuth) - dscos = np.abs(1 / cosazimuth) - tanaltitudebyscale = np.tan(altitude) / scale - - index = 1 - - # main loop - while (amaxvalue >= dz) and (np.abs(dx) < sizex) and (np.abs(dy) < sizey): - - if (pibyfour <= azimuth and azimuth < threetimespibyfour) or ( - fivetimespibyfour <= azimuth and azimuth < seventimespibyfour - ): - dy = signsinazimuth * index - dx = -1 * signcosazimuth * np.abs(np.round(index / tanazimuth)) - ds = dssin - else: - dy = signsinazimuth * np.abs(np.round(index * tanazimuth)) - dx = -1 * signcosazimuth * index - ds = dscos - - # note: dx and dy represent absolute values while ds is an incremental value - dz = ds * index * tanaltitudebyscale - temp[0:sizex, 0:sizey] = 0 - - absdx = np.abs(dx) - absdy = np.abs(dy) - - xc1 = int((dx + absdx) / 2) - xc2 = int(sizex + (dx - absdx) / 2) - yc1 = int((dy + absdy) / 2) - yc2 = int(sizey + (dy - absdy) / 2) - - xp1 = int(-((dx - absdx) / 2)) - xp2 = int(sizex - (dx + absdx) / 2) - yp1 = int(-((dy - absdy) / 2)) - yp2 = int(sizey - (dy + absdy) / 2) - - temp[xp1:xp2, yp1:yp2] = a[xc1:xc2, yc1:yc2] - dz - f = np.fmax(f, temp) # Moving building shadow - - index = index + 1 - - # # Removing walls in shadow due to selfshadowing - # azilow = azimuth-np.pi/2 - # azihigh = azimuth+np.pi/2 - - # if azilow >= 0 and azihigh < 2*np.pi: # 90 to 270 (SHADOW) - # facesh = (np.logical_or(aspect < azilow, aspect >= azihigh).astype(float)-wallbol+1) - # elif azilow < 0 and azihigh <= 2*np.pi: # 0 to 90 - # azilow = azilow + 2*np.pi - # facesh = np.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 # (SHADOW) # check for the -1 - # elif azilow > 0 and azihigh >= 2*np.pi: # 270 to 360 - # azihigh = azihigh-2*np.pi - # facesh = np.logical_or(aspect > azilow, aspect <= azihigh)*-1 + 1 # (SHADOW) - - # sh = np.copy(f-a) # shadow volume - # facesun = np.logical_and(facesh + (walls > 0).astype(float) == 1, walls > 0).astype(float) - # wallsun = np.copy(walls-sh) - # wallsun[wallsun < 0] = 0 - # wallsun[facesh == 1] = 0 # Removing walls in "self"-shadow - # wallsh = np.copy(walls-wallsun) - - # sh = np.logical_not(np.logical_not(sh)).astype(float) - # sh = sh * -1 + 1 + # Extract map grid spatial dimensions + sizex, sizey = a.shape + + # Clone the DSM matrix to track shifting horizon shadows + f = a.clone() + # --- COORDINATE GRID GENERATION FOR GLOBAL TRANSFORMATION --- + y, x = torch.meshgrid( + torch.linspace(-1, 1, sizex, device=device), + torch.linspace(-1, 1, sizey, device=device), + indexing="ij", + ) + # Reshape grid layout to PyTorch standard: (1, H, W, 2) with (X, Y) ordered at dim=-1 + grille_base = torch.stack((x, y), dim=-1).unsqueeze(0) + + # Prepare 4D-batched representation of the DSM for the sampling engine (1, 1, H, W) + a_4d = a.unsqueeze(0).unsqueeze(0) + + # Extract geometric limits and spatial sun vector components + amaxvalue = torch.max(a) + sinazimuth = torch.sin(azimuth) + cosazimuth = torch.cos(azimuth) + tanaltitudebyscale = torch.tan(altitude) / scale + + # Dynamically compute the maximum loop range needed based on terrain peak height + max_steps = int(amaxvalue / tanaltitudebyscale) + 2 + + # Core Ray-Tracing Loop (Accelerated using structural translation layers) + for index in range(1, max_steps): + # 1. Project physical shadow displacement on the horizontal plane (in pixels) + shift_x = index * sinazimuth / scale + shift_y = index * cosazimuth / scale + dz = index * tanaltitudebyscale + + # Early termination checkpoint if the casting rays clear the map canvas bounds + if abs(shift_x) >= sizey and abs(shift_y) >= sizex: + break + + # 2. Shift the normalized coordinate sheet (-1 to 1 space) + grille_deplacee = grille_base.clone() + grille_deplacee[..., 0] -= (2.0 * shift_x) / (sizey - 1) + grille_deplacee[..., 1] -= (2.0 * shift_y) / (sizex - 1) + + # 3. Retrieve the shifted layer map natively using bilinear interpolation + temp = ( + F.grid_sample( + a_4d, grille_deplacee, mode="bilinear", padding_mode="border" + ).squeeze() + - dz + ) + + # 4. Amalgamate shadows via upper envelope tracking + f = torch.maximum(f, temp) + + # --- FACADE EVALUATION & SHADOW METRICS POST-PROCESSING --- sh, wallsh, wallsun, facesh, facesun = shade_on_walls( - azimuth, aspect, walls, a, f + azimuth, aspect, walls, a, f, device ) + if walls_scheme is not False: - sh_, wallsh_, wallsun_, facesh_, facesun_ = shade_on_walls( - azimuth, aspect_scheme, walls_scheme, a, f + _, wallsh_, _, _, _ = shade_on_walls( + azimuth, aspect_scheme, walls_scheme, a, f, device ) - shade_on_wall = wallsh_.copy() + shade_on_wall = wallsh_.clone() + return (sh, wallsh, wallsun, facesh, facesun, shade_on_wall) - return ( - (sh, wallsh, wallsun, facesh, facesun, shade_on_wall) - if walls_scheme is not False - else (sh, wallsh, wallsun, facesh, facesun) - ) + return (sh, wallsh, wallsun, facesh, facesun) diff --git a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py index 4a28224..72c2c43 100644 --- a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py +++ b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py @@ -1,54 +1,36 @@ from __future__ import division -import numpy as np import torch +import torch.nn.functional as F # import matplotlib.pylab as plt -def shade_on_walls(azimuth, aspect, walls, dsm, f, shvoveg): - # wall shadows wall parameterization - wallbol = (walls > 0).float() - - # Removing walls in shadow due to selfshadowing - azilow = azimuth - torch.pi / 2 - azihigh = azimuth + torch.pi / 2 - if azilow >= 0 and azihigh < 2 * torch.pi: # 90 to 270 (SHADOW) - facesh = ( - torch.logical_or(aspect < azilow, aspect >= azihigh).float() - - wallbol - + 1 - ) # TODO check - elif azilow < 0 and azihigh <= 2 * torch.pi: # 0 to 90 - azilow = azilow + 2 * torch.pi - facesh = ( - torch.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 - ) # (SHADOW) - elif azilow > 0 and azihigh >= 2 * torch.pi: # 270 to 360 - azihigh -= 2 * torch.pi - facesh = ( - torch.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 - ) # (SHADOW) - - shvo = f - dsm # building shadow volume - facesun = torch.logical_and( - facesh + (walls > 0).float() == 1, walls > 0 - ).float() - wallsun = torch.clone(walls - shvo) - wallsun[wallsun < 0] = 0 - wallsun[facesh == 1] = 0 # Removing walls in "self"-shadow - wallsh = torch.clone(walls - wallsun) - - wallshve = shvoveg * wallbol - wallshve = wallshve - wallsh - wallshve[wallshve < 0] = 0 - id = torch.where(wallshve > walls) - wallshve[id] = walls[id].float() - wallsun = wallsun - wallshve # problem with wallshve only - id = torch.where(wallsun < 0) - wallshve[id] = 0 - wallsun[id] = 0 - # if torch.sum(wallshve <= 0) == wallshve.size: - # wallshve[:, :] = 0 +def shade_on_walls(azimuth, aspect, walls, dsm, f, shvoveg, device): + cos_incidence = torch.cos(aspect - azimuth) + facesh = (cos_incidence <= 0).float() * (walls > 0).float() + + shvo = f - dsm + + facesun = ((facesh == 0) & (walls > 0)).float() + + wallsun = walls - shvo + wallsun = torch.clamp(wallsun, min=0.0) + wallsun = torch.where( + facesh == 1, torch.tensor(0.0, device=device), wallsun + ) + + wallsh = walls - wallsun + + wallshve = shvoveg * (walls > 0).float() + wallshve = torch.clamp(wallshve - wallsh, min=0.0) + + mask_clip = wallshve > walls + wallshve[mask_clip] = walls[mask_clip].to(dtype=wallshve.dtype) + + wallsun = wallsun - wallshve + mask_neg = wallsun < 0 + wallshve[mask_neg] = 0 + wallsun[mask_neg] = 0 return wallsh, wallsun, wallshve, facesh, facesun @@ -104,140 +86,141 @@ def shadowingfunction_wallheight_23( :return: """ - # conversion + # automatically get the device to use from the input device = a.device if isinstance(a, torch.Tensor) else torch.device("cpu") degrees = torch.pi / 180.0 azimuth *= degrees altitude *= degrees - # measure the size of the image - sizex = a.shape[0] - sizey = a.shape[1] - - # initialise parameters - dx = torch.tensor(0.0, device=device) - dy = torch.tensor(0.0, device=device) - dz = torch.tensor(0.0, device=device) - temp = torch.zeros((sizex, sizey), device=device) - tempvegdem = torch.zeros((sizex, sizey), device=device) - tempvegdem2 = torch.zeros((sizex, sizey), device=device) - templastfabovea = torch.zeros((sizex, sizey), device=device) - templastgabovea = torch.zeros((sizex, sizey), device=device) - bushplant = bush > 1 - sh = torch.zeros((sizex, sizey), device=device) # shadows from buildings - vbshvegsh = torch.clone(sh) # vegetation blocking buildings - vegsh = torch.add( - torch.zeros((sizex, sizey), device=device), bushplant - ).float() # vegetation shadow - f = torch.clone(a) - shvoveg = torch.clone(vegdem) # for vegetation shadowvolume - # g = torch.clone(sh) - wallbol = (walls > 0).float() - - # other loop parameters - pibyfour = torch.pi / 4 - threetimespibyfour = 3 * pibyfour - fivetimespibyfour = 5 * pibyfour - seventimespibyfour = 7 * pibyfour + sizex, sizey = a.shape + bushplant = (bush > 1).float() + + # Tensor initialisation + sh = torch.zeros((sizex, sizey), device=device) + vbshvegsh = torch.zeros((sizex, sizey), device=device) + vegsh = torch.zeros((sizex, sizey), device=device) + bushplant + f = a.clone() + shvoveg = vegdem.clone() + + # ---coordinates drag the layer --- + y, x = torch.meshgrid( + torch.linspace(-1, 1, sizex, device=device), + torch.linspace(-1, 1, sizey, device=device), + indexing="ij", + ) + grille_base = torch.stack((x, y), dim=-1).unsqueeze(0) + + # Preparation of the layers packed for grid_sample + a_4d = a.unsqueeze(0).unsqueeze(0).float() + veg_4d = vegdem.unsqueeze(0).unsqueeze(0).float() + veg2_4d = vegdem2.unsqueeze(0).unsqueeze(0).float() + + # Trigonometric params for the sun's step sinazimuth = torch.sin(azimuth) cosazimuth = torch.cos(azimuth) - tanazimuth = torch.tan(azimuth) - signsinazimuth = torch.sign(sinazimuth) - signcosazimuth = torch.sign(cosazimuth) - dssin = torch.abs(1 / sinazimuth) - dscos = torch.abs(1 / cosazimuth) tanaltitudebyscale = torch.tan(altitude) / scale - index = 0 - - # new case with pergola (thin vertical layer of vegetation), August 2021 - dzprev = 0 - - # main loop - while (amaxvalue >= dz) and (torch.abs(dx) < sizex) and (torch.abs(dy) < sizey): - if ((pibyfour <= azimuth) and (azimuth < threetimespibyfour)) or ( - (fivetimespibyfour <= azimuth) and (azimuth < seventimespibyfour) - ): - dy = signsinazimuth * index - dx = -1 * signcosazimuth * torch.abs(torch.round(index / tanazimuth)) - ds = dssin - else: - dy = signsinazimuth * torch.abs(torch.round(index * tanazimuth)) - dx = -1 * signcosazimuth * index - ds = dscos - - # note: dx and dy represent absolute values while ds is an incremental value - dz = (ds * index) * tanaltitudebyscale - tempvegdem[0:sizex, 0:sizey] = 0 - tempvegdem2[0:sizex, 0:sizey] = 0 - temp[0:sizex, 0:sizey] = 0 - templastfabovea[0:sizex, 0:sizey] = 0.0 - templastgabovea[0:sizex, 0:sizey] = 0.0 - absdx = torch.abs(dx) - absdy = torch.abs(dy) - xc1 = int((dx + absdx) / 2) - xc2 = int(sizex + (dx - absdx) / 2) - yc1 = int((dy + absdy) / 2) - yc2 = int(sizey + (dy - absdy) / 2) - xp1 = -int((dx - absdx) / 2) - xp2 = int(sizex - (dx + absdx) / 2) - yp1 = -int((dy - absdy) / 2) - yp2 = int(sizey - (dy + absdy) / 2) - - tempvegdem[xp1:xp2, yp1:yp2] = vegdem[xc1:xc2, yc1:yc2] - dz - tempvegdem2[xp1:xp2, yp1:yp2] = vegdem2[xc1:xc2, yc1:yc2] - dz - temp[xp1:xp2, yp1:yp2] = a[xc1:xc2, yc1:yc2] - dz - - f = torch.fmax(f, temp) # Moving building shadow - shvoveg = torch.fmax( - shvoveg, tempvegdem - ) # moving vegetation shadow volume - - sh[f > a] = 1 - sh[f <= a] = 0 - fabovea = (tempvegdem > a).int() # vegdem above DEM - gabovea = (tempvegdem2 > a).int() # vegdem2 above DEM - - # new pergola condition - templastfabovea[xp1:xp2, yp1:yp2] = vegdem[xc1:xc2, yc1:yc2] - dzprev - templastgabovea[xp1:xp2, yp1:yp2] = vegdem2[xc1:xc2, yc1:yc2] - dzprev + # Calculation of the maximum of steps + # We stop when the shadow exceed the max possible height + max_steps = int(amaxvalue / tanaltitudebyscale) + 2 + + # Facteur d'avancement du rayon (équivalent géométrique de ton ancien 'ds') + # Plus besoin de gros blocs If/Else selon les quadrants du soleil ! + delta_z_par_pas = tanaltitudebyscale + + for index in range(1, max_steps): + + # 1. Calculations of the slide + shift_x = index * sinazimuth / scale + shift_y = index * cosazimuth / scale + dz = index * delta_z_par_pas + + # Stop if the shadow is completely outside of the map + if abs(shift_x) >= sizey and abs(shift_y) >= sizex: + break + + # 2. Applying the offset to our normalized coordinate sheet (-1 to 1) + grille_deplacee = grille_base.clone() + grille_deplacee[..., 0] -= (2.0 * shift_x) / (sizey - 1) + grille_deplacee[..., 1] -= (2.0 * shift_y) / (sizex - 1) + + # 3. Global Material Sampling (Instant Layer Swipe) + temp = ( + F.grid_sample( + a_4d, grille_deplacee, mode="bilinear", padding_mode="border" + ).squeeze() + - dz + ) + tempvegdem = ( + F.grid_sample( + veg_4d, grille_deplacee, mode="bilinear", padding_mode="border" + ).squeeze() + - dz + ) + tempvegdem2 = ( + F.grid_sample( + veg2_4d, + grille_deplacee, + mode="bilinear", + padding_mode="border", + ).squeeze() + - dz + ) + + # 4. Update of cast shadow volumes + f = torch.fmax(f, temp) + shvoveg = torch.fmax(shvoveg, tempvegdem) + + sh = torch.where( + f > a, + torch.tensor(1.0, device=device), + torch.tensor(0.0, device=device), + ) + fabovea = tempvegdem > a + gabovea = tempvegdem2 > a + + # 5. Pergola logic (Overlaying layers with the & operator) + templastfabovea = tempvegdem + delta_z_par_pas + templastgabovea = tempvegdem2 + delta_z_par_pas + lastfabovea = templastfabovea > a lastgabovea = templastgabovea > a - dzprev = dz - vegsh2 = torch.add( - torch.add( - torch.add(fabovea, gabovea).float(), lastfabovea - ).float(), - lastgabovea, - ).float() - vegsh2[vegsh2 == 4] = 0.0 - # vegsh2[vegsh2 == 1] = 0. # This one is the ultimate question... - vegsh2[vegsh2 > 0] = 1.0 - - vegsh = torch.fmax(vegsh, vegsh2) - vegsh[vegsh * sh > 0] = 0 - vbshvegsh = ( - torch.clone(vegsh) + vbshvegsh - ) # removing shadows 'behind' buildings - - index += 1 + + # If all 4 layers are True at the same time, it's a pergola (light passes through). + is_pergola = fabovea & gabovea & lastfabovea & lastgabovea + + # The shadow exists if one of the layers is True, UNLESS it's a pergola. + vegsh2 = (fabovea | gabovea | lastfabovea | lastgabovea) & ( + ~is_pergola + ) + vegsh2 = vegsh2.float() + + # Accumulation of vegetation shadows + vegsh = torch.maximum(vegsh, vegsh2) + vegsh = torch.where( + vegsh * sh > 0, torch.tensor(0.0, device=device), vegsh + ) + vbshvegsh.add_(vegsh) sh = 1 - sh - vbshvegsh[vbshvegsh > 0] = 1 + vbshvegsh = torch.where( + vbshvegsh > 0, torch.tensor(1.0, device=device), vbshvegsh + ) vbshvegsh = vbshvegsh - vegsh - vegsh[vegsh > 0] = 1 + vegsh = torch.where(vegsh > 0, torch.tensor(1.0, device=device), vegsh) shvoveg = (shvoveg - a) * vegsh # Vegetation shadow volume vegsh = 1 - vegsh vbshvegsh = 1 - vbshvegsh + # print(torch.max(shvoveg)) wallsh, wallsun, wallshve, facesh, facesun = shade_on_walls( - azimuth, aspect, walls, a, f, shvoveg + azimuth, aspect, walls, a, f, shvoveg, device ) # print(torch.max(wallshve)) if walls_scheme is not False: wallsh_, wallsun_, wallshve_, facesh_, facesun_ = shade_on_walls( - azimuth, aspect_scheme, walls_scheme, a, f, shvoveg + azimuth, aspect_scheme, walls_scheme, a, f, shvoveg, device ) # print(torch.max(wallshve_)) shade_on_wall = wallsh_.clone() @@ -245,7 +228,6 @@ def shadowingfunction_wallheight_23( shade_on_wall < wallshve_ ] - # return vegsh, sh, vbshvegsh, wallsh, wallsun, wallshve, facesh, facesun, shade_on_wall return ( ( vegsh, diff --git a/util/SEBESOLWEIGCommonFiles/sun_position.py b/util/SEBESOLWEIGCommonFiles/sun_position.py index 9acf685..1f309ff 100644 --- a/util/SEBESOLWEIGCommonFiles/sun_position.py +++ b/util/SEBESOLWEIGCommonFiles/sun_position.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import division from __future__ import print_function -import datetime -import numpy as np import torch @@ -215,10 +213,11 @@ def julian_calculation(t_input): ut_time = torch.tensor( ((time["hour"] - time["UTC"]) / 24) + (time["min"] / (60 * 24)) - + (time["sec"] / (60 * 60 * 24)) - , device=device) # time of day in UT time. + + (time["sec"] / (60 * 60 * 24)), + device=device, + ) # time of day in UT time. D = time["day"] + ut_time - # Day of month in decimal time, ex. 2sd day of month at 12:30:30UT, D=2.521180556 + # Day of month in decimal time, ex. 2sd day of month at 12:30:30UT, D=2.521180556 # In 1582, the gregorian calendar was adopted if time["year"] == 1582: @@ -345,8 +344,9 @@ def earth_heliocentric_position_calculation(julian): [30, 0.44, 83996.85], [30, 2.74, 1349.87], [25, 3.16, 4690.48], - ] - , device=device) + ], + device=device, + ) L1_terms = torch.tensor( [ @@ -384,8 +384,9 @@ def earth_heliocentric_position_calculation(julian): [8, 5.3, 2352.87], [6, 2.65, 9437.76], [6, 4.67, 4690.48], - ] - , device=device) + ], + device=device, + ) L2_terms = torch.tensor( [ @@ -409,8 +410,9 @@ def earth_heliocentric_position_calculation(julian): [3, 2.28, 553.57], [2, 4.38, 5223.69], [2, 3.75, 0.98], - ] - , device=device) + ], + device=device, + ) L3_terms = torch.tensor( [ @@ -421,11 +423,13 @@ def earth_heliocentric_position_calculation(julian): [1, 4.72, 3.52], [1, 5.3, 18849.23], [1, 5.97, 242.73], - ] - , device=device) + ], + device=device, + ) L4_terms = torch.tensor( - [[114.0, 3.142, 0], [8, 4.13, 6283.08], [1, 3.84, 12566.15]] - , device=device) + [[114.0, 3.142, 0], [8, 4.13, 6283.08], [1, 3.84, 12566.15]], + device=device, + ) L5_terms = torch.tensor([1, 3.14, 0], device=device) L5_terms = torch.atleast_2d( @@ -494,10 +498,13 @@ def earth_heliocentric_position_calculation(julian): [80, 3.88, 5223.69], [44, 3.7, 2352.87], [32, 4, 1577.34], - ] - , device=device) + ], + device=device, + ) - B1_terms = torch.tensor([[9, 3.9, 5507.55], [6, 1.73, 5223.69]], device=device) + B1_terms = torch.tensor( + [[9, 3.9, 5507.55], [6, 1.73, 5223.69]], device=device + ) A0 = B0_terms[:, 0] B0 = B0_terms[:, 1] @@ -566,8 +573,9 @@ def earth_heliocentric_position_calculation(julian): [28, 1.21, 6286.6], [28, 1.9, 6279.55], [26, 4.59, 10447.39], - ] - , device=device) + ], + device=device, + ) R1_terms = torch.tensor( [ @@ -581,8 +589,9 @@ def earth_heliocentric_position_calculation(julian): [10, 5.91, 10977.08], [9, 1.42, 6275.96], [9, 0.27, 5486.78], - ] - , device=device) + ], + device=device, + ) R2_terms = torch.tensor( [ @@ -592,15 +601,19 @@ def earth_heliocentric_position_calculation(julian): [9, 3.63, 77713.77], [6, 1.87, 5573.14], [3, 5.47, 18849], - ] - , device=device) + ], + device=device, + ) - R3_terms = torch.tensor([[145.0, 4.273, 6283.076], [7, 3.92, 12566.15]], device=device) + R3_terms = torch.tensor( + [[145.0, 4.273, 6283.076], [7, 3.92, 12566.15]], device=device + ) R4_terms = [4, 2.56, 6283.08] # Force it to be a tensor first, then ensure it's 2D R4_terms = torch.atleast_2d( - torch.tensor(R4_terms, device=device)) # since L5_terms is 1D, we have to convert it to 2D to avoid indexErrors + torch.tensor(R4_terms, device=device) + ) # since L5_terms is 1D, we have to convert it to 2D to avoid indexErrors A0 = R0_terms[:, 0] B0 = R0_terms[:, 1] @@ -677,7 +690,11 @@ def nutation_calculation(julian): JCE = julian["ephemeris_century"] # 1. Mean elongation of the moon from the sun - p = torch.atleast_2d(torch.tensor([(1 / 189474), -0.0019142, 445267.11148, 297.85036], device=device)) + p = torch.atleast_2d( + torch.tensor( + [(1 / 189474), -0.0019142, 445267.11148, 297.85036], device=device + ) + ) # X0 = polyval(p, JCE); X0 = ( @@ -688,7 +705,11 @@ def nutation_calculation(julian): ) # This is faster than polyval... # 2. Mean anomaly of the sun (earth) - p = torch.atleast_2d(torch.tensor([-(1 / 300000), -0.0001603, 35999.05034, 357.52772], device=device)) + p = torch.atleast_2d( + torch.tensor( + [-(1 / 300000), -0.0001603, 35999.05034, 357.52772], device=device + ) + ) # X1 = polyval(p, JCE) X1 = ( @@ -699,7 +720,11 @@ def nutation_calculation(julian): ) # 3. Mean anomaly of the moon - p = torch.atleast_2d(torch.tensor([(1 / 56250), 0.0086972, 477198.867398, 134.96298], device=device)) + p = torch.atleast_2d( + torch.tensor( + [(1 / 56250), 0.0086972, 477198.867398, 134.96298], device=device + ) + ) # X2 = polyval(p, JCE); X2 = ( @@ -710,7 +735,11 @@ def nutation_calculation(julian): ) # 4. Moon argument of latitude - p = torch.atleast_2d(torch.tensor([(1 / 327270), -0.0036825, 483202.017538, 93.27191], device=device)) + p = torch.atleast_2d( + torch.tensor( + [(1 / 327270), -0.0036825, 483202.017538, 93.27191], device=device + ) + ) # X3 = polyval(p, JCE) X3 = ( @@ -722,7 +751,11 @@ def nutation_calculation(julian): # 5. Longitude of the ascending node of the moon's mean orbit on the # ecliptic, measured from the mean equinox of the date - p = torch.atleast_2d(torch.tensor([(1 / 450000), 0.0020708, -1934.136261, 125.04452], device=device)) + p = torch.atleast_2d( + torch.tensor( + [(1 / 450000), 0.0020708, -1934.136261, 125.04452], device=device + ) + ) # X4 = polyval(p, JCE); X4 = ( @@ -798,8 +831,9 @@ def nutation_calculation(julian): [2, -1, -1, 2, 2], [0, 0, 3, 2, 2], [2, -1, 0, 2, 2], - ] - , device=device) + ], + device=device, + ) nutation_terms = torch.tensor( [ @@ -866,12 +900,15 @@ def nutation_calculation(julian): [-3, 0, 0, 0], [-3, 0, 0, 0], [-3, 0, 0, 0], - ] - , device=device) + ], + device=device, + ) # Using the tabulated values, compute the delta_longitude and # delta_obliquity. - Xi = torch.tensor([X0, X1, X2, X3, X4], device=device) # a col mat in octave + Xi = torch.tensor( + [X0, X1, X2, X3, X4], device=device + ) # a col mat in octave tabulated_argument = torch.matmul(Y_terms.float(), Xi) * (torch.pi / 180) @@ -903,19 +940,22 @@ def true_obliquity_calculation(julian, nutation): """ p = torch.atleast_2d( - torch.tensor([ - 2.45, - 5.79, - 27.87, - 7.12, - -39.05, - -249.67, - -51.38, - 1999.25, - -1.55, - -4680.93, - 84381.448, - ], device=device) + torch.tensor( + [ + 2.45, + 5.79, + 27.87, + 7.12, + -39.05, + -249.67, + -51.38, + 1999.25, + -1.55, + -4680.93, + 84381.448, + ], + device=device, + ) ) # mean_obliquity = polyval(p, julian.ephemeris_millenium/10); @@ -1023,7 +1063,9 @@ def sun_rigth_ascension_calculation( argument_denominator = torch.cos(apparent_sun_longitude * torch.pi / 180) sun_rigth_ascension = ( - torch.arctan2(argument_numerator, argument_denominator) * 180 / torch.pi + torch.arctan2(argument_numerator, argument_denominator) + * 180 + / torch.pi ) # Limit the range to [0,360]; sun_rigth_ascension = set_to_range(sun_rigth_ascension, 0, 360) @@ -1101,7 +1143,10 @@ def topocentric_sun_position_calculate( ) # Term u, used in the following calculations (in radians) - u = torch.arctan(0.99664719 * torch.tan(torch.as_tensor(location["latitude"]) * torch.pi / 180)) + u = torch.arctan( + 0.99664719 + * torch.tan(torch.as_tensor(location["latitude"]) * torch.pi / 180) + ) # Term x, used in the following calculations x = torch.cos(u) + ( @@ -1187,10 +1232,18 @@ def sun_topocentric_zenith_angle_calculate( # Topocentric elevation, without atmospheric refraction argument = ( torch.sin(torch.as_tensor(location["latitude"]) * torch.pi / 180) - * torch.sin(torch.as_tensor(topocentric_sun_position["declination"]) * torch.pi / 180) + * torch.sin( + torch.as_tensor(topocentric_sun_position["declination"]) + * torch.pi + / 180 + ) ) + ( torch.cos(torch.as_tensor(location["latitude"]) * torch.pi / 180) - * torch.cos(torch.as_tensor(topocentric_sun_position["declination"]) * torch.pi / 180) + * torch.cos( + torch.as_tensor(topocentric_sun_position["declination"]) + * torch.pi + / 180 + ) * torch.cos(torch.as_tensor(topocentric_local_hour) * torch.pi / 180) ) true_elevation = torch.arcsin(argument) * 180 / torch.pi @@ -1212,15 +1265,23 @@ def sun_topocentric_zenith_angle_calculate( # Topocentric azimuth angle. The +180 conversion is to pass from astronomer # notation (westward from south) to navigation notation (eastward from # north); - nominator = torch.sin(torch.as_tensor(topocentric_local_hour * torch.pi / 180)) + nominator = torch.sin( + torch.as_tensor(topocentric_local_hour * torch.pi / 180) + ) denominator = ( torch.cos(torch.as_tensor(topocentric_local_hour) * torch.pi / 180) * torch.sin(torch.as_tensor(location["latitude"]) * torch.pi / 180) ) - ( - torch.tan(torch.as_tensor(topocentric_sun_position["declination"]) * torch.pi / 180) + torch.tan( + torch.as_tensor(topocentric_sun_position["declination"]) + * torch.pi + / 180 + ) * torch.cos(torch.as_tensor(location["latitude"]) * torch.pi / 180) ) - sun["azimuth"] = (torch.arctan2(nominator, denominator) * 180 / torch.pi) + 180 + sun["azimuth"] = ( + torch.arctan2(nominator, denominator) * 180 / torch.pi + ) + 180 # Set the range to [0-360] sun["azimuth"] = set_to_range(sun["azimuth"], 0, 360) @@ -1240,4 +1301,4 @@ def set_to_range(var, min_interval, max_interval): if var < min_interval: var = var + max_interval - return var \ No newline at end of file + return var diff --git a/util/shadowingfunctions.py b/util/shadowingfunctions.py index 0cb952d..552e81f 100644 --- a/util/shadowingfunctions.py +++ b/util/shadowingfunctions.py @@ -4,6 +4,7 @@ from math import radians import matplotlib.pylab as plt import numpy as np +import torch.nn.functional as F def _to_tensor(x, device, dtype=torch.float32): @@ -17,76 +18,104 @@ def _to_tensor(x, device, dtype=torch.float32): def shadowingfunctionglobalradiation( a, azimuth, altitude, scale, feedback, forsvf, device=torch.device("cpu") ): - a = _to_tensor(a, device) + """ + Computes global radiation shadows on a DSM using PyTorch hardware acceleration. + + This function leverages `F.grid_sample` to perform global map translations, + completely removing multi-index array slicing and trigonometric quadrant switches. + It is heavily optimized for GPU matrix operations. + + Args: + a (torch.Tensor or numpy.ndarray): Digital Surface Model (DSM) matrix. + azimuth (float): Sun azimuth angle in degrees. + altitude (float): Sun altitude angle in degrees. + scale (float): Spatial resolution modifier (1 pixel = 1 meter -> 1.0). + feedback (QgsProcessingFeedback or similar): Object used to report algorithm progress. + forsvf (int): Flag to toggle progress reporting (0 = report progress, 1 = skip). + device (torch.device, optional): Device context for execution. Defaults to CPU. + + Returns: + torch.Tensor: Binary shadow mask (1.0 = in sun, 0.0 = in shadow) matching `a.dtype`. + """ + # Ensure inputs are correctly formatted PyTorch tensors on the target device + if not isinstance(a, torch.Tensor): + a = torch.tensor(a, device=device) + else: + a = a.to(device=device) + # Convert angular sun positions to radians degrees = torch.pi / 180.0 azimuth = azimuth * degrees altitude = altitude * degrees - sizex = a.shape[0] - sizey = a.shape[1] + sizex, sizey = a.shape + + # Track progress bounds if requested if forsvf == 0: barstep = max(sizex, sizey) total = 100.0 / barstep - f = a - dx = 0.0 - dy = 0.0 - dz = 0.0 - temp = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) - index = 1.0 + # Clone initial DSM layout to accumulate the upper shadow envelope + f = a.clone() + + # --- COORDINATE GRID GENERATION FOR GLOBAL TRANSFORMATION --- + y, x = torch.meshgrid( + torch.linspace(-1, 1, sizex, device=device), + torch.linspace(-1, 1, sizey, device=device), + indexing="ij", + ) + # Target PyTorch shape: (1, H, W, 2) with (X, Y) layout at dim=-1 + grille_base = torch.stack((x, y), dim=-1).unsqueeze(0) + + # Convert the 2D DSM to a 4D tensor chunk (1, 1, H, W) for grid sampling + a_4d = a.unsqueeze(0).unsqueeze(0) + # Pre-calculate sun path factors amaxvalue = torch.max(a).item() - pibyfour = torch.pi / 4.0 - threetimespibyfour = 3.0 * pibyfour - fivetimespibyfour = 5.0 * pibyfour - seventimespibyfour = 7.0 * pibyfour sinazimuth = torch.sin(azimuth) cosazimuth = torch.cos(azimuth) - tanazimuth = torch.tan(azimuth) - signsinazimuth = torch.sign(sinazimuth).item() - signcosazimuth = torch.sign(cosazimuth).item() - dssin = torch.abs(1.0 / sinazimuth).item() - dscos = torch.abs(1.0 / cosazimuth).item() - tanaltitudebyscale = torch.tan(altitude).item() / scale - - while amaxvalue >= dz and abs(dx) < sizex and abs(dy) < sizey: + tanaltitudebyscale = torch.tan(altitude) / scale + + # Dynamically estimate the safety limit for our vector operations + max_steps = int(amaxvalue / tanaltitudebyscale.item()) + 2 + + # Vectorized loop structure (Eliminates slice variables and block switches) + for step_idx in range(1, max_steps): + index = float(step_idx) + if forsvf == 0: feedback.setProgress(int(index * total)) - if ( - (pibyfour <= azimuth and azimuth < threetimespibyfour) - or (fivetimespibyfour <= azimuth and azimuth < seventimespibyfour) - ): - dy = signsinazimuth * index - dx = -1.0 * signcosazimuth * abs(torch.round(index / tanazimuth)) - ds = dssin - else: - dy = signsinazimuth * abs(torch.round(index * tanazimuth)) - dx = -1.0 * signcosazimuth * index - ds = dscos - - dz = ds * index * tanaltitudebyscale - temp[:, :] = 0.0 - - absdx = abs(dx) - absdy = abs(dy) - xc1 = int((dx + absdx) / 2.0 + 1.0) - xc2 = int(sizex + (dx - absdx) / 2.0) - yc1 = int((dy + absdy) / 2.0 + 1.0) - yc2 = int(sizey + (dy - absdy) / 2.0) - xp1 = int(-((dx - absdx) / 2.0) + 1.0) - xp2 = int(sizex - (dx + absdx) / 2.0) - yp1 = int(-((dy - absdy) / 2.0) + 1.0) - yp2 = int(sizey - (dy + absdy) / 2.0) - - temp[xp1:xp2, yp1:yp2] = a[xc1:xc2, yc1:yc2] - dz + # 1. Project ground physical shadow offset (in pixels) + shift_x = index * sinazimuth / scale + shift_y = index * cosazimuth / scale + dz = index * tanaltitudebyscale + + # Safe breakout loop check if rays completely clear the canvas boundaries + if abs(shift_x) >= sizey and abs(shift_y) >= sizex: + break + + # 2. Shift the underlying layout grid space (-1 to 1 space boundaries) + grille_deplacee = grille_base.clone() + grille_deplacee[..., 0] -= (2.0 * shift_x) / (sizey - 1) + grille_deplacee[..., 1] -= (2.0 * shift_y) / (sizex - 1) + + # 3. Retrieve translated elevation layout using bilinear interpolation + temp = ( + F.grid_sample( + a_4d, grille_deplacee, mode="bilinear", padding_mode="border" + ).squeeze() + - dz + ) + + # 4. Amalgamate upper terrain shadow bounds f = torch.maximum(f, temp) - index += 1.0 + # Calculate local shadow gaps f = f - a - f = f == 0 - sh = f.to(dtype=a.dtype) + + # Binary conversion: Where f == 0, the ground matches the shadow envelope (it is in sun) + sh = (f == 0).to(dtype=a.dtype) return sh @@ -105,6 +134,9 @@ def shadowingfunction_20( forsvf, device=torch.device("cpu"), ): + # automatically get the device to use from the input + device = a.device if isinstance(a, torch.Tensor) else torch.device("cpu") + a = _to_tensor(a, device) vegdem = _to_tensor(vegdem, device) vegdem2 = _to_tensor(vegdem2, device) @@ -122,8 +154,6 @@ def shadowingfunction_20( total = 100.0 / barstep feedback.setProgress(0) - dx = 0.0 - dy = 0.0 dz = 0.0 temp = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) tempvegdem = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) @@ -136,223 +166,110 @@ def shadowingfunction_20( vegsh = bushplant.to(dtype=a.dtype) f = a - pibyfour = torch.pi / 4.0 - threetimespibyfour = 3.0 * pibyfour - fivetimespibyfour = 5.0 * pibyfour - seventimespibyfour = 7.0 * pibyfour - sinazimuth = torch.sin(azimuth) - cosazimuth = torch.cos(azimuth) - tanazimuth = torch.tan(azimuth) - signsinazimuth = torch.sign(sinazimuth).item() - signcosazimuth = torch.sign(cosazimuth).item() - dssin = torch.abs(1.0 / sinazimuth).item() - dscos = torch.abs(1.0 / cosazimuth).item() - tanaltitudebyscale = torch.tan(altitude).item() / scale - index = 0.0 + shvoveg = vegdem.clone() + y, x = torch.meshgrid( + torch.linspace(-1, 1, sizex, device=device), + torch.linspace(-1, 1, sizey, device=device), + indexing="ij", + ) + grille_base = torch.stack((x, y), dim=-1).unsqueeze(0) - dzprev = 0.0 + # Preparation of the layers packed for grid_sample + a_4d = a.unsqueeze(0).unsqueeze(0).float() + veg_4d = vegdem.unsqueeze(0).unsqueeze(0).float() + veg2_4d = vegdem2.unsqueeze(0).unsqueeze(0).float() - while (amaxvalue >= dz) and (abs(dx) < sizex) and (abs(dy) < sizey): - if forsvf == 0: - feedback.setProgress(int(index * total)) + # Trigonometric params for the sun's step + sinazimuth = torch.sin(azimuth) + cosazimuth = torch.cos(azimuth) + tanaltitudebyscale = torch.tan(altitude) / scale - if ( - (pibyfour <= azimuth) - and (azimuth < threetimespibyfour) - or (fivetimespibyfour <= azimuth) - and (azimuth < seventimespibyfour) - ): - dy = signsinazimuth * index - dx = -1.0 * signcosazimuth * abs(torch.round(index / tanazimuth)) - ds = dssin - else: - dy = signsinazimuth * abs(torch.round(index * tanazimuth)) - dx = -1.0 * signcosazimuth * index - ds = dscos - - dz = (ds * index) * tanaltitudebyscale - tempvegdem[:, :] = 0.0 - tempvegdem2[:, :] = 0.0 - temp[:, :] = 0.0 - templastfabovea[:, :] = 0.0 - templastgabovea[:, :] = 0.0 - - absdx = abs(dx) - absdy = abs(dy) - xc1 = int((dx + absdx) / 2.0) - xc2 = int(sizex + (dx - absdx) / 2.0) - yc1 = int((dy + absdy) / 2.0) - yc2 = int(sizey + (dy - absdy) / 2.0) - xp1 = int(-((dx - absdx) / 2.0)) - xp2 = int(sizex - (dx + absdx) / 2.0) - yp1 = int(-((dy - absdy) / 2.0)) - yp2 = int(sizey - (dy + absdy) / 2.0) - - tempvegdem[xp1:xp2, yp1:yp2] = vegdem[xc1:xc2, yc1:yc2] - dz - tempvegdem2[xp1:xp2, yp1:yp2] = vegdem2[xc1:xc2, yc1:yc2] - dz - temp[xp1:xp2, yp1:yp2] = a[xc1:xc2, yc1:yc2] - dz + # Calculation of the maximum of steps + # We stop when the shadow exceed the max possible height + max_steps = int(amaxvalue / tanaltitudebyscale) + 2 + + # Facteur d'avancement du rayon (équivalent géométrique de ton ancien 'ds') + # Plus besoin de gros blocs If/Else selon les quadrants du soleil ! + delta_z_par_pas = tanaltitudebyscale + + for index in range(1, max_steps): + # 1. Calculations of the slide + shift_x = index * sinazimuth / scale + shift_y = index * cosazimuth / scale + dz = index * delta_z_par_pas + + # Stop if the shadow is completely outside of the map + if abs(shift_x) >= sizey and abs(shift_y) >= sizex: + break + + # 2. Applying the offset to our normalized coordinate sheet (-1 to 1) + grille_deplacee = grille_base.clone() + grille_deplacee[..., 0] -= (2.0 * shift_x) / (sizey - 1) + grille_deplacee[..., 1] -= (2.0 * shift_y) / (sizex - 1) + + # 3. Global Material Sampling (Instant Layer Swipe) + temp = ( + F.grid_sample( + a_4d, grille_deplacee, mode="bilinear", padding_mode="border" + ).squeeze() + - dz + ) + tempvegdem = ( + F.grid_sample( + veg_4d, grille_deplacee, mode="bilinear", padding_mode="border" + ).squeeze() + - dz + ) + tempvegdem2 = ( + F.grid_sample( + veg2_4d, + grille_deplacee, + mode="bilinear", + padding_mode="border", + ).squeeze() + - dz + ) - f = torch.maximum(f, temp) - sh = (f > a).to(dtype=a.dtype) - vbshvegsh = vbshvegsh + # 4. Update of cast shadow volumes + f = torch.fmax(f, temp) + shvoveg = torch.fmax(shvoveg, tempvegdem) + sh = torch.where( + f > a, + torch.tensor(1.0, device=device), + torch.tensor(0.0, device=device), + ) fabovea = tempvegdem > a gabovea = tempvegdem2 > a - templastfabovea[xp1:xp2, yp1:yp2] = vegdem[xc1:xc2, yc1:yc2] - dzprev - templastgabovea[xp1:xp2, yp1:yp2] = vegdem2[xc1:xc2, yc1:yc2] - dzprev + # 5. Pergola logic (Overlaying layers with the & operator) + templastfabovea = tempvegdem + delta_z_par_pas + templastgabovea = tempvegdem2 + delta_z_par_pas + lastfabovea = templastfabovea > a lastgabovea = templastgabovea > a - dzprev = dz - - vegsh2 = ( - fabovea.to(dtype=a.dtype) - + gabovea.to(dtype=a.dtype) - + lastfabovea.to(dtype=a.dtype) - + lastgabovea.to(dtype=a.dtype) - ) - vegsh2[vegsh2 == 4.0] = 0.0 - vegsh2[vegsh2 > 0.0] = 1.0 - - vegsh = torch.maximum(vegsh, vegsh2) - vegsh[(vegsh * sh) > 0.0] = 0.0 - vbshvegsh = vegsh + vbshvegsh - - index += 1.0 - - sh = 1.0 - sh - vbshvegsh[vbshvegsh > 0.0] = 1.0 - vbshvegsh = vbshvegsh - vegsh - vegsh = 1.0 - vegsh - vbshvegsh = 1.0 - vbshvegsh - - shadowresult = {"sh": sh, "vegsh": vegsh, "vbshvegsh": vbshvegsh} - return shadowresult - - -def shadowingfunction_20_old( - a, vegdem, vegdem2, azimuth, altitude, scale, amaxvalue, bush, dlg, forsvf -): - a = _to_tensor(a, torch.device("cpu")) - vegdem = _to_tensor(vegdem, torch.device("cpu")) - vegdem2 = _to_tensor(vegdem2, torch.device("cpu")) - bush = _to_tensor(bush, torch.device("cpu")) - degrees = torch.pi / 180.0 - if azimuth == 0.0: - azimuth = 1e-12 - azimuth = azimuth * degrees - altitude = altitude * degrees + # If all 4 layers are True at the same time, it's a pergola (light passes through). + is_pergola = fabovea & gabovea & lastfabovea & lastgabovea - sizex = a.shape[0] - sizey = a.shape[1] - if forsvf == 0: - barstep = max(sizex, sizey) - dlg.progressBar.setRange(0, barstep) - dlg.progressBar.setValue(0) - - dx = 0.0 - dy = 0.0 - dz = 0.0 - temp = torch.zeros((sizex, sizey), dtype=a.dtype) - tempvegdem = torch.zeros((sizex, sizey), dtype=a.dtype) - tempvegdem2 = torch.zeros((sizex, sizey), dtype=a.dtype) - sh = torch.zeros((sizex, sizey), dtype=a.dtype) - vbshvegsh = torch.zeros((sizex, sizey), dtype=a.dtype) - vegsh = torch.zeros((sizex, sizey), dtype=a.dtype) - tempbush = torch.zeros((sizex, sizey), dtype=a.dtype) - f = a - g = torch.zeros((sizex, sizey), dtype=a.dtype) - bushplant = bush > 1.0 - - pibyfour = torch.pi / 4.0 - threetimespibyfour = 3.0 * pibyfour - fivetimespibyfour = 5.0 * pibyfour - seventimespibyfour = 7.0 * pibyfour - sinazimuth = torch.sin(azimuth) - cosazimuth = torch.cos(azimuth) - tanazimuth = torch.tan(azimuth) - signsinazimuth = torch.sign(sinazimuth).item() - signcosazimuth = torch.sign(cosazimuth).item() - dssin = torch.abs(1.0 / sinazimuth).item() - dscos = torch.abs(1.0 / cosazimuth).item() - tanaltitudebyscale = torch.tan(altitude).item() / scale - index = 1.0 - - while amaxvalue >= dz and abs(dx) < sizex and abs(dy) < sizey: - if forsvf == 0: - dlg.progressBar.setValue(int(index)) - if ( - pibyfour <= azimuth - and azimuth < threetimespibyfour - or fivetimespibyfour <= azimuth - and azimuth < seventimespibyfour - ): - dy = signsinazimuth * index - dx = -1.0 * signcosazimuth * abs(torch.round(index / tanazimuth)) - ds = dssin - else: - dy = signsinazimuth * abs(torch.round(index * tanazimuth)) - dx = -1.0 * signcosazimuth * index - ds = dscos - - dz = ds * index * tanaltitudebyscale - tempvegdem[:, :] = 0.0 - tempvegdem2[:, :] = 0.0 - temp[:, :] = 0.0 - - absdx = abs(dx) - absdy = abs(dy) - xc1 = int((dx + absdx) / 2.0 + 1.0) - xc2 = int(sizex + (dx - absdx) / 2.0) - yc1 = int((dy + absdy) / 2.0 + 1.0) - yc2 = int(sizey + (dy - absdy) / 2.0) - xp1 = int(-((dx - absdx) / 2.0) + 1.0) - xp2 = int(sizex - (dx + absdx) / 2.0) - yp1 = int(-((dy - absdy) / 2.0) + 1.0) - yp2 = int(sizey - (dy + absdy) / 2.0) - - tempvegdem[xp1:xp2, yp1:yp2] = vegdem[xc1:xc2, yc1:yc2] - dz - tempvegdem2[xp1:xp2, yp1:yp2] = vegdem2[xc1:xc2, yc1:yc2] - dz - temp[xp1:xp2, yp1:yp2] = a[xc1:xc2, yc1:yc2] - dz + # The shadow exists if one of the layers is True, UNLESS it's a pergola. + vegsh2 = (fabovea | gabovea | lastfabovea | lastgabovea) & ( + ~is_pergola + ) + vegsh2 = vegsh2.float() - f = torch.maximum(f, temp) - sh[(f > a)] = 1.0 - sh[(f <= a)] = 0.0 - fabovea = tempvegdem > a - gabovea = tempvegdem2 > a - vegsh2 = fabovea.to(dtype=a.dtype) - gabovea.to(dtype=a.dtype) + # Accumulation of vegetation shadows vegsh = torch.maximum(vegsh, vegsh2) - vegsh[(vegsh * sh) > 0.0] = 0.0 - vbshvegsh = vegsh + vbshvegsh - - if index == 1.0: - firstvegdem = tempvegdem - temp - firstvegdem[firstvegdem <= 0.0] = 1000.0 - vegsh[firstvegdem < dz] = 1.0 - vegsh = vegsh * (vegdem2 > a).to(dtype=a.dtype) - vbshvegsh = torch.zeros((sizex, sizey), dtype=a.dtype) - - if torch.max(bush) > 0.0 and torch.max((fabovea.to(dtype=a.dtype) * bush)) > 0.0: - tempbush[:, :] = 0.0 - tempbush[xp1:xp2, yp1:yp2] = bush[xc1:xc2, yc1:yc2] - dz - g = torch.maximum(g, tempbush) - g = g * bushplant - - index += 1.0 + vegsh = torch.where( + vegsh * sh > 0, torch.tensor(0.0, device=device), vegsh + ) + vbshvegsh.add_(vegsh) sh = 1.0 - sh - vbshvegsh[vbshvegsh > 0.0] = 1.0 + vbshvegsh = torch.where( + vbshvegsh > 0.0, torch.tensor(1.0, device=device), vbshvegsh + ) vbshvegsh = vbshvegsh - vegsh - - if torch.max(bush) > 0.0: - g = g - bush - g[g > 0.0] = 1.0 - g[g < 0.0] = 0.0 - vegsh = vegsh - bushplant.to(dtype=a.dtype) + g - vegsh[vegsh < 0.0] = 0.0 - - vegsh[vegsh > 0.0] = 1.0 vegsh = 1.0 - vegsh vbshvegsh = 1.0 - vbshvegsh @@ -374,171 +291,174 @@ def shadowingfunction_findwallID( facesh, wall_dict, sh, - device + device, ): """ - This function identifies what wall id and voxel height that is seen from a ground pixel - - INPUTS: - dsm = Digital surface model - azimuth and altitude = sun position in degrees - scale= scale of DSM (1 meter pixels=1, 2 meter pixels=0.5) - uniqueWallIDs = pixel row 'outside' buildings. will be calculated if empty - walls = height of walls - dem = Digital elevation model. (Should be excluded in future to incorporate ground elevation) - - OUTPUT: - buildIDSeen = ID seen from ground pixel - voxelHeight = Wall height shadow volume - - Fredrik Lindberg 2023-02-16 - fredrikl@gvc.gu.se - + Identifies which wall ID and voxel height are visible along solar ray vectors from ground pixels. + + This version removes explicit pixel-slice shifting loops by utilizing PyTorch's hardware-accelerated + `F.grid_sample` engine. It implements nearest-neighbor interpolation to prevent spatial distortion + of discrete categorical Wall IDs during matrix transformation layers. + + Args: + dsm (torch.Tensor): Digital Surface Model tensor. + azimuth (float): Sun azimuth angle in degrees. + altitude (float): Sun altitude angle in degrees. + scale (float): Spatial resolution modifier (1 pixel = 1 meter -> 1.0). + walls (torch.Tensor): Height profile of the pixels representing building walls. + uniqueWallIDs (torch.Tensor): Matrix containing distinct structural identifiers for each wall. + dem (torch.Tensor): Digital Elevation Model representing bare earth topography. + wall2d_id (torch.Tensor or list): Reference map containing baseline wall components. + voxel_height (torch.Tensor or list): Reference metric indicating structural slice heights. + voxelId_list (torch.Tensor or list): Linear index tracker for specific 3D voxel spaces. + facesh (torch.Tensor): Binary mask identifying building walls shaded by their own geometry. + wall_dict (dict): Dictionary mapping categorical string/int Wall IDs to their true heights. + sh (torch.Tensor): Baseline ground shadow mask layer (1 = sun, 0 = shadow). + device (torch.device): Execution hardware targeting context (CPU/GPU). + + Returns: + tuple: A tuple containing: + - buildIDSeen (torch.Tensor): Categorical ID of the wall casting a shadow onto the pixel. + - voxelHeight (torch.Tensor): Accumulated vertical shadow volume height profile. + - voxelId (torch.Tensor): Specific structural voxel block identifier seen by the layout. """ - - # Remove ground heights + # 1. PRE-PROCESSING & FORMATTING dsm = dsm - dem - dsm[dsm < 0.5] = 0 + dsm = torch.where(dsm < 0.5, torch.tensor(0.0, device=device), dsm) - # conversion, degrees to radians - azimuth = radians(azimuth) - altitude = radians(altitude) + # Conversion degrees to radians + azimuth_rad = radians(azimuth) + altitude_rad = radians(altitude) - # measure the size of the image - rows = dsm.shape[0] - cols = dsm.shape[1] + rows, cols = dsm.shape - # initialise parameters - f = torch.clone(dsm) + # Initialise tracked arrays buildIDSeen = torch.zeros((rows, cols), device=device) - - dx = torch.tensor(0, device=device) - dy = torch.tensor(0, device=device) - dz = torch.tensor(0, device=device) - temp = torch.zeros((rows, cols), device=device) - temp2 = torch.zeros((rows, cols), device=device) # walls - tempwallID = torch.zeros((rows, cols), device=device) - uniqueWallIDsOrig = torch.clone(uniqueWallIDs) - voxelHeight = torch.zeros((rows, cols), device=device) temp3 = torch.ones((rows, cols), device=device) - # create a fast PyTorch tensor lookup table for wall_dict + # Mask wall tracking based on facing attributes + uniqueWallIDs_masked = uniqueWallIDs * facesh + + # Build a high-performance native tensor lookup table for wall heights max_wall_id = int(max(wall_dict.keys())) if wall_dict else 0 wall_height_lookup = torch.zeros(max_wall_id + 1, device=device) for k, v in wall_dict.items(): wall_height_lookup[int(k)] = v - # other loop parameters - amaxvalue = torch.max(dsm) - pibyfour = torch.pi / 4 - threetimespibyfour = 3 * pibyfour - fivetimespibyfour = 5 * pibyfour - seventimespibyfour = 7 * pibyfour - azimuth = torch.tensor(azimuth, device=device) - sinazimuth = torch.sin(azimuth) - cosazimuth = torch.cos(azimuth) - tanazimuth = torch.tan(azimuth) - signsinazimuth = torch.sign(sinazimuth) - signcosazimuth = torch.sign(cosazimuth) - dssin = torch.abs(1 / sinazimuth) - dscos = torch.abs(1 / cosazimuth) - altitude = torch.tensor(altitude, device=device) - scale = torch.tensor(scale, device=device) - - tanaltitudebyscale = torch.tan(altitude) / scale - - index = 1 - - # main loop - while (amaxvalue >= dz) and (torch.abs(dx) < rows) and (torch.abs(dy) < cols): - - if (pibyfour <= azimuth and azimuth < threetimespibyfour) or ( - fivetimespibyfour <= azimuth and azimuth < seventimespibyfour - ): - dy = signsinazimuth * index - dx = -1 * signcosazimuth * torch.abs(torch.round(index / tanazimuth)) - ds = dssin - else: - dy = signsinazimuth * torch.abs(torch.round(index * tanazimuth)) - dx = -1 * signcosazimuth * index - ds = dscos - - dz = ds * index * tanaltitudebyscale - temp[0:rows, 0:cols] = 0 - temp2[0:rows, 0:cols] = 0 - - absdx = torch.abs(dx) - absdy = torch.abs(dy) - - xc1 = int((dx + absdx) / 2) - xc2 = int(rows + (dx - absdx) / 2) - yc1 = int((dy + absdy) / 2) - yc2 = int(cols + (dy - absdy) / 2) - - xp1 = int(-((dx - absdx) / 2)) - xp2 = int(rows - (dx + absdx) / 2) - yp1 = int(-((dy - absdy) / 2)) - yp2 = int(cols - (dy + absdy) / 2) - - wallSeen = facesh - uniqueWallIDs = uniqueWallIDs * wallSeen + # --- COORDINATE GRID GENERATION FOR THE SAMPLING TRANSFORMS --- + y, x = torch.meshgrid( + torch.linspace(-1, 1, rows, device=device), + torch.linspace(-1, 1, cols, device=device), + indexing="ij", + ) + grille_base = torch.stack((x, y), dim=-1).unsqueeze(0) + + # Emballer uniqueWallIDs in 4D (1, 1, H, W) for grid_sample + # We keep it as float for grid_sample, and convert back to long for indexing + wall_id_4d = uniqueWallIDs_masked.unsqueeze(0).unsqueeze(0).float() + + # Trig variables + sinazimuth = torch.sin(torch.tensor(azimuth_rad, device=device)) + cosazimuth = torch.cos(torch.tensor(azimuth_rad, device=device)) + tanaltitudebyscale = ( + torch.tan(torch.tensor(altitude_rad, device=device)) / scale + ) - # Moving wall id - tempwallID[xp1:xp2, yp1:yp2] = uniqueWallIDs[xc1:xc2, yc1:yc2] - - # FIX: Native PyTorch tensor lookup instead of np.vectorize + amaxvalue = torch.max(dsm) + max_steps = int(amaxvalue / tanaltitudebyscale) + 2 + + # 2. CORE VECTORIZED GRID-SHIFTING LOOP + for index in range(1, max_steps): + # Project spatial offsets on pixel dimensions + shift_x = index * sinazimuth / scale + shift_y = index * cosazimuth / scale + dz = index * tanaltitudebyscale + + # Break early if ray projections completely escape spatial boundaries + if abs(shift_x) >= cols and abs(shift_y) >= rows: + break + + # Displace the structural tracking sheet + grille_deplacee = grille_base.clone() + grille_deplacee[..., 0] -= (2.0 * shift_x) / (cols - 1) + grille_deplacee[..., 1] -= (2.0 * shift_y) / (rows - 1) + + # CRITICAL OPTIMIZATION: mode="nearest" keeps Wall IDs integer-pure (no interpolation fuzziness) + tempwallID = F.grid_sample( + wall_id_4d, grille_deplacee, mode="nearest", padding_mode="zeros" + ).squeeze() + + # Batch-map wall ID categories directly to structural heights via tensor indexing temp_wallHeight = wall_height_lookup[tempwallID.long()] - - # Descending wall, how much of the wall that is still above ground level - temp2 = temp_wallHeight - dz - # buildIDSeen calculations - buildIDSeen = (temp2 > 0) * temp3 * tempwallID + buildIDSeen + # Track wall degradation down ray segments + temp2 = temp_wallHeight - dz - # voxelHeight calculations - voxelHeight = (temp2 > 0) * temp3 * (temp_wallHeight - temp2) + voxelHeight + # Process logical flags globally on the GPU cores + valid_mask = temp2 > 0 + active_pixels_mask = valid_mask & temp3 - # Remember pixels previous iteration that walls have not progressed into yet. - temp3 = torch.clone(temp2 <= 0) * (buildIDSeen == 0) + # Amalgamate ray intersection metrics + buildIDSeen = torch.where(active_pixels_mask, tempwallID, buildIDSeen) + voxelHeight = torch.where( + active_pixels_mask, temp_wallHeight - temp2, voxelHeight + ) - index += 1 + # Update remaining target pixel profiles using fast tensor boolean operations + temp3 = (temp2 <= 0) & (buildIDSeen == 0) - # Ceil voxel height values to integers + # 3. POST-PROCESSING & VOXEL IDENTIFICATION voxelHeight_ceil = torch.ceil(voxelHeight) - - # Empty raster to fill with voxel IDs voxelId = torch.zeros((rows, cols), device=device) - - # Ensure mapping references are native PyTorch tensors on the correct device - wall2d_id = torch.tensor(wall2d_id, device=device) - voxel_height = torch.tensor(voxel_height, device=device) - voxelId_list = torch.tensor(voxelId_list, dtype=torch.long, device=device) - - # Flatten maps to find unique combinations - a = buildIDSeen.flatten() - b = voxelHeight_ceil.flatten() - c = torch.column_stack([a, b]) - d = torch.unique(c, dim=0) - d = d[~torch.all(d == 0, dim=1)] - - # Fill voxelId matrix with unique voxel IDs - for temp_id, temp_height in d: - # FIX: Safer element checking using .numel() and extraction via index [0] + + # Secure mapping arrays are local tensors + if not isinstance(wall2d_id, torch.Tensor): + wall2d_id = torch.tensor(wall2d_id, device=device) + else: + wall2d_id = wall2d_id.to(device) + + if not isinstance(voxel_height, torch.Tensor): + voxel_height = torch.tensor(voxel_height, device=device) + else: + voxel_height = voxel_height.to(device) + + if not isinstance(voxelId_list, torch.Tensor): + voxelId_list = torch.tensor( + voxelId_list, dtype=torch.long, device=device + ) + else: + voxelId_list = voxelId_list.to(device=device, dtype=torch.long) + + # Find unique Wall ID & Voxel Height pairings found across the canvas + stacked_profiles = torch.column_stack( + [buildIDSeen.flatten(), voxelHeight_ceil.flatten()] + ) + unique_combinations = torch.unique(stacked_profiles, dim=0) + unique_combinations = unique_combinations[ + ~torch.all(unique_combinations == 0, dim=1) + ] + + # Map the linear 3D voxel index references + for temp_id, temp_height in unique_combinations: mask = (wall2d_id == temp_id) & (voxel_height == temp_height) temp_fill_id = voxelId_list[mask] - - pixel_mask = (buildIDSeen == temp_id) & (voxelHeight_ceil == temp_height) - + + pixel_mask = (buildIDSeen == temp_id) & ( + voxelHeight_ceil == temp_height + ) + if temp_fill_id.numel() > 0: voxelId[pixel_mask] = temp_fill_id[0].float() else: - buildIDSeen[pixel_mask] = 0 - voxelHeight_ceil[pixel_mask] = 0 + buildIDSeen[pixel_mask] = 0.0 + voxelHeight_ceil[pixel_mask] = 0.0 - # Correct for shadows - buildIDSeen = buildIDSeen * (1 - sh) - voxelHeight = voxelHeight * (1 - sh) - voxelId = voxelId * (1 - sh) + # Invert original shadow notation layout logic (1 - sh) + # This aligns the mapping accurately to raw cast shadows + shadow_correction = 1.0 - sh + buildIDSeen *= shadow_correction + voxelHeight *= shadow_correction + voxelId *= shadow_correction - return buildIDSeen, voxelHeight, voxelId \ No newline at end of file + return buildIDSeen, voxelHeight, voxelId From 2c9f1040f334250b45fce0ebd672a7f720a0be76 Mon Sep 17 00:00:00 2001 From: "Mael.PHILIPPE" Date: Tue, 26 May 2026 11:15:31 +0200 Subject: [PATCH 06/20] Changed solweig config --- processor/configsolweig.ini | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/processor/configsolweig.ini b/processor/configsolweig.ini index 76e223e..fbe7335 100644 --- a/processor/configsolweig.ini +++ b/processor/configsolweig.ini @@ -6,32 +6,32 @@ #--------------------------------------------------------------------------------------------------------- ####### INPUTS ####### # output path -output_dir=/home/lemap/Documents/suede/utils/tutorials/GVC_Validation_Setup/out/ +output_dir=/home/lemap/Documents/suede/datasets/gotenburg/solweig_out/ # working dir -working_dir=/home/lemap/Documents/suede/utils/tutorials/GVC_Validation_Setup/ +working_dir=/home/lemap/Documents/suede/datasets/gotenburg/ # parameters json file para_json_path=/home/lemap/Documents/suede/umep_process_execute/UMEP-processing/processor/parametersforsolweig.json # Input ground and building dsm -filepath_dsm=/home/lemap/Documents/suede/utils/tutorials/GVC_Validation_Setup/DSM_GVC.tif +filepath_dsm=/home/lemap/Documents/suede/datasets/gotenburg/dsm_cut.tif # Input vegetation dsm -filepath_cdsm=/home/lemap/Documents/suede/utils/tutorials/GVC_Validation_Setup/CDSM_GVC.tif +filepath_cdsm=/home/lemap/Documents/suede/datasets/gotenburg/cdsm_cut.tif # Input trunkzone vegetation dsm filepath_tdsm= # Input Digital Elevation Model -filepath_dem=/home/lemap/Documents/suede/utils/tutorials/GVC_Validation_Setup/DEM_GVC.tif +filepath_dem=/home/lemap/Documents/suede/datasets/gotenburg/dem_cut.tif # Input Land cover dataset -filepath_lc=/home/lemap/Documents/suede/utils/tutorials/GVC_Validation_Setup/LC_GVC.tif +filepath_lc=/home/lemap/Documents/suede/datasets/gotenburg/lc_cut_corrected.tif # Input wall height raster -filepath_wh=/home/lemap/Documents/suede/utils/tutorials/GVC_Validation_Setup/wall_height_GVC.tif +filepath_wh=/home/lemap/Documents/suede/datasets/gotenburg/wallheight.tif # Input wall aspect raster -filepath_wa=/home/lemap/Documents/suede/utils/tutorials/GVC_Validation_Setup/wall_aspect_GVC.tif +filepath_wa=/home/lemap/Documents/suede/datasets/gotenburg/wallaspect.tif # Skyview factor files -input_svf=/home/lemap/Documents/suede/utils/tutorials/GVC_Validation_Setup/svfs.zip +input_svf=/home/lemap/Documents/suede/datasets/gotenburg/skyview_out/svfs.zip # Input file for anisotrophic sky input_aniso= #Point of Interest file for ground -poi_file=/home/lemap/Documents/suede/utils/tutorials/GVC_Validation_Setup/POI_GVC.shp -poi_field=id +poi_file= +poi_field= # Input file for wall temperture scheme (Wallenberg et al. 2025) input_wall= # Input file for surface temperature data @@ -40,7 +40,7 @@ input_surf= woi_file= woi_field= # input meteorolgical file (i.e. forcing file) -input_met=/home/lemap/Documents/suede/utils/tutorials/GVC_Validation_Setup/MetFile20100523_Prepared.txt +input_met=/home/lemap/Documents/suede/datasets/gotenburg/MetFile20100523_Prepared.txt ## input settings ## # option to execute solweig outside of osgeo/qgis environment From 2626a3f8a201f61c3c7c2c47ebc9bb8f25d0005a Mon Sep 17 00:00:00 2001 From: "Mael.PHILIPPE" Date: Wed, 27 May 2026 09:46:25 +0200 Subject: [PATCH 07/20] fix/removed french comments in wallalgorithms.py --- functions/wallalgorithms.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/functions/wallalgorithms.py b/functions/wallalgorithms.py index 9a95ae2..b6f8048 100644 --- a/functions/wallalgorithms.py +++ b/functions/wallalgorithms.py @@ -13,16 +13,11 @@ from scipy.ndimage import maximum_filter def findwalls_sp(arr_dsm, walllimit, device, footprint=None): - """ - Identifie les murs de manière ultra-optimisée en mémoire (sans F.unfold). - """ - # 1. S'assurer que l'entrée est un tenseur PyTorch if isinstance(arr_dsm, torch.Tensor): dsm_tensor = arr_dsm else: dsm_tensor = torch.tensor(arr_dsm, device=device) - # 2. Définir le footprint par défaut (forme de diamant / points cardinaux) if footprint is None or footprint is False: footprint = torch.tensor([ [0, 1, 0], @@ -35,32 +30,23 @@ def findwalls_sp(arr_dsm, walllimit, device, footprint=None): fh, fw = footprint.shape pad_h, pad_w = fh // 2, fw // 2 - # Padding adaptatif basé sur la taille du filtre padded_a = dsm_tensor.unsqueeze(0).unsqueeze(0) padded_a = F.pad(padded_a, pad=(pad_w, pad_w, pad_h, pad_h), mode="replicate") padded_a = padded_a.squeeze(0).squeeze(0) - # Initialisation de la matrice des maximums avec une valeur minimale (-infini) max_neighbors = torch.full_like(dsm_tensor, float('-inf')) - # Trouver les coordonnées où le filtre est actif (égal à 1) y_indices, x_indices = torch.where(footprint == 1) - # Utilisation du glissement par vue (0 copie mémoire) H, W = dsm_tensor.shape for dy, dx in zip(y_indices, x_indices): - # Cette ligne crée une "vue" virtuelle sans allouer de RAM shifted_view = padded_a[dy : dy + H, dx : dx + W] - # Comparaison élément par élément optimisée max_neighbors = torch.maximum(max_neighbors, shifted_view) - # 3. Identification des pixels de murs walls = max_neighbors - dsm_tensor - # Appliquer la limite de hauteur des murs walls[walls < walllimit] = 0 - # 4. Remise à zéro des bordures extérieures walls[0, :] = 0 walls[-1, :] = 0 walls[:, 0] = 0 @@ -146,7 +132,7 @@ def filter1Goodwin_as_aspect_v3( buildfilt1_list = [] buildfilt2_list = [] - # 2. Pre-calculate all 180 directional filters on CPU + # 2. Pre-calculate all 180 directional filters on CPU or GPU with torch.no_grad(): for h in range(180): filtmatrix1temp = sc.rotate( From 9e95e869564c10337a2ab3672505d86f59f6a834 Mon Sep 17 00:00:00 2001 From: "Mael.PHILIPPE" Date: Wed, 27 May 2026 09:47:53 +0200 Subject: [PATCH 08/20] auto formating files via black --- .../SEBE_2015a_calc_forprocessing.py | 37 +- functions/SEBEfiles/WriteMetaDataSEBE.py | 27 +- functions/SEBEfiles/importdata.py | 41 +-- functions/SEBEfiles/sunmapcreator_2015a.py | 23 +- .../SOLWEIGpython/COMFA/CNRRabs_Total.py | 4 +- functions/SOLWEIGpython/COMFA/Ratio_Kb.py | 4 +- .../COMFA/radiationfunctionsCOMFA.py | 24 +- functions/SOLWEIGpython/Kside_veg_v2019a.py | 102 ++---- functions/SOLWEIGpython/Kside_veg_v2022a.py | 190 +++------- functions/SOLWEIGpython/Kup_veg_2015a.py | 15 +- functions/SOLWEIGpython/Lcyl_v2022a.py | 8 +- functions/SOLWEIGpython/Lside_veg.py | 92 ++--- functions/SOLWEIGpython/Lside_veg_v2015a.py | 52 +-- functions/SOLWEIGpython/PET_calculations.py | 37 +- .../Solweig_2021a_calc_forprocessing.py | 24 +- .../Solweig_2022a_calc_forprocessing.py | 47 +-- .../Solweig_2025a_calc_forprocessing.py | 25 +- .../Solweig_2026a_calc_forprocessing.py | 32 +- functions/SOLWEIGpython/Solweig_run.py | 119 ++---- functions/SOLWEIGpython/Tgmaps_v1.py | 6 +- functions/SOLWEIGpython/UTCI_calculations.py | 12 +- .../SOLWEIGpython/WriteMetadataSOLWEIG.py | 45 +-- functions/SOLWEIGpython/anisotropic_sky.py | 30 +- functions/SOLWEIGpython/cylindric_wedge.py | 12 +- functions/SOLWEIGpython/emissivity_models.py | 4 +- functions/SOLWEIGpython/ground_surface.py | 98 ++--- functions/SOLWEIGpython/gvf_2015a.py | 24 +- functions/SOLWEIGpython/gvf_2018a.py | 24 +- .../SOLWEIGpython/patch_characteristics.py | 18 +- functions/SOLWEIGpython/patch_radiation.py | 58 +-- functions/SOLWEIGpython/sunonsurface_2018a.py | 19 +- functions/SOLWEIGpython/wallOfInterest.py | 4 +- functions/SOLWEIGpython/wall_cover.py | 9 +- .../SOLWEIGpython/wall_surface_temperature.py | 75 ++-- functions/SOLWEIGpython/wallsAsNetCDF.py | 7 +- functions/TreeGenerator/makevegdems.py | 16 +- .../SOLWEIG1D/Kside1D_veg_v2019a.py | 91 ++--- .../TreePlanter/SOLWEIG1D/SOLWEIG1D_2023a.py | 3 +- functions/TreePlanter/SOLWEIG1D/SOLWEIG_1D.py | 3 +- .../SOLWEIG1D/Solweig1D_2019a_calc.py | 4 +- .../SOLWEIG1D/Solweig1D_2023a_calc.py | 9 +- .../TreePlanter/SOLWEIG1D/anisotropic_sky.py | 30 +- .../SOLWEIG1D/emissivity_models.py | 4 +- .../TreePlanter/SOLWEIG1D/patch_radiation.py | 74 +--- .../TreeGeneratorTempold/makevegdems.py | 20 +- .../TreePlanter/GreedyAlgorithm.py | 28 +- .../TreePlanter/HillClimberAlgorithm.py | 32 +- .../TreePlanter/StartingPositions.py | 8 +- .../TreePlanter/TreePlanterClasses.py | 48 +-- .../TreePlanter/TreePlanterHillClimber.py | 8 +- .../TreePlanter/TreePlanterPrepare.py | 4 +- .../TreePlanter/TreePlanterTreeshade.py | 4 +- .../TreePlanter/TreePlanter/adjustments.py | 35 +- functions/URock/CalculatesIndicators.py | 10 +- functions/URock/DataUtil.py | 31 +- functions/URock/H2gisConnection.py | 13 +- functions/URock/InitWindField.py | 150 ++------ functions/URock/MainCalculation.py | 127 ++----- functions/URock/Obstacles.py | 16 +- functions/URock/WindSolver.py | 18 +- functions/URock/WriteMetadataURock.py | 15 +- functions/URock/Zones.py | 50 +-- functions/URock/loadData.py | 31 +- functions/URock/saveData.py | 71 +--- functions/URock/urock_analyser_functions.py | 58 +-- .../URock/urock_processing_algorithm_dep.py | 58 +-- functions/dailyshading.py | 14 +- functions/svf_for_voxels.py | 56 +-- functions/svf_functions.py | 40 +- functions/wallalgorithms.py | 151 +++++--- plugin_upload.py | 16 +- postprocessor/solwieganalyzer_algorithm.py | 44 +-- postprocessor/spatialtc_algorithm.py | 121 ++----- postprocessor/suewsanalyzer_algorithm.py | 44 +-- postprocessor/targetanalyzer_algorithm.py | 67 +--- postprocessor/treeplanter_algorithm.py | 79 ++-- postprocessor/urock_analyser_algorithm.py | 28 +- postprocessor/uwganalyzer_algorithm.py | 44 +-- preprocessor/copernicusera5_algorithm.py | 32 +- preprocessor/dsm_generator_algorithm.py | 54 +-- preprocessor/imagemorphparms_algorithm.py | 73 +--- .../imagemorphparmspoint_algorithm.py | 55 +-- preprocessor/landcoverfraction_algorithm.py | 77 ++-- .../landcoverfractionpoint_algorithm.py | 59 +-- preprocessor/skyviewfactor_algorithm.py | 77 ++-- preprocessor/targetprepare_algorithm.py | 45 +-- preprocessor/treegenerator_algorithm.py | 30 +- preprocessor/urock_prepare_algorithm.py | 34 +- preprocessor/uwgprepare_algorithm.py | 28 +- preprocessor/wall_heightaspect_algorithm.py | 16 +- processor/sebe_algorithm.py | 75 ++-- processor/shadow_generator_algorithm.py | 44 +-- processor/solweig_algorithm.py | 207 +++-------- processor/solweig_algorithm_old.py | 341 +++++------------- processor/suews_algorithm.py | 71 +--- processor/target_algorithm.py | 60 +-- processor/urock_processing_algorithm.py | 56 +-- processor/uwg_algorithm.py | 67 +--- util/RoughnessCalcFunctionV2.py | 51 +-- util/SEBESOLWEIGCommonFiles/Perez_v3.py | 17 +- .../Solweig_v2015_metdata_noload.py | 8 +- .../clearnessindex_2013b.py | 16 +- util/SEBESOLWEIGCommonFiles/create_patches.py | 16 +- .../SEBESOLWEIGCommonFiles/diffusefraction.py | 17 +- .../shadowingfunction_wallheight_13.py | 4 +- .../shadowingfunction_wallheight_23.py | 20 +- util/SEBESOLWEIGCommonFiles/sun_position.py | 98 ++--- util/f90nml/fpy.py | 4 +- util/f90nml/namelist.py | 22 +- util/f90nml/parser.py | 21 +- util/imageMorphometricParms_v2.py | 16 +- util/landCoverFractions_v2.py | 7 +- util/misc.py | 12 +- util/ncWMSConnector.py | 39 +- util/shadowingfunctions.py | 20 +- util/ssParms.py | 8 +- util/umep_installer.py | 23 +- util/umep_solweig_export_component.py | 16 +- util/umep_suewsss_export_component.py | 116 ++---- util/umep_uwg_export_component.py | 92 ++--- 120 files changed, 1459 insertions(+), 3806 deletions(-) diff --git a/functions/SEBEfiles/SEBE_2015a_calc_forprocessing.py b/functions/SEBEfiles/SEBE_2015a_calc_forprocessing.py index 9b57e25..5cea9b4 100644 --- a/functions/SEBEfiles/SEBE_2015a_calc_forprocessing.py +++ b/functions/SEBEfiles/SEBE_2015a_calc_forprocessing.py @@ -29,7 +29,7 @@ def SEBE_2015a_calc( usevegdem, feedback, wallmaxheight, - device + device, ): # Parameters @@ -66,7 +66,9 @@ def SEBE_2015a_calc( # feedback.setProgressText('voxel:' + str(voxelheight)) # feedback.setProgressText('wallsections:' + str(wallsections)) # feedback.setProgressText('torch.shape(wallrow)[0]:' + str(torch.shape(wallrow)[0])) - wallmatrix = torch.zeros((torch.shape(wallrow)[0], int(wallsections)), device=device) + wallmatrix = torch.zeros( + (torch.shape(wallrow)[0], int(wallsections)), device=device + ) Energyyearwall = torch.clone(wallmatrix) # Main loop - Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) @@ -132,15 +134,13 @@ def SEBE_2015a_calc( ) shadow = torch.clone(sh - (1.0 - vegsh) * (1.0 - psi)) else: - sh, wallsh, wallsun, facesh, facesun = ( - shadowingfunction_wallheight_13( - a, - radmatI[index, 1], - radmatI[index, 0], - scale, - walls, - dirwalls * deg2rad, - ) + sh, wallsh, wallsun, facesh, facesun = shadowingfunction_wallheight_13( + a, + radmatI[index, 1], + radmatI[index, 0], + scale, + walls, + dirwalls * deg2rad, ) shadow = torch.clone(sh) @@ -185,8 +185,7 @@ def SEBE_2015a_calc( wallmatrix[ p, 0 : int( - wallstot[int(wallrow[p]), int(wallcol[p])] - / voxelheight + wallstot[int(wallrow[p]), int(wallcol[p])] / voxelheight ), ] = ( Iw[wallrow[p], wallcol[p]] @@ -203,9 +202,7 @@ def SEBE_2015a_calc( ) / voxelheight ) - - 1 : int( - wallstot[wallrow[p], wallcol[p]] / voxelheight - ), + - 1 : int(wallstot[wallrow[p], wallcol[p]] / voxelheight), ] = ( Iw[wallrow[p], wallcol[p]] + Dw[wallrow[p], wallcol[p]] @@ -224,13 +221,9 @@ def SEBE_2015a_calc( ) / voxelheight ), - ] = ( - Iw[wallrow[p], wallcol[p]] + Dw[wallrow[p], wallcol[p]] - ) * psi + ] = (Iw[wallrow[p], wallcol[p]] + Dw[wallrow[p], wallcol[p]]) * psi - if ( - wallsh[wallrow[p], wallcol[p]] > 0 - ): # sections in building shade + if wallsh[wallrow[p], wallcol[p]] > 0: # sections in building shade wallmatrix[ p, 0 : int(wallsh[wallrow[p], wallcol[p]] / voxelheight), diff --git a/functions/SEBEfiles/WriteMetaDataSEBE.py b/functions/SEBEfiles/WriteMetaDataSEBE.py index 40ff417..68ca3f8 100644 --- a/functions/SEBEfiles/WriteMetaDataSEBE.py +++ b/functions/SEBEfiles/WriteMetaDataSEBE.py @@ -38,21 +38,15 @@ def writeRunInfo( file.write("\n") file.write("Digital surface model (DSM): " + filepath_dsm) file.write("\n") - file.write( - "Model domain: rows = " + str(rows) + ", columns = " + str(cols) - ) + file.write("Model domain: rows = " + str(rows) + ", columns = " + str(cols)) file.write("\n") # get CRS prj = gdal_dsm.GetProjection() srs = osr.SpatialReference(wkt=prj) if srs.IsProjected: - file.write( - "Projected referece system: " + srs.GetAttrValue("projcs") - ) + file.write("Projected referece system: " + srs.GetAttrValue("projcs")) file.write("\n") - file.write( - "Geographical coordinate system: " + srs.GetAttrValue("geogcs") - ) + file.write("Geographical coordinate system: " + srs.GetAttrValue("geogcs")) file.write("\n") file.write("Latitude: " + str(lat)) file.write("\n") @@ -61,26 +55,19 @@ def writeRunInfo( file.write("UTC: " + str(UTC)) file.write("\n") if usevegdem == 1: - file.write( - "Transmissivity of light through vegetation: " + str(trans) - ) + file.write("Transmissivity of light through vegetation: " + str(trans)) file.write("\n") - file.write( - "Digital vegetation canopy model (CDSM): " + filePath_cdsm - ) + file.write("Digital vegetation canopy model (CDSM): " + filePath_cdsm) file.write("\n") if trunkfile == 1: file.write( - "Digital vegetation zrunk zone model (TDSM): " - + filePath_tdsm + "Digital vegetation zrunk zone model (TDSM): " + filePath_tdsm ) file.write("\n") else: file.write("Trunkzone estimated from CDSM") file.write("\n") - file.write( - "Trunkzone as percent of canopy height: " + str(trunkratio) - ) + file.write("Trunkzone as percent of canopy height: " + str(trunkratio)) file.write("\n") else: file.write("Vegetation scheme inactive") diff --git a/functions/SEBEfiles/importdata.py b/functions/SEBEfiles/importdata.py index 0b6bd39..2080e54 100644 --- a/functions/SEBEfiles/importdata.py +++ b/functions/SEBEfiles/importdata.py @@ -73,9 +73,7 @@ def importdata(*args, device): if not isinstance(fileName, str): raise TypeError("importdata: File name needs to be a string.") if "-pastespecial" in fileName: - raise ValueError( - "importdata: Option " "-pastespecial" " not implemented." - ) + raise ValueError("importdata: Option " "-pastespecial" " not implemented.") if nargin > 1: delimiter = args[1] @@ -83,9 +81,7 @@ def importdata(*args, device): if not isinstance(delimiter, str): raise TypeError("importdata: Delimiter needs to be a character.") if len(delimiter) > 1 and not delimiter is "\t": - raise ValueError( - "importdata: Delimiter cannot be longer than 1 character." - ) + raise ValueError("importdata: Delimiter cannot be longer than 1 character.") if delimiter is "\\": delimiter = "\\\\" # if delimiter is "\" change to "\\" @@ -106,13 +102,9 @@ def importdata(*args, device): ext = ext.lower() # Make sure file extension is in lower case. if ext in [".au", ".snd"]: - raise ValueError( - "importdata: Not implemented for file format " + ext + "." - ) + raise ValueError("importdata: Not implemented for file format " + ext + ".") elif ext is ".avi": - raise ValueError( - "importdata: Not implemented for file format " + ext + "." - ) + raise ValueError("importdata: Not implemented for file format " + ext + ".") elif ext in [ ".bmp", ".cur", @@ -137,9 +129,7 @@ def importdata(*args, device): headerRows = 0 img = Image.open(fileName) output["cdata"] = torch.tensor(img, device=device) - output["colormap"] = ( - img.mode - ) # TODO: check if this method is euaivalent + output["colormap"] = img.mode # TODO: check if this method is euaivalent output["alpha"] = img.split()[-1] elif ext is ".mat": import scipy.io as sio @@ -148,20 +138,14 @@ def importdata(*args, device): headerRows = 0 output = sio.loadmat(fileName) elif ext is ".wk1": - raise ValueError( - "importdata: Not implemented for file format " + ext + "." - ) + raise ValueError("importdata: Not implemented for file format " + ext + ".") elif ext in [".xls", ".xlsx"]: - raise ValueError( - "importdata: Not implemented for file format " + ext + "." - ) + raise ValueError("importdata: Not implemented for file format " + ext + ".") elif ext in [".wav", ".wave"]: # delimiter = None # headerRows = 0 # [output.data, output.fs] = wavread(fileName) - raise ValueError( - "importdata: Not implemented for file format " + ext + "." - ) + raise ValueError("importdata: Not implemented for file format " + ext + ".") else: # Assume the file is in ascii format. output, delimiter, headerRows = importdata_ascii( @@ -174,9 +158,7 @@ def importdata(*args, device): for ( key, val, - ) in ( - output.copy().items() - ): # copy() for py3 compatibility or use items() + ) in output.copy().items(): # copy() for py3 compatibility or use items() if not val: del output[key] @@ -255,7 +237,10 @@ def importdata_ascii(fileName, delimiter, headerRows, device): # print "headerRows py:", headerRows # Go through the data and put it in either output.data or output.textdata depending on if it is numeric or not. output["data"] = ( - torch.from_numpy(np.empty((len(fileContentRows) - headerRows, dataColumns)), device=device) * torch.nan + torch.from_numpy( + np.empty((len(fileContentRows) - headerRows, dataColumns)), device=device + ) + * torch.nan ) for i, line in enumerate(fileContentRows[headerRows:]): # Only use the row if it contains anything other than white-space characters. diff --git a/functions/SEBEfiles/sunmapcreator_2015a.py b/functions/SEBEfiles/sunmapcreator_2015a.py index 6b93a6a..5f0e2c1 100644 --- a/functions/SEBEfiles/sunmapcreator_2015a.py +++ b/functions/SEBEfiles/sunmapcreator_2015a.py @@ -26,7 +26,6 @@ def sunmapcreator_2015a( :return: """ - # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) patch_option = 1 # 145 patches ( @@ -47,13 +46,19 @@ def sunmapcreator_2015a( ) radmatI = torch.transpose( - torch.vstack((iangle2, skyvaultazi, torch.zeros((13, len(iangle2)), device=device))) + torch.vstack( + (iangle2, skyvaultazi, torch.zeros((13, len(iangle2)), device=device)) + ) ) radmatD = torch.transpose( - torch.vstack((iangle2, skyvaultazi, torch.zeros((13, len(iangle2)), device=device))) + torch.vstack( + (iangle2, skyvaultazi, torch.zeros((13, len(iangle2)), device=device)) + ) ) radmatR = torch.transpose( - torch.vstack((iangle2, skyvaultazi, torch.zeros((13, len(iangle2)), device=device))) + torch.vstack( + (iangle2, skyvaultazi, torch.zeros((13, len(iangle2)), device=device)) + ) ) iazimuth = skyvaultazi @@ -114,19 +119,13 @@ def sunmapcreator_2015a( radmatD[:, 2] = radmatD[:, 2] + D * lv[:, 2] radmatR[:, 2] = radmatR[:, 2] + G * (1 / 145) * albedo - if output["energymonth"] == 1: - radmatI[azipos2, met[i, 1] + 2] = ( - radmatI[azipos2, met[i, 1] + 2] + I - ) - radmatD[:, met[i, 1] + 2] = ( - radmatD[:, met[i, 1] + 2] + D * lv[:, 2] - ) + radmatI[azipos2, met[i, 1] + 2] = radmatI[azipos2, met[i, 1] + 2] + I + radmatD[:, met[i, 1] + 2] = radmatD[:, met[i, 1] + 2] + D * lv[:, 2] radmatR[:, met[i, 1] + 2] = ( radmatR[:, met[i, 1] + 2] + G * (1 / 145) * albedo ) - # Adjusting the numbers if multiple years is used if torch.shape(met)[0] > 8760: diff --git a/functions/SOLWEIGpython/COMFA/CNRRabs_Total.py b/functions/SOLWEIGpython/COMFA/CNRRabs_Total.py index eef0828..f5bde5a 100644 --- a/functions/SOLWEIGpython/COMFA/CNRRabs_Total.py +++ b/functions/SOLWEIGpython/COMFA/CNRRabs_Total.py @@ -71,7 +71,5 @@ def CNRRabs_Total( # CNRRabs_Total.m:41 Acyl = CRT_Acyl(L, D) # CNRRabs_Total.m:43 - Total_CNRRabs = multiply( - Aeff, ((Kin_abs + Kup_abs + Lin_abs + Lup_abs) / (Acyl)) - ) + Total_CNRRabs = multiply(Aeff, ((Kin_abs + Kup_abs + Lin_abs + Lup_abs) / (Acyl))) # CNRRabs_Total.m:46 diff --git a/functions/SOLWEIGpython/COMFA/Ratio_Kb.py b/functions/SOLWEIGpython/COMFA/Ratio_Kb.py index 023ddf4..a02f823 100644 --- a/functions/SOLWEIGpython/COMFA/Ratio_Kb.py +++ b/functions/SOLWEIGpython/COMFA/Ratio_Kb.py @@ -5,9 +5,7 @@ @function -def Ratio_Kb( - Kin=None, A=None, lat=None, d=None, t=None, Atr=None, *args, **kwargs -): +def Ratio_Kb(Kin=None, A=None, lat=None, d=None, t=None, Atr=None, *args, **kwargs): varargin = Ratio_Kb.varargin nargin = Ratio_Kb.nargin diff --git a/functions/SOLWEIGpython/COMFA/radiationfunctionsCOMFA.py b/functions/SOLWEIGpython/COMFA/radiationfunctionsCOMFA.py index d2e2b98..6e0fd12 100644 --- a/functions/SOLWEIGpython/COMFA/radiationfunctionsCOMFA.py +++ b/functions/SOLWEIGpython/COMFA/radiationfunctionsCOMFA.py @@ -13,9 +13,7 @@ from ....util.SEBESOLWEIGCommonFiles.diffusefraction import diffusefraction -def CNRRabs_Total( - alpha, L, D, Lin, Lup, Kin, Kup, Atr, d, t, lat, A, emis, Aeff -): +def CNRRabs_Total(alpha, L, D, Lin, Lup, Kin, Kup, Atr, d, t, lat, A, emis, Aeff): # VERTICAL CYLINDER MODEL # Used to calculate the total radiation absorbed by a cylinder, such as the CRT, with inputs # alpha (albedo of cylinder), Kin (incoming shortwave measured by CNR net @@ -58,9 +56,7 @@ def CNRRabs_Total( Lin_abs = LinMeas_abs(L, D, Lin, emis) Lup_abs = LupMeas_abs(L, D, Lup, emis) Acyl = CRT_Acyl(L, D) - Total_CNRRabs = np.dot( - Aeff, ((Kin_abs + Kup_abs + Lin_abs + Lup_abs) / (Acyl)) - ) + Total_CNRRabs = np.dot(Aeff, ((Kin_abs + Kup_abs + Lin_abs + Lup_abs) / (Acyl))) return Total_CNRRabs, zen, Kd @@ -105,9 +101,7 @@ def COMFA_RAD_SPATIAL_TC( Lup_abs = LupMeas_abs(L, D, Lup, emis) Acyl = CRT_Acyl(L, D) # Total_CNRRabs_solweig = np.dot(Aeff,((Kin_abs + Kup_abs + Lin_abs + Lup_abs) / (Acyl))) - Total_CNRRabs_solweig = Aeff * ( - (Kin_abs + Kup_abs + Lin_abs + Lup_abs) / Acyl - ) + Total_CNRRabs_solweig = Aeff * ((Kin_abs + Kup_abs + Lin_abs + Lup_abs) / Acyl) return Total_CNRRabs_solweig @@ -189,9 +183,7 @@ def Rad_Total_solweig( Lup_abs = LupMeas_abs(L, D, Lup, emis) Acyl = CRT_Acyl(L, D) # Total_CNRRabs_solweig = np.dot(Aeff,((Kin_abs + Kup_abs + Lin_abs + Lup_abs) / (Acyl))) - Total_CNRRabs_solweig = Aeff * ( - (Kin_abs + Kup_abs + Lin_abs + Lup_abs) / Acyl - ) + Total_CNRRabs_solweig = Aeff * ((Kin_abs + Kup_abs + Lin_abs + Lup_abs) / Acyl) return Total_CNRRabs_solweig, zen / (np.pi / 180), Kd @@ -210,9 +202,7 @@ def CNR_Kinabs_meas(alpha, Kin, L, D, A, lat, d, t, Atr): Kd = Kin - Kb zen = solar_zenith(lat, d, t) Acs = CRT_Acs(L, D) - Kb_abs = np.dot( - np.dot((1 - alpha), (np.dot(Kb, np.sin(zen * deg2rad)))), Acs - ) + Kb_abs = np.dot(np.dot((1 - alpha), (np.dot(Kb, np.sin(zen * deg2rad)))), Acs) Acyl = CRT_Acyl(L, D) Kd_abs = np.dot(np.dot(np.dot((1 - alpha), Kd), 0.5), Acyl) Kin_abs = Kb_abs + Kd_abs @@ -270,9 +260,7 @@ def solar_zenith(lat, d, t): deg2rad = np.pi / 180.0 # make sure to change latitudinal correction for whatever city you are in. - LC = ( - -49 / 60.0 - ) # TODO Remove since is should vary with location. What us this? + LC = -49 / 60.0 # TODO Remove since is should vary with location. What us this? ET = solar_ET(d) to = 12 - LC - ET diff --git a/functions/SOLWEIGpython/Kside_veg_v2019a.py b/functions/SOLWEIGpython/Kside_veg_v2019a.py index 9a9eeec..c0ea39e 100644 --- a/functions/SOLWEIGpython/Kside_veg_v2019a.py +++ b/functions/SOLWEIGpython/Kside_veg_v2019a.py @@ -52,38 +52,22 @@ def Kside_veg_v2019a( KnorthI = 0 else: ### Kside with weights ### if azimuth > (360 - t) or azimuth <= (180 - t): - KeastI = ( - radI - * shadow - * np.cos(altitude * deg2rad) - * np.sin(aziE * deg2rad) - ) + KeastI = radI * shadow * np.cos(altitude * deg2rad) * np.sin(aziE * deg2rad) else: KeastI = 0 if azimuth > (90 - t) and azimuth <= (270 - t): KsouthI = ( - radI - * shadow - * np.cos(altitude * deg2rad) - * np.sin(aziS * deg2rad) + radI * shadow * np.cos(altitude * deg2rad) * np.sin(aziS * deg2rad) ) else: KsouthI = 0 if azimuth > (180 - t) and azimuth <= (360 - t): - KwestI = ( - radI - * shadow - * np.cos(altitude * deg2rad) - * np.sin(aziW * deg2rad) - ) + KwestI = radI * shadow * np.cos(altitude * deg2rad) * np.sin(aziW * deg2rad) else: KwestI = 0 if azimuth <= (90 - t) or azimuth > (270 - t): KnorthI = ( - radI - * shadow - * np.cos(altitude * deg2rad) - * np.sin(aziN * deg2rad) + radI * shadow * np.cos(altitude * deg2rad) * np.sin(aziN * deg2rad) ) else: KnorthI = 0 @@ -129,8 +113,7 @@ def Kside_veg_v2019a( aziDel = 360 phiVar[ix] = (aziDel * deg2rad) * ( - np.sin((aniAlt[ix] + 6) * deg2rad) - - np.sin((aniAlt[ix] - 6) * deg2rad) + np.sin((aniAlt[ix] + 6) * deg2rad) - np.sin((aniAlt[ix] - 6) * deg2rad) ) # Solid angle / Steradian radTot = radTot + ( @@ -147,24 +130,19 @@ def Kside_veg_v2019a( np.pi / 2 ) # Angle of incidence, np.cos(0) because cylinder - always perpendicular KsideD = ( - KsideD - + diffsh[:, :, idx] * lumChi[idx] * anglIncC * phiVar[idx] + KsideD + diffsh[:, :, idx] * lumChi[idx] * anglIncC * phiVar[idx] ) # Diffuse vertical radiation Keast = ( - albedo * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) - + KupE + albedo * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) + KupE ) * 0.5 Ksouth = ( - albedo * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) - + KupS + albedo * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) + KupS ) * 0.5 Kwest = ( - albedo * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) - + KupW + albedo * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + KupW ) * 0.5 Knorth = ( - albedo * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) - + KupN + albedo * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + KupN ) * 0.5 else: # Box diffRadE = np.zeros((rows, cols)) @@ -176,107 +154,71 @@ def Kside_veg_v2019a( if aniAzi[idx] <= (180): anglIncE = np.cos(aniAlt[idx] * deg2rad) * np.cos( (90 - aniAzi[idx]) * deg2rad - ) * np.sin(np.pi / 2) + np.sin( - aniAlt[idx] * deg2rad - ) * np.cos( + ) * np.sin(np.pi / 2) + np.sin(aniAlt[idx] * deg2rad) * np.cos( np.pi / 2 ) diffRadE = ( diffRadE - + diffsh[:, :, idx] - * lumChi[idx] - * anglIncE - * phiVar[idx] + + diffsh[:, :, idx] * lumChi[idx] * anglIncE * phiVar[idx] ) # * 0.5 if aniAzi[idx] > (90) and aniAzi[idx] <= (270): anglIncS = np.cos(aniAlt[idx] * deg2rad) * np.cos( (180 - aniAzi[idx]) * deg2rad - ) * np.sin(np.pi / 2) + np.sin( - aniAlt[idx] * deg2rad - ) * np.cos( + ) * np.sin(np.pi / 2) + np.sin(aniAlt[idx] * deg2rad) * np.cos( np.pi / 2 ) diffRadS = ( diffRadS - + diffsh[:, :, idx] - * lumChi[idx] - * anglIncS - * phiVar[idx] + + diffsh[:, :, idx] * lumChi[idx] * anglIncS * phiVar[idx] ) # * 0.5 if aniAzi[idx] > (180) and aniAzi[idx] <= (360): anglIncW = np.cos(aniAlt[idx] * deg2rad) * np.cos( (270 - aniAzi[idx]) * deg2rad - ) * np.sin(np.pi / 2) + np.sin( - aniAlt[idx] * deg2rad - ) * np.cos( + ) * np.sin(np.pi / 2) + np.sin(aniAlt[idx] * deg2rad) * np.cos( np.pi / 2 ) diffRadW = ( diffRadW - + diffsh[:, :, idx] - * lumChi[idx] - * anglIncW - * phiVar[idx] + + diffsh[:, :, idx] * lumChi[idx] * anglIncW * phiVar[idx] ) # * 0.5 if aniAzi[idx] > (270) or aniAzi[idx] <= (90): anglIncN = np.cos(aniAlt[idx] * deg2rad) * np.cos( (0 - aniAzi[idx]) * deg2rad - ) * np.sin(np.pi / 2) + np.sin( - aniAlt[idx] * deg2rad - ) * np.cos( + ) * np.sin(np.pi / 2) + np.sin(aniAlt[idx] * deg2rad) * np.cos( np.pi / 2 ) diffRadN = ( diffRadN - + diffsh[:, :, idx] - * lumChi[idx] - * anglIncN - * phiVar[idx] + + diffsh[:, :, idx] * lumChi[idx] * anglIncN * phiVar[idx] ) # * 0.5 KeastDG = ( diffRadE - + ( - albedo - * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) - + KupE - ) + + (albedo * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) + KupE) * 0.5 ) Keast = KeastI + KeastDG KsouthDG = ( diffRadS - + ( - albedo - * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) - + KupS - ) + + (albedo * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) + KupS) * 0.5 ) Ksouth = KsouthI + KsouthDG KwestDG = ( diffRadW - + ( - albedo - * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) - + KupW - ) + + (albedo * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + KupW) * 0.5 ) Kwest = KwestI + KwestDG KnorthDG = ( diffRadN - + ( - albedo - * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) - + KupN - ) + + (albedo * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + KupN) * 0.5 ) Knorth = KnorthI + KnorthDG diff --git a/functions/SOLWEIGpython/Kside_veg_v2022a.py b/functions/SOLWEIGpython/Kside_veg_v2022a.py index 11e379c..7e499ec 100644 --- a/functions/SOLWEIGpython/Kside_veg_v2022a.py +++ b/functions/SOLWEIGpython/Kside_veg_v2022a.py @@ -96,34 +96,22 @@ def Kside_veg_v2022a( else: KeastI = torch.where( (azimuth > (360 - t)) | (azimuth <= (180 - t)), - radI - * shadow - * torch.cos(altitude * deg2rad) - * torch.sin(aziE * deg2rad), + radI * shadow * torch.cos(altitude * deg2rad) * torch.sin(aziE * deg2rad), torch.zeros((rows, cols), device=device), ) KsouthI = torch.where( (azimuth > (90 - t)) & (azimuth <= (270 - t)), - radI - * shadow - * torch.cos(altitude * deg2rad) - * torch.sin(aziS * deg2rad), + radI * shadow * torch.cos(altitude * deg2rad) * torch.sin(aziS * deg2rad), torch.zeros((rows, cols), device=device), ) KwestI = torch.where( (azimuth > (180 - t)) & (azimuth <= (360 - t)), - radI - * shadow - * torch.cos(altitude * deg2rad) - * torch.sin(aziW * deg2rad), + radI * shadow * torch.cos(altitude * deg2rad) * torch.sin(aziW * deg2rad), torch.zeros((rows, cols), device=device), ) KnorthI = torch.where( (azimuth <= (90 - t)) | (azimuth > (270 - t)), - radI - * shadow - * torch.cos(altitude * deg2rad) - * torch.sin(aziN * deg2rad), + radI * shadow * torch.cos(altitude * deg2rad) * torch.sin(aziN * deg2rad), torch.zeros((rows, cols), device=device), ) KsideI = shadow * 0 @@ -161,21 +149,15 @@ def Kside_veg_v2022a( steradian[i] = ( (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad ) * ( - torch.sin( - (patch_altitude[i] + patch_altitude[0]) * deg2rad - ) - - torch.sin( - (patch_altitude[i] - patch_altitude[0]) * deg2rad - ) + torch.sin((patch_altitude[i] + patch_altitude[0]) * deg2rad) + - torch.sin((patch_altitude[i] - patch_altitude[0]) * deg2rad) ) else: steradian[i] = ( (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad ) * ( torch.sin((patch_altitude[i]) * deg2rad) - - torch.sin( - (patch_altitude[i - 1] + patch_altitude[0]) * deg2rad - ) + - torch.sin((patch_altitude[i - 1] + patch_altitude[0]) * deg2rad) ) radTot += ( @@ -188,22 +170,17 @@ def Kside_veg_v2022a( if cyl == 1: for idx in range(patch_azimuth.shape[0]): - anglIncC = torch.cos( - patch_altitude[idx] * deg2rad - ) * torch.cos(torch.tensor(0.0)) - KsideD += ( - diffsh[:, :, idx] * lumChi[idx] * anglIncC * steradian[idx] + anglIncC = torch.cos(patch_altitude[idx] * deg2rad) * torch.cos( + torch.tensor(0.0) ) + KsideD += diffsh[:, :, idx] * lumChi[idx] * anglIncC * steradian[idx] sunlit_surface = ( - albedo * (radI * torch.cos(altitude * deg2rad)) - + (radD * 0.5) + albedo * (radI * torch.cos(altitude * deg2rad)) + (radD * 0.5) ) / torch.pi shaded_surface = (albedo * radD * 0.5) / torch.pi - temp_vegsh = (vegshmat[:, :, idx] == 0) | ( - vbshvegshmat[:, :, idx] == 0 - ) + temp_vegsh = (vegshmat[:, :, idx] == 0) | (vbshvegshmat[:, :, idx] == 0) Kref_veg += ( shaded_surface * temp_vegsh @@ -246,58 +223,43 @@ def Kside_veg_v2022a( else: for idx in range(patch_azimuth.shape[0]): if (patch_azimuth[idx] > 360) or (patch_azimuth[idx] <= 180): - anglIncE = torch.cos( - patch_altitude[idx] * deg2rad - ) * torch.cos((90 - patch_azimuth[idx] + t) * deg2rad) + anglIncE = torch.cos(patch_altitude[idx] * deg2rad) * torch.cos( + (90 - patch_azimuth[idx] + t) * deg2rad + ) diffRadE += ( - diffsh[:, :, idx] - * lumChi[idx] - * anglIncE - * steradian[idx] + diffsh[:, :, idx] * lumChi[idx] * anglIncE * steradian[idx] ) if (patch_azimuth[idx] > 90) and (patch_azimuth[idx] <= 270): - anglIncS = torch.cos( - patch_altitude[idx] * deg2rad - ) * torch.cos((180 - patch_azimuth[idx] + t) * deg2rad) + anglIncS = torch.cos(patch_altitude[idx] * deg2rad) * torch.cos( + (180 - patch_azimuth[idx] + t) * deg2rad + ) diffRadS += ( - diffsh[:, :, idx] - * lumChi[idx] - * anglIncS - * steradian[idx] + diffsh[:, :, idx] * lumChi[idx] * anglIncS * steradian[idx] ) if (patch_azimuth[idx] > 180) and (patch_azimuth[idx] <= 360): - anglIncW = torch.cos( - patch_altitude[idx] * deg2rad - ) * torch.cos((270 - patch_azimuth[idx] + t) * deg2rad) + anglIncW = torch.cos(patch_altitude[idx] * deg2rad) * torch.cos( + (270 - patch_azimuth[idx] + t) * deg2rad + ) diffRadW += ( - diffsh[:, :, idx] - * lumChi[idx] - * anglIncW - * steradian[idx] + diffsh[:, :, idx] * lumChi[idx] * anglIncW * steradian[idx] ) if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] <= 90): - anglIncN = torch.cos( - patch_altitude[idx] * deg2rad - ) * torch.cos((0 - patch_azimuth[idx] + t) * deg2rad) + anglIncN = torch.cos(patch_altitude[idx] * deg2rad) * torch.cos( + (0 - patch_azimuth[idx] + t) * deg2rad + ) diffRadN += ( - diffsh[:, :, idx] - * lumChi[idx] - * anglIncN - * steradian[idx] + diffsh[:, :, idx] * lumChi[idx] * anglIncN * steradian[idx] ) sunlit_surface = ( - albedo * (radI * torch.cos(altitude * deg2rad)) - + (radD * 0.5) + albedo * (radI * torch.cos(altitude * deg2rad)) + (radD * 0.5) ) / torch.pi shaded_surface = (albedo * radD * 0.5) / torch.pi - temp_vegsh = (vegshmat[:, :, idx] == 0) | ( - vbshvegshmat[:, :, idx] == 0 - ) + temp_vegsh = (vegshmat[:, :, idx] == 0) | (vbshvegshmat[:, :, idx] == 0) Kref_veg += ( shaded_surface * temp_vegsh @@ -365,18 +327,14 @@ def Kside_veg_v2022a( * torch.cos(patch_altitude[idx] * deg2rad) ) - if (patch_azimuth[idx] > 360) or ( - patch_azimuth[idx] < 180 - ): + if (patch_azimuth[idx] > 360) or (patch_azimuth[idx] < 180): Kref_sun_e += ( sunlit_surface * sunlit_patches * steradian[idx] * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos( - (90 - patch_azimuth[idx] + t) * deg2rad - ) + * torch.cos((90 - patch_azimuth[idx] + t) * deg2rad) ) Kref_sh_e += ( shaded_surface @@ -384,22 +342,16 @@ def Kside_veg_v2022a( * steradian[idx] * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos( - (90 - patch_azimuth[idx] + t) * deg2rad - ) + * torch.cos((90 - patch_azimuth[idx] + t) * deg2rad) ) - if (patch_azimuth[idx] > 90) and ( - patch_azimuth[idx] < 270 - ): + if (patch_azimuth[idx] > 90) and (patch_azimuth[idx] < 270): Kref_sun_s += ( sunlit_surface * sunlit_patches * steradian[idx] * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos( - (180 - patch_azimuth[idx] + t) * deg2rad - ) + * torch.cos((180 - patch_azimuth[idx] + t) * deg2rad) ) Kref_sh_s += ( shaded_surface @@ -407,22 +359,16 @@ def Kside_veg_v2022a( * steradian[idx] * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos( - (180 - patch_azimuth[idx] + t) * deg2rad - ) + * torch.cos((180 - patch_azimuth[idx] + t) * deg2rad) ) - if (patch_azimuth[idx] > 180) and ( - patch_azimuth[idx] < 360 - ): + if (patch_azimuth[idx] > 180) and (patch_azimuth[idx] < 360): Kref_sun_w += ( sunlit_surface * sunlit_patches * steradian[idx] * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos( - (270 - patch_azimuth[idx] + t) * deg2rad - ) + * torch.cos((270 - patch_azimuth[idx] + t) * deg2rad) ) Kref_sh_w += ( shaded_surface @@ -430,9 +376,7 @@ def Kside_veg_v2022a( * steradian[idx] * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos( - (270 - patch_azimuth[idx] + t) * deg2rad - ) + * torch.cos((270 - patch_azimuth[idx] + t) * deg2rad) ) if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] < 90): Kref_sun_n += ( @@ -459,41 +403,29 @@ def Kside_veg_v2022a( * torch.cos(patch_altitude[idx] * deg2rad) ) - if (patch_azimuth[idx] > 360) or ( - patch_azimuth[idx] < 180 - ): + if (patch_azimuth[idx] > 360) or (patch_azimuth[idx] < 180): Kref_sh_e += ( shaded_surface * steradian[idx] * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos( - (90 - patch_azimuth[idx] + t) * deg2rad - ) + * torch.cos((90 - patch_azimuth[idx] + t) * deg2rad) ) - if (patch_azimuth[idx] > 90) and ( - patch_azimuth[idx] < 270 - ): + if (patch_azimuth[idx] > 90) and (patch_azimuth[idx] < 270): Kref_sh_s += ( shaded_surface * steradian[idx] * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos( - (180 - patch_azimuth[idx] + t) * deg2rad - ) + * torch.cos((180 - patch_azimuth[idx] + t) * deg2rad) ) - if (patch_azimuth[idx] > 180) and ( - patch_azimuth[idx] < 360 - ): + if (patch_azimuth[idx] > 180) and (patch_azimuth[idx] < 360): Kref_sh_w += ( shaded_surface * steradian[idx] * torch.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos( - (270 - patch_azimuth[idx] + t) * deg2rad - ) + * torch.cos((270 - patch_azimuth[idx] + t) * deg2rad) ) if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] < 90): Kref_sh_n += ( @@ -504,37 +436,13 @@ def Kside_veg_v2022a( * torch.cos((0 - patch_azimuth[idx] + t) * deg2rad) ) - Keast = ( - KeastI - + diffRadE - + Kref_sun_e - + Kref_sh_e - + Kref_veg_e - + KupE * 0.5 - ) - Kwest = ( - KwestI - + diffRadW - + Kref_sun_w - + Kref_sh_w - + Kref_veg_w - + KupW * 0.5 - ) + Keast = KeastI + diffRadE + Kref_sun_e + Kref_sh_e + Kref_veg_e + KupE * 0.5 + Kwest = KwestI + diffRadW + Kref_sun_w + Kref_sh_w + Kref_veg_w + KupW * 0.5 Knorth = ( - KnorthI - + diffRadN - + Kref_sun_n - + Kref_sh_n - + Kref_veg_n - + KupN * 0.5 + KnorthI + diffRadN + Kref_sun_n + Kref_sh_n + Kref_veg_n + KupN * 0.5 ) Ksouth = ( - KsouthI - + diffRadS - + Kref_sun_s - + Kref_sh_s - + Kref_veg_s - + KupS * 0.5 + KsouthI + diffRadS + Kref_sun_s + Kref_sh_s + Kref_veg_s + KupS * 0.5 ) else: diff --git a/functions/SOLWEIGpython/Kup_veg_2015a.py b/functions/SOLWEIGpython/Kup_veg_2015a.py index 2b1b9b8..70ce7b1 100644 --- a/functions/SOLWEIGpython/Kup_veg_2015a.py +++ b/functions/SOLWEIGpython/Kup_veg_2015a.py @@ -23,28 +23,23 @@ def Kup_veg_2015a( ): Kup = (gvfalb * radI * torch.sin(altitude * (torch.pi / 180.0))) + ( - radD * svfbuveg - + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) + radD * svfbuveg + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) ) * gvfalbnosh KupE = (gvfalbE * radI * torch.sin(altitude * (torch.pi / 180.0))) + ( - radD * svfbuveg - + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) + radD * svfbuveg + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) ) * gvfalbnoshE KupS = (gvfalbS * radI * torch.sin(altitude * (torch.pi / 180.0))) + ( - radD * svfbuveg - + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) + radD * svfbuveg + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) ) * gvfalbnoshS KupW = (gvfalbW * radI * torch.sin(altitude * (torch.pi / 180.0))) + ( - radD * svfbuveg - + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) + radD * svfbuveg + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) ) * gvfalbnoshW KupN = (gvfalbN * radI * torch.sin(altitude * (torch.pi / 180.0))) + ( - radD * svfbuveg - + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) + radD * svfbuveg + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) ) * gvfalbnoshN return Kup, KupE, KupS, KupW, KupN diff --git a/functions/SOLWEIGpython/Lcyl_v2022a.py b/functions/SOLWEIGpython/Lcyl_v2022a.py index a7fc5da..f51b0f1 100644 --- a/functions/SOLWEIGpython/Lcyl_v2022a.py +++ b/functions/SOLWEIGpython/Lcyl_v2022a.py @@ -67,17 +67,13 @@ def Lcyl_v2022a( for i in range(patch_altitude.shape[0]): # If there are more than one patch in a band if skyalt_c[skyalt == patch_altitude[i]] > 1: - steradian[i] = ( - (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad - ) * ( + steradian[i] = ((360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad) * ( np.sin((patch_altitude[i] + patch_altitude[0]) * deg2rad) - np.sin((patch_altitude[i] - patch_altitude[0]) * deg2rad) ) # If there is only one patch in band, i.e. 90 degrees else: - steradian[i] = ( - (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad - ) * ( + steradian[i] = ((360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad) * ( np.sin((patch_altitude[i]) * deg2rad) - np.sin((patch_altitude[i - 1] + patch_altitude[0]) * deg2rad) ) diff --git a/functions/SOLWEIGpython/Lside_veg.py b/functions/SOLWEIGpython/Lside_veg.py index b54d1a3..386191c 100644 --- a/functions/SOLWEIGpython/Lside_veg.py +++ b/functions/SOLWEIGpython/Lside_veg.py @@ -80,9 +80,7 @@ def Lside_veg_v2022a( ) ## Least - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( - svfE, svfEveg, svfEaveg, vikttot - ) + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg(svfE, svfEveg, svfEaveg, vikttot) if altitude > 0: # daytime alfaB = torch.arctan(svfalfaE) @@ -93,18 +91,13 @@ def Lside_veg_v2022a( Lwallsun = ( SBC * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziE * (torch.pi / 180))) - ** 4 - ) + * ((Ta + 273.15 + Tw * torch.sin(aziE * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) * torch.cos(betasun) * 0.5 ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 else: Lwallsun = 0 Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 @@ -126,9 +119,7 @@ def Lside_veg_v2022a( # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky ## Lsouth - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( - svfS, svfSveg, svfSaveg, vikttot - ) + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg(svfS, svfSveg, svfSaveg, vikttot) if altitude > 0: # daytime alfaB = torch.arctan(svfalfaS) @@ -139,18 +130,13 @@ def Lside_veg_v2022a( Lwallsun = ( SBC * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziS * (torch.pi / 180))) - ** 4 - ) + * ((Ta + 273.15 + Tw * torch.sin(aziS * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) * torch.cos(betasun) * 0.5 ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 else: Lwallsun = 0 Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 @@ -172,9 +158,7 @@ def Lside_veg_v2022a( # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky ## Lwest - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( - svfW, svfWveg, svfWaveg, vikttot - ) + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg(svfW, svfWveg, svfWaveg, vikttot) if altitude > 0: # daytime alfaB = torch.arctan(svfalfaW) @@ -185,18 +169,13 @@ def Lside_veg_v2022a( Lwallsun = ( SBC * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziW * (torch.pi / 180))) - ** 4 - ) + * ((Ta + 273.15 + Tw * torch.sin(aziW * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) * torch.cos(betasun) * 0.5 ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 else: Lwallsun = 0 Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 @@ -218,9 +197,7 @@ def Lside_veg_v2022a( # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky ## Lnorth - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( - svfN, svfNveg, svfNaveg, vikttot - ) + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg(svfN, svfNveg, svfNaveg, vikttot) if altitude > 0: # daytime alfaB = torch.arctan(svfalfaN) @@ -231,18 +208,13 @@ def Lside_veg_v2022a( Lwallsun = ( SBC * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziN * (torch.pi / 180))) - ** 4 - ) + * ((Ta + 273.15 + Tw * torch.sin(aziN * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) * torch.cos(betasun) * 0.5 ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 else: Lwallsun = 0 Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 @@ -338,9 +310,7 @@ def Lside_veg_v2026( ) ## Least - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( - svfE, svfEveg, svfEaveg, vikttot - ) + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg(svfE, svfEveg, svfEaveg, vikttot) if anisotropic_longwave == 1: Least = torch.zeros_like(Ldown, device=device) @@ -359,18 +329,13 @@ def Lside_veg_v2026( Lwallsun = ( SBC * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziE * (torch.pi / 180))) - ** 4 - ) + * ((Ta + 273.15 + Tw * torch.sin(aziE * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) * torch.cos(betasun) * 0.5 ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 else: Lwallsun = 0 Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 @@ -399,18 +364,13 @@ def Lside_veg_v2026( Lwallsun = ( SBC * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziS * (torch.pi / 180))) - ** 4 - ) + * ((Ta + 273.15 + Tw * torch.sin(aziS * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) * torch.cos(betasun) * 0.5 ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 else: Lwallsun = 0 Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 @@ -439,18 +399,13 @@ def Lside_veg_v2026( Lwallsun = ( SBC * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziW * (torch.pi / 180))) - ** 4 - ) + * ((Ta + 273.15 + Tw * torch.sin(aziW * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) * torch.cos(betasun) * 0.5 ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 else: Lwallsun = 0 Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 @@ -479,18 +434,13 @@ def Lside_veg_v2026( Lwallsun = ( SBC * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziN * (torch.pi / 180))) - ** 4 - ) + * ((Ta + 273.15 + Tw * torch.sin(aziN * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) * torch.cos(betasun) * 0.5 ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 else: Lwallsun = 0 Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 diff --git a/functions/SOLWEIGpython/Lside_veg_v2015a.py b/functions/SOLWEIGpython/Lside_veg_v2015a.py index 7723f8d..c414093 100644 --- a/functions/SOLWEIGpython/Lside_veg_v2015a.py +++ b/functions/SOLWEIGpython/Lside_veg_v2015a.py @@ -65,9 +65,7 @@ def Lside_veg_v2015a( ) ## Least - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( - svfE, svfEveg, svfEaveg, vikttot - ) + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg(svfE, svfEveg, svfEaveg, vikttot) if altitude > 0: # daytime alfaB = torch.arctan(svfalfaE) @@ -78,18 +76,13 @@ def Lside_veg_v2015a( Lwallsun = ( SBC * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziE * (torch.pi / 180))) - ** 4 - ) + * ((Ta + 273.15 + Tw * torch.sin(aziE * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) * torch.cos(betasun) * 0.5 ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 else: Lwallsun = 0 Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 @@ -106,9 +99,7 @@ def Lside_veg_v2015a( # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky ## Lsouth - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( - svfS, svfSveg, svfSaveg, vikttot - ) + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg(svfS, svfSveg, svfSaveg, vikttot) if altitude > 0: # daytime alfaB = torch.arctan(svfalfaS) @@ -119,18 +110,13 @@ def Lside_veg_v2015a( Lwallsun = ( SBC * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziS * (torch.pi / 180))) - ** 4 - ) + * ((Ta + 273.15 + Tw * torch.sin(aziS * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) * torch.cos(betasun) * 0.5 ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 else: Lwallsun = 0 Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 @@ -147,9 +133,7 @@ def Lside_veg_v2015a( # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky ## Lwest - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( - svfW, svfWveg, svfWaveg, vikttot - ) + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg(svfW, svfWveg, svfWaveg, vikttot) if altitude > 0: # daytime alfaB = torch.arctan(svfalfaW) @@ -160,18 +144,13 @@ def Lside_veg_v2015a( Lwallsun = ( SBC * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziW * (torch.pi / 180))) - ** 4 - ) + * ((Ta + 273.15 + Tw * torch.sin(aziW * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) * torch.cos(betasun) * 0.5 ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 else: Lwallsun = 0 Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 @@ -188,9 +167,7 @@ def Lside_veg_v2015a( # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky ## Lnorth - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( - svfN, svfNveg, svfNaveg, vikttot - ) + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg(svfN, svfNveg, svfNaveg, vikttot) if altitude > 0: # daytime alfaB = torch.arctan(svfalfaN) @@ -201,18 +178,13 @@ def Lside_veg_v2015a( Lwallsun = ( SBC * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziN * (torch.pi / 180))) - ** 4 - ) + * ((Ta + 273.15 + Tw * torch.sin(aziN * (torch.pi / 180))) ** 4) * viktwall * (1 - F_sh) * torch.cos(betasun) * 0.5 ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 else: Lwallsun = 0 Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 diff --git a/functions/SOLWEIGpython/PET_calculations.py b/functions/SOLWEIGpython/PET_calculations.py index 73dd3c1..424acbc 100644 --- a/functions/SOLWEIGpython/PET_calculations.py +++ b/functions/SOLWEIGpython/PET_calculations.py @@ -128,20 +128,12 @@ def _PET(ta, RH, tmrt, v, mbody, age, ht, work, icl, sex): metbf = ( 3.19 * mbody ** (3 / 4) - * ( - 1 - + 0.004 * (30 - age) - + 0.018 * ((ht * 100 / (mbody ** (1 / 3))) - 42.1) - ) + * (1 + 0.004 * (30 - age) + 0.018 * ((ht * 100 / (mbody ** (1 / 3))) - 42.1)) ) metbm = ( 3.45 * mbody ** (3 / 4) - * ( - 1 - + 0.004 * (30 - age) - + 0.010 * ((ht * 100 / (mbody ** (1 / 3))) - 43.4) - ) + * (1 + 0.004 * (30 - age) + 0.010 * ((ht * 100 / (mbody ** (1 / 3))) - 43.4)) ) if sex == 1: met = metbm + work @@ -215,12 +207,7 @@ def _PET(ta, RH, tmrt, v, mbody, age, ht, work, icl, sex): while (enbal * enbal2) >= 0 and count3 < 200: enbal2 = enbal # 20 - rclo2 = ( - emcl - * sigma - * ((tcl + 273.2) ** 4 - (tmrt + 273.2) ** 4) - * feff - ) + rclo2 = emcl * sigma * ((tcl + 273.2) ** 4 - (tmrt + 273.2) ** 4) * feff tsk = 1 / htcl * (hc * (tcl - ta) + rclo2) + tcl # radiation balance @@ -252,9 +239,7 @@ def _PET(ta, RH, tmrt, v, mbody, age, ht, work, icl, sex): c_8 = -c_1 * c_3 - tsk * c_4 + tsk * c_6 c_9 = c_7 * c_7 - 4.0 * c_5 * c_8 c_10 = 5.28 * adu - c_6 - c_5 * tsk - c_11 = c_10 * c_10 - 4 * c_5 * ( - c_6 * tsk - c_1 - 5.28 * adu * tsk - ) + c_11 = c_10 * c_10 - 4 * c_5 * (c_6 * tsk - c_1 - 5.28 * adu * tsk) # tsk[tsk==36]=36.01 # print(tsk.shape) if tsk == 36: @@ -262,11 +247,7 @@ def _PET(ta, RH, tmrt, v, mbody, age, ht, work, icl, sex): tcore[7] = c_1 / (5.28 * adu + c_2 * 6.3 / 3600) + tsk tcore[3] = ( - c_1 - / ( - 5.28 * adu - + (c_2 * 6.3 / 3600) / (1 + 0.5 * (34 - tsk)) - ) + c_1 / (5.28 * adu + (c_2 * 6.3 / 3600) / (1 + 0.5 * (34 - tsk))) + tsk ) if c_11 >= 0: @@ -403,13 +384,7 @@ def _PET(ta, RH, tmrt, v, mbody, age, ht, work, icl, sex): * sigma * ((tx + 273.2) ** 4 - (tsk + 273.2) ** 4) ) - rclo = ( - feff - * acl - * emcl - * sigma - * ((tx + 273.2) ** 4 - (tcl + 273.2) ** 4) - ) + rclo = feff * acl * emcl * sigma * ((tx + 273.2) ** 4 - (tcl + 273.2) ** 4) rsum = rbare + rclo # convection diff --git a/functions/SOLWEIGpython/Solweig_2021a_calc_forprocessing.py b/functions/SOLWEIGpython/Solweig_2021a_calc_forprocessing.py index 74fbd47..db81223 100644 --- a/functions/SOLWEIGpython/Solweig_2021a_calc_forprocessing.py +++ b/functions/SOLWEIGpython/Solweig_2021a_calc_forprocessing.py @@ -199,9 +199,7 @@ def Solweig_2021a_calc( aniLum + diffsh[:, :, idx] * lv[0][idx][2] ) # Total relative luminance from sky into each cell - dRad = ( - aniLum * radD - ) # Total diffuse radiation from sky into each cell + dRad = aniLum * radD # Total diffuse radiation from sky into each cell else: dRad = radD * svfbuveg lv = 0 @@ -224,15 +222,13 @@ def Solweig_2021a_calc( ) shadow = sh - (1 - vegsh) * (1 - psi) else: - sh, wallsh, wallsun, facesh, facesun = ( - shadowingfunction_wallheight_13( - dsm, - azimuth, - altitude, - scale, - walls, - dirwalls * np.pi / 180.0, - ) + sh, wallsh, wallsun, facesh, facesun = shadowingfunction_wallheight_13( + dsm, + azimuth, + altitude, + scale, + walls, + dirwalls * np.pi / 180.0, ) shadow = sh @@ -278,9 +274,7 @@ def Solweig_2021a_calc( Tg = Tg * CI_Tg # new estimation Tgwall = Tgwall * CI_Tg if landcover == 1: - Tg[Tg < 0] = ( - 0 # temporary for removing low Tg during morning 20130205 - ) + Tg[Tg < 0] = 0 # temporary for removing low Tg during morning 20130205 # # # # Ground View Factors # # # # ( diff --git a/functions/SOLWEIGpython/Solweig_2022a_calc_forprocessing.py b/functions/SOLWEIGpython/Solweig_2022a_calc_forprocessing.py index f3e2353..0f860a4 100644 --- a/functions/SOLWEIGpython/Solweig_2022a_calc_forprocessing.py +++ b/functions/SOLWEIGpython/Solweig_2022a_calc_forprocessing.py @@ -235,9 +235,7 @@ def Solweig_2022a_calc( for idx in range(lv.shape[0]): aniLum += diffsh[:, :, idx] * lv[idx, 2] - dRad = ( - aniLum * radD - ) # Total diffuse radiation from sky into each cell + dRad = aniLum * radD # Total diffuse radiation from sky into each cell else: dRad = radD * svfbuveg patchchoice = 1 @@ -263,15 +261,13 @@ def Solweig_2022a_calc( ) shadow = sh - (1 - vegsh) * (1 - psi) else: - sh, wallsh, wallsun, facesh, facesun = ( - shadowingfunction_wallheight_13( - dsm, - azimuth, - altitude, - scale, - walls, - dirwalls * np.pi / 180.0, - ) + sh, wallsh, wallsun, facesh, facesun = shadowingfunction_wallheight_13( + dsm, + azimuth, + altitude, + scale, + walls, + dirwalls * np.pi / 180.0, ) shadow = sh @@ -282,10 +278,7 @@ def Solweig_2022a_calc( # Tgampwall = (TgK_wall * altmax - (Tstart_wall)) + (Tstart_wall) # Old Tgampwall = TgK_wall * altmax + Tstart_wall Tg = Tgamp * np.sin( - ( - ((dectime - np.floor(dectime)) - SNUP / 24) - / (TmaxLST / 24 - SNUP / 24) - ) + (((dectime - np.floor(dectime)) - SNUP / 24) / (TmaxLST / 24 - SNUP / 24)) * np.pi / 2 ) # 2015 a, based on max sun altitude @@ -322,9 +315,7 @@ def Solweig_2022a_calc( Tg = Tg * CI_TgG # new estimation Tgwall = Tgwall * CI_TgG if landcover == 1: - Tg[Tg < 0] = ( - 0 # temporary for removing low Tg during morning 20130205 - ) + Tg[Tg < 0] = 0 # temporary for removing low Tg during morning 20130205 # # # # Ground View Factors # # # # ( @@ -519,9 +510,7 @@ def Solweig_2022a_calc( if anisotropic_sky == 1: if "lv" not in locals(): # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) - skyvaultalt, skyvaultazi, _, _, _, _, _ = create_patches( - patch_option - ) + skyvaultalt, skyvaultazi, _, _, _, _, _ = create_patches(patch_option) patch_emissivities = np.zeros(skyvaultalt.shape[0]) @@ -563,11 +552,7 @@ def Solweig_2022a_calc( (svf + svfveg - 1) * esky * SBC * ((Ta + 273.15) ** 4) + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) - + (2 - svf - svfveg) - * (1 - ewall) - * esky - * SBC - * ((Ta + 273.15) ** 4) + + (2 - svf - svfveg) * (1 - ewall) * esky * SBC * ((Ta + 273.15) ** 4) ) # Jonsson et al.(2006) # Ldown = Ldown - 25 # Shown by Jonsson et al.(2006) and Duarte et al.(2006) @@ -629,9 +614,7 @@ def Solweig_2022a_calc( KsideI * Fcyl + (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside - ) + absL * ( - (Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside - ) + ) + absL * ((Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside) # Human body considered as a cylinder with Perez et al. (1993) (anisotropic sky diffuse) # and Martin and Berdahl (1984) (anisotropic sky longwave) elif cyl == 1 and anisotropic_sky == 1: @@ -648,9 +631,7 @@ def Solweig_2022a_calc( else: # Human body considered as a standing cube Sstr = absK * ( (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside - ) + absL * ( - (Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside - ) + ) + absL * ((Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside) Tmrt = np.sqrt(np.sqrt((Sstr / (absL * SBC)))) - 273.2 diff --git a/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py b/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py index de979e0..b61a069 100644 --- a/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py +++ b/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py @@ -259,9 +259,7 @@ def Solweig_2025a_calc( for idx in range(lv.shape[0]): aniLum += diffsh[:, :, idx] * lv[idx, 2] - dRad = ( - aniLum * radD - ) # Total diffuse radiation from sky into each cell + dRad = aniLum * radD # Total diffuse radiation from sky into each cell else: dRad = radD * svfbuveg patchchoice = 1 @@ -308,10 +306,7 @@ def Solweig_2025a_calc( # Tgampwall = (TgK_wall * altmax - (Tstart_wall)) + (Tstart_wall) # Old Tgampwall = TgK_wall * altmax + Tstart_wall Tg = Tgamp * np.sin( - ( - ((dectime - np.floor(dectime)) - SNUP / 24) - / (TmaxLST / 24 - SNUP / 24) - ) + (((dectime - np.floor(dectime)) - SNUP / 24) / (TmaxLST / 24 - SNUP / 24)) * np.pi / 2 ) # 2015 a, based on max sun altitude @@ -347,9 +342,7 @@ def Solweig_2025a_calc( Tg = Tg * CI_TgG # new estimation Tgwall = Tgwall * CI_TgG if landcover == 1: - Tg[Tg < 0] = ( - 0 # temporary for removing low Tg during morning 20130205 - ) + Tg[Tg < 0] = 0 # temporary for removing low Tg during morning 20130205 # # # # Ground View Factors # # # # ( @@ -603,9 +596,7 @@ def Solweig_2025a_calc( if anisotropic_sky == 1: if "lv" not in locals(): # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) - skyvaultalt, skyvaultazi, _, _, _, _, _ = create_patches( - patch_option - ) + skyvaultalt, skyvaultazi, _, _, _, _, _ = create_patches(patch_option) patch_emissivities = np.zeros(skyvaultalt.shape[0]) @@ -707,9 +698,7 @@ def Solweig_2025a_calc( KsideI * Fcyl + (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside - ) + absL * ( - (Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside - ) + ) + absL * ((Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside) # Human body considered as a cylinder with Perez et al. (1993) (anisotropic sky diffuse) # and Martin and Berdahl (1984) (anisotropic sky longwave) elif cyl == 1 and anisotropic_sky == 1: @@ -726,9 +715,7 @@ def Solweig_2025a_calc( else: # Human body considered as a standing cube Sstr = absK * ( (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside - ) + absL * ( - (Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside - ) + ) + absL * ((Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside) Tmrt = np.sqrt(np.sqrt((Sstr / (absL * SBC)))) - 273.2 diff --git a/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py index 4844b7d..292b80d 100644 --- a/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py +++ b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py @@ -278,9 +278,7 @@ def Solweig_2026a_calc( for idx in range(lv.shape[0]): aniLum += diffsh[:, :, idx] * lv[idx, 2] - dRad = ( - aniLum * radD - ) # Total diffuse radiation from sky into each cell + dRad = aniLum * radD # Total diffuse radiation from sky into each cell else: dRad = radD * svfbuveg patchchoice = 1 @@ -361,11 +359,7 @@ def Solweig_2026a_calc( (svf + svfveg - 1) * esky * SBC * ((Ta + 273.15) ** 4) + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) - + (2 - svf - svfveg) - * (1 - ewall) - * esky - * SBC - * ((Ta + 273.15) ** 4) + + (2 - svf - svfveg) * (1 - ewall) * esky * SBC * ((Ta + 273.15) ** 4) ) # Jonsson et al.(2006) # Ldown = Ldown - 25 # Shown by Jonsson et al.(2006) and Duarte et al.(2006) if CI < 0.95: # non - clear conditions @@ -460,9 +454,7 @@ def Solweig_2026a_calc( ) # timeadd only here v2021a if landcover == 1: - Tg[Tg < 0] = ( - 0 # temporary for removing low Tg during morning 20130205 - ) + Tg[Tg < 0] = 0 # temporary for removing low Tg during morning 20130205 ### Ground View Factors ( @@ -608,11 +600,7 @@ def Solweig_2026a_calc( (svf + svfveg - 1) * esky * SBC * ((Ta + 273.15) ** 4) + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) - + (2 - svf - svfveg) - * (1 - ewall) - * esky - * SBC - * ((Ta + 273.15) ** 4) + + (2 - svf - svfveg) * (1 - ewall) * esky * SBC * ((Ta + 273.15) ** 4) ) # Jonsson et al.(2006) # Ldown = Ldown - 25 # Shown by Jonsson et al.(2006) and Duarte et al.(2006) @@ -784,9 +772,7 @@ def Solweig_2026a_calc( patch_option, device ) - patch_emissivities = torch.zeros( - skyvaultalt.shape[0], device=device - ) + patch_emissivities = torch.zeros(skyvaultalt.shape[0], device=device) x = torch.transpose(torch.atleast_2d(skyvaultalt)) y = torch.transpose(torch.atleast_2d(skyvaultazi)) @@ -887,9 +873,7 @@ def Solweig_2026a_calc( KsideI * Fcyl + (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside - ) + absL * ( - (Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside - ) + ) + absL * ((Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside) # Human body considered as a cylinder with Perez et al. (1993) (anisotropic sky diffuse) # and Martin and Berdahl (1984) (anisotropic sky longwave) elif cyl == 1 and anisotropic_sky == 1: @@ -906,9 +890,7 @@ def Solweig_2026a_calc( else: Sstr = absK * ( (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside - ) + absL * ( - (Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside - ) + ) + absL * ((Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside) # # # # Tmrt # # # # Tmrt = torch.sqrt(torch.sqrt((Sstr / (absL * SBC)))) - 273.2 diff --git a/functions/SOLWEIGpython/Solweig_run.py b/functions/SOLWEIGpython/Solweig_run.py index 7e9ebb5..4429e3a 100644 --- a/functions/SOLWEIGpython/Solweig_run.py +++ b/functions/SOLWEIGpython/Solweig_run.py @@ -158,24 +158,14 @@ def solweig_run(configPath, feedback): if usevegdem == 1: if standAlone == 0: vegdsm = torch.from_numpy( - ( - gdal.Open(configDict["filepath_cdsm"]) - .ReadAsArray() - .astype(float) - ) + (gdal.Open(configDict["filepath_cdsm"]).ReadAsArray().astype(float)) ).to(device) else: - vegdsm, _, _ = common.load_raster( - configDict["filepath_cdsm"], bbox=None - ) + vegdsm, _, _ = common.load_raster(configDict["filepath_cdsm"], bbox=None) if configDict["filepath_tdsm"] != "": if standAlone == 0: vegdsm2 = torch.from_numpy( - ( - gdal.Open(configDict["filepath_tdsm"]) - .ReadAsArray() - .astype(float) - ) + (gdal.Open(configDict["filepath_tdsm"]).ReadAsArray().astype(float)) ).to(device) else: vegdsm2, _, _ = common.load_raster( @@ -192,16 +182,10 @@ def solweig_run(configPath, feedback): if landcover == 1: if standAlone == 0: lcgrid = torch.from_numpy( - ( - gdal.Open(configDict["filepath_lc"]) - .ReadAsArray() - .astype(float) - ) + (gdal.Open(configDict["filepath_lc"]).ReadAsArray().astype(float)) ).to(device) else: - lcgrid, _, _ = common.load_raster( - configDict["filepath_lc"], bbox=None - ) + lcgrid, _, _ = common.load_raster(configDict["filepath_lc"], bbox=None) else: lcgrid = 0 @@ -212,14 +196,10 @@ def solweig_run(configPath, feedback): gdal_dem = gdal.Open( configDict["filepath_dem"] ) # .ReadAsArray().astype(float) - dem = torch.from_numpy(gdal_dem.ReadAsArray().astype(float)).to( - device - ) + dem = torch.from_numpy(gdal_dem.ReadAsArray().astype(float)).to(device) nd = gdal_dem.GetRasterBand(1).GetNoDataValue() else: - dem, _, _ = common.load_raster( - configDict["filepath_dem"], bbox=None - ) + dem, _, _ = common.load_raster(configDict["filepath_dem"], bbox=None) nd = -9999 # TODO: standAlone nd exposure # response to issue and #230 @@ -418,12 +398,8 @@ def solweig_run(configPath, feedback): (gdal.Open(configDict["filepath_wa"]).ReadAsArray().astype(float)) ).to(device) else: - wallheight, _, _ = common.load_raster( - configDict["filepath_wh"], bbox=None - ) - wallaspect, _, _ = common.load_raster( - configDict["filepath_wa"], bbox=None - ) + wallheight, _, _ = common.load_raster(configDict["filepath_wh"], bbox=None) + wallaspect, _, _ = common.load_raster(configDict["filepath_wa"], bbox=None) # Metdata headernum = 1 @@ -431,9 +407,7 @@ def solweig_run(configPath, feedback): Twater = [] metdata = torch.from_numpy( - np.loadtxt( - configDict["input_met"], skiprows=headernum, delimiter=delim - ) + np.loadtxt(configDict["input_met"], skiprows=headernum, delimiter=delim) ).to(device) location = {"longitude": lon, "latitude": lat, "altitude": alt} @@ -505,9 +479,7 @@ def solweig_run(configPath, feedback): for k in range(0, poisxy.shape[0]): poi_save = [] # torch.zeros((1, 33)) - data_out = ( - configDict["output_dir"] + "/POI_" + str(poiname[k]) + ".txt" - ) + data_out = configDict["output_dir"] + "/POI_" + str(poiname[k]) + ".txt" np.savetxt( data_out, ( @@ -538,17 +510,15 @@ def solweig_run(configPath, feedback): if param["Tmrt_params"]["Value"]["posture"] == "Standing": Fside = param["Posture"]["Standing"]["Value"]["Fside"] Fup = param["Posture"]["Standing"]["Value"]["Fup"] - height = torch.tensor( - param["Posture"]["Standing"]["Value"]["height"] - ).to(device) + height = torch.tensor(param["Posture"]["Standing"]["Value"]["height"]).to( + device + ) Fcyl = param["Posture"]["Standing"]["Value"]["Fcyl"] pos = 1 else: Fside = param["Posture"]["Sitting"]["Value"]["Fside"] Fup = param["Posture"]["Sitting"]["Value"]["Fup"] - height = torch.tensor( - param["Posture"]["Sitting"]["Value"]["height"] - ).to(device) + height = torch.tensor(param["Posture"]["Sitting"]["Value"]["height"]).to(device) Fcyl = param["Posture"]["Sitting"]["Value"]["Fcyl"] pos = 0 @@ -594,9 +564,7 @@ def solweig_run(configPath, feedback): # % Bush separation bush = torch.logical_not((vegdsm2 * vegdsm)) * vegdsm - svfbuveg = svf - (1.0 - svfveg) * ( - 1.0 - transVeg - ) # % major bug fixed 20141203 + svfbuveg = svf - (1.0 - svfveg) * (1.0 - transVeg) # % major bug fixed 20141203 else: psi = leafon * 0.0 + 1.0 svfbuveg = svf @@ -699,10 +667,7 @@ def solweig_run(configPath, feedback): TgK = Knight + param["Ts_deg"]["Value"]["Cobble_stone_2014a"] Tstart = Knight - param["Tstart"]["Value"]["Cobble_stone_2014a"] TmaxLST = param["TmaxLST"]["Value"]["Cobble_stone_2014a"] - alb_grid = ( - Knight - + param["Albedo"]["Effective"]["Value"]["Cobble_stone_2014a"] - ) + alb_grid = Knight + param["Albedo"]["Effective"]["Value"]["Cobble_stone_2014a"] emis_grid = Knight + param["Emissivity"]["Value"]["Cobble_stone_2014a"] TgK_wall = param["Ts_deg"]["Value"]["Walls"] Tstart_wall = param["Tstart"]["Value"]["Walls"] @@ -1159,9 +1124,7 @@ def solweig_run(configPath, feedback): poi_save[0, 28] = CI poi_save[0, 29] = shadow[int(poisxy[k, 2]), int(poisxy[k, 1])] poi_save[0, 30] = svf[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 31] = svfbuveg[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] + poi_save[0, 31] = svfbuveg[int(poisxy[k, 2]), int(poisxy[k, 1])] poi_save[0, 32] = KsideI[int(poisxy[k, 2]), int(poisxy[k, 1])] # Recalculating wind speed based on powerlaw WsPET = (1.1 / sensorheight) ** 0.2 * Ws[i] @@ -1191,12 +1154,7 @@ def solweig_run(configPath, feedback): poi_save[0, 37] = Lside[int(poisxy[k, 2]), int(poisxy[k, 1])] poi_save[0, 38] = dRad[int(poisxy[k, 2]), int(poisxy[k, 1])] poi_save[0, 39] = Kside[int(poisxy[k, 2]), int(poisxy[k, 1])] - data_out = ( - configDict["output_dir"] - + "/POI_" - + str(poiname[k]) - + ".txt" - ) + data_out = configDict["output_dir"] + "/POI_" + str(poiname[k]) + ".txt" # f_handle = file(data_out, 'a') f_handle = open(data_out, "ab") np.savetxt( @@ -1211,9 +1169,7 @@ def solweig_run(configPath, feedback): f_handle.close() # If wall temperature parameterization scheme is in use - if ( - configDict["wallscheme"] == 1 - ): # folderWallScheme: TODO: Fix for standalone + if configDict["wallscheme"] == 1: # folderWallScheme: TODO: Fix for standalone # Store wall data for output if not woisxy is None: for k in range(0, woisxy.shape[0]): @@ -1245,20 +1201,15 @@ def solweig_run(configPath, feedback): ), "wallShade", ].to_numpy() - temp_all = torch.concatenate( - [temp_wall, K_in, L_in, wallShade] - ).to(device) - # temp_all = torch.concatenate([temp_wall]) - # wall_data = torch.zeros((1, 7 + temp_wall.shape[0])) - wall_data = torch.zeros((1, 7 + temp_all.shape[0])).to( + temp_all = torch.concatenate([temp_wall, K_in, L_in, wallShade]).to( device ) + # temp_all = torch.concatenate([temp_wall]) + # wall_data = torch.zeros((1, 7 + temp_wall.shape[0])) + wall_data = torch.zeros((1, 7 + temp_all.shape[0])).to(device) # Part of file name (wallid), i.e. WOI_wallid.txt data_out = ( - configDict["output_dir"] - + "/WOI_" - + str(woiname[k]) - + ".txt" + configDict["output_dir"] + "/WOI_" + str(woiname[k]) + ".txt" ) if i == 0: # Output file header @@ -1292,8 +1243,7 @@ def solweig_run(configPath, feedback): # Num format for output file data woi_numformat = ( - "%d %d %d %d %.5f %.2f %.2f" - + " %.2f" * temp_all.shape[0] + "%d %d %d %d %.5f %.2f %.2f" + " %.2f" * temp_all.shape[0] ) # Open file, add data, save f_handle = open(data_out, "ab") @@ -1448,10 +1398,7 @@ def solweig_run(configPath, feedback): for k in range(poisxy.shape[0]): Lsky_patch_characteristics[:, 2] = patch_characteristics[:, k] skyviewimage_out = ( - configDict["output_dir"] - + "/POI_" - + str(poiname[k]) - + ".png" + configDict["output_dir"] + "/POI_" + str(poiname[k]) + ".png" ) PolarBarPlot( Lsky_patch_characteristics, @@ -1469,9 +1416,7 @@ def solweig_run(configPath, feedback): if feedback is not None: feedback.setProgressText("Saving files for Tree Planter tool") # Save DSM - copyfile( - configDict["filepath_dsm"], configDict["output_dir"] + "/DSM.tif" - ) + copyfile(configDict["filepath_dsm"], configDict["output_dir"] + "/DSM.tif") # Save CDSM if usevegdem == 1: @@ -1535,13 +1480,9 @@ def solweig_run(configPath, feedback): ) # Copying met file for SpatialTC - copyfile( - configDict["input_met"], configDict["output_dir"] + "/metforcing.txt" - ) + copyfile(configDict["input_met"], configDict["output_dir"] + "/metforcing.txt") - tmrtplot = ( - tmrtplot / Ta.__len__() - ) # fix average Tmrt instead of sum, 20191022 + tmrtplot = tmrtplot / Ta.__len__() # fix average Tmrt instead of sum, 20191022 if standAlone == 0: saveraster( gdal_dsm, diff --git a/functions/SOLWEIGpython/Tgmaps_v1.py b/functions/SOLWEIGpython/Tgmaps_v1.py index d7e29ac..3dc2969 100644 --- a/functions/SOLWEIGpython/Tgmaps_v1.py +++ b/functions/SOLWEIGpython/Tgmaps_v1.py @@ -20,9 +20,9 @@ def Tgmaps_v1(lc_grid, solweig_parameters): Tstart[Tstart == i] = solweig_parameters["Tstart"]["Value"][ solweig_parameters["Names"]["Value"][str((int(i.item())))] ] - alb_grid[alb_grid == i] = solweig_parameters["Albedo"]["Effective"][ - "Value" - ][solweig_parameters["Names"]["Value"][str((int(i.item())))]] + alb_grid[alb_grid == i] = solweig_parameters["Albedo"]["Effective"]["Value"][ + solweig_parameters["Names"]["Value"][str((int(i.item())))] + ] emis_grid[emis_grid == i] = solweig_parameters["Emissivity"]["Value"][ solweig_parameters["Names"]["Value"][str((int(i.item())))] ] diff --git a/functions/SOLWEIGpython/UTCI_calculations.py b/functions/SOLWEIGpython/UTCI_calculations.py index a773a19..f7a9754 100644 --- a/functions/SOLWEIGpython/UTCI_calculations.py +++ b/functions/SOLWEIGpython/UTCI_calculations.py @@ -89,13 +89,7 @@ def utci_polynomial(D_Tmrt, Ta, va, Pa): + (6.62154879e-10) * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt + (4.03863260e-13) * Ta * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt + (1.95087203e-12) * va * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt - + (-4.73602469e-12) - * D_Tmrt - * D_Tmrt - * D_Tmrt - * D_Tmrt - * D_Tmrt - * D_Tmrt + + (-4.73602469e-12) * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt * D_Tmrt + (5.12733497e00) * Pa + (-3.12788561e-01) * Ta * Pa + (-1.96701861e-02) * Ta * Ta * Pa @@ -332,9 +326,7 @@ def utci_calculator_grid(Ta, RH, Tmrt, va10m, feedback): UTCI_approx[iy, ix] = -9999 # Calculate 6th order polynomial as approximation if wind speed is above zero elif va10m[iy, ix] > 0: - UTCI_approx[iy, ix] = utci_polynomial( - D_Tmrt, Ta, va10m[iy, ix], Pa - ) + UTCI_approx[iy, ix] = utci_polynomial(D_Tmrt, Ta, va10m[iy, ix], Pa) # Progress counter index = index + 1 diff --git a/functions/SOLWEIGpython/WriteMetadataSOLWEIG.py b/functions/SOLWEIGpython/WriteMetadataSOLWEIG.py index ee033b7..446277e 100644 --- a/functions/SOLWEIGpython/WriteMetadataSOLWEIG.py +++ b/functions/SOLWEIGpython/WriteMetadataSOLWEIG.py @@ -87,21 +87,15 @@ def writeRunInfo( file.write("\n") file.write("Digital surface model (DSM): " + filepath_dsm) file.write("\n") - file.write( - "Model domain: rows = " + str(rows) + ", columns = " + str(cols) - ) + file.write("Model domain: rows = " + str(rows) + ", columns = " + str(cols)) file.write("\n") # get CRS prj = gdal_dsm.GetProjection() srs = osr.SpatialReference(wkt=prj) if srs.IsProjected: - file.write( - "Projected reference system: " + srs.GetAttrValue("projcs") - ) + file.write("Projected reference system: " + srs.GetAttrValue("projcs")) file.write("\n") - file.write( - "Geographical coordinate system: " + srs.GetAttrValue("geogcs") - ) + file.write("Geographical coordinate system: " + srs.GetAttrValue("geogcs")) file.write("\n") file.write("Latitude: " + str(lat)) file.write("\n") @@ -110,26 +104,19 @@ def writeRunInfo( file.write("UTC: " + str(UTC)) file.write("\n") if usevegdem == 1: - file.write( - "Transmissivity of light through vegetation: " + str(trans) - ) + file.write("Transmissivity of light through vegetation: " + str(trans)) file.write("\n") - file.write( - "Digital vegetation canopy model (CDSM): " + filePath_cdsm - ) + file.write("Digital vegetation canopy model (CDSM): " + filePath_cdsm) file.write("\n") if trunkfile == 1: file.write( - "Digital vegetation trunk zone model (TDSM): " - + filePath_tdsm + "Digital vegetation trunk zone model (TDSM): " + filePath_tdsm ) # FO# zrunk -> trunk file.write("\n") else: file.write("Trunkzone estimated from CDSM") file.write("\n") - file.write( - "Trunkzone as percent of canopy height: " + str(trunkratio) - ) + file.write("Trunkzone as percent of canopy height: " + str(trunkratio)) file.write("\n") else: file.write("Vegetation scheme inactive") @@ -175,9 +162,7 @@ def writeRunInfo( file.write("\n") file.write("Minute: " + str(metdata[0, 3])) file.write("\n") - file.write( - "Air temperature: " + str(metdata[0, 11]) - ) # FO# Ait -> Air + file.write("Air temperature: " + str(metdata[0, 11])) # FO# Ait -> Air file.write("\n") file.write("Relative humidity: " + str(metdata[0, 10])) file.write("\n") @@ -219,14 +204,10 @@ def writeRunInfo( file.write("ADDITIONAL SETTINGS") file.write("\n") if elvis == 1: - file.write( - "Sky emissivity adjusted according to Jonsson et al. (2005)" - ) + file.write("Sky emissivity adjusted according to Jonsson et al. (2005)") file.write("\n") if cyl == 1: - file.write( - "Human considered as a standing cylinder" - ) # FO# '' -> standing + file.write("Human considered as a standing cylinder") # FO# '' -> standing else: file.write("Human considered as a standing cube") file.write("\n") @@ -247,11 +228,7 @@ def writeRunInfo( else: for i in range(effusivity.size): if i == 0: - file.write( - "There are " - + str(effusivity.size) - + " wall types:" - ) + file.write("There are " + str(effusivity.size) + " wall types:") file.write("\n") file.write( "Wall surface parameterization scheme. Wall effusivity set to " diff --git a/functions/SOLWEIGpython/anisotropic_sky.py b/functions/SOLWEIGpython/anisotropic_sky.py index bd8971b..dfca9e2 100644 --- a/functions/SOLWEIGpython/anisotropic_sky.py +++ b/functions/SOLWEIGpython/anisotropic_sky.py @@ -156,9 +156,7 @@ def anisotropic_sky( # Radiance fraction normalization for i in np.arange(patch_altitude.shape[0]): radTot += ( - patch_luminance[i] - * steradians[i] - * np.sin(patch_altitude[i] * deg2rad) + patch_luminance[i] * steradians[i] * np.sin(patch_altitude[i] * deg2rad) ) # Radiance fraction normalization lumChi = (patch_luminance * radD) / radTot @@ -179,14 +177,12 @@ def anisotropic_sky( temp_sh_roof = temp_sh * (voxelMaps[:, :, i] == 0) # Estimate sunlit and shaded patches - sunlit_patches, shaded_patches = ( - sunlit_shaded_patches.shaded_or_sunlit( - solar_altitude, - solar_azimuth, - patch_altitude[i], - patch_azimuth[i], - asvf, - ) + sunlit_patches, shaded_patches = sunlit_shaded_patches.shaded_or_sunlit( + solar_altitude, + solar_azimuth, + patch_altitude[i], + patch_azimuth[i], + asvf, ) if cyl == 1: @@ -338,25 +334,19 @@ def anisotropic_sky( ### CALCULATIONS FOR SHORTWAVE RADIATION ### if solar_altitude > 0: # Shortwave radiation from sky - KsideD += ( - temp_sky * lumChi[i] * angle_of_incidence * steradians[i] - ) + KsideD += temp_sky * lumChi[i] * angle_of_incidence * steradians[i] # Shortwave reflected on sunlit surfaces # sunlit_surface = ((albedo * radG) / np.pi) sunlit_surface = ( - albedo * (radI * np.cos(solar_altitude * deg2rad)) - + (radD * 0.5) + albedo * (radI * np.cos(solar_altitude * deg2rad)) + (radD * 0.5) ) / np.pi # Shortwave reflected on shaded surfaces and vegetation shaded_surface = (albedo * radD * 0.5) / np.pi # Shortwave radiation from vegetation Kref_veg += ( - shaded_surface - * temp_vegsh - * steradians[i] - * angle_of_incidence + shaded_surface * temp_vegsh * steradians[i] * angle_of_incidence ) # Shortwave radiation from buildings diff --git a/functions/SOLWEIGpython/cylindric_wedge.py b/functions/SOLWEIGpython/cylindric_wedge.py index c876d88..8b3675b 100644 --- a/functions/SOLWEIGpython/cylindric_wedge.py +++ b/functions/SOLWEIGpython/cylindric_wedge.py @@ -39,9 +39,9 @@ def cylindric_wedge(zen, svfalfa, rows, cols): A = torch.zeros((rows, cols), device=device) # A(length(svfalfa),length(svfalfa))=0; - A[xa < 0] = ( - torch.sin(phi[xa < 0]) - phi[xa < 0] * torch.cos(phi[xa < 0]) - ) / (1 - torch.cos(phi[xa < 0])) + A[xa < 0] = (torch.sin(phi[xa < 0]) - phi[xa < 0] * torch.cos(phi[xa < 0])) / ( + 1 - torch.cos(phi[xa < 0]) + ) ukil = torch.zeros((rows, cols), device=device) # ukil(length(svfalfa),length(svfalfa))=0 @@ -95,9 +95,9 @@ def cylindric_wedge_voxel(zen, svfalfa): A = torch.zeros((svfalfa.shape[0]), device=device) # A(length(svfalfa),length(svfalfa))=0; - A[xa < 0] = ( - torch.sin(phi[xa < 0]) - phi[xa < 0] * torch.cos(phi[xa < 0]) - ) / (1 - torch.cos(phi[xa < 0])) + A[xa < 0] = (torch.sin(phi[xa < 0]) - phi[xa < 0] * torch.cos(phi[xa < 0])) / ( + 1 - torch.cos(phi[xa < 0]) + ) ukil = torch.zeros((svfalfa.shape[0]), device=device) # ukil(length(svfalfa),length(svfalfa))=0 diff --git a/functions/SOLWEIGpython/emissivity_models.py b/functions/SOLWEIGpython/emissivity_models.py index 1762e50..6a3acf0 100644 --- a/functions/SOLWEIGpython/emissivity_models.py +++ b/functions/SOLWEIGpython/emissivity_models.py @@ -78,9 +78,7 @@ def model2(sky_patches, esky, Ta): # b_c = 0.1 # Estimate emissivites at different altitudes/zenith angles - esky_band = 1 - (1 - esky) * np.exp( - b_c * (1.7 - (1 / np.cos(skyzen * deg2rad))) - ) + esky_band = 1 - (1 - esky) * np.exp(b_c * (1.7 - (1 / np.cos(skyzen * deg2rad)))) # Altitudes of the Robinson & Stone patches p_alt = sky_patches[:, 0] diff --git a/functions/SOLWEIGpython/ground_surface.py b/functions/SOLWEIGpython/ground_surface.py index 903259f..a6050dd 100644 --- a/functions/SOLWEIGpython/ground_surface.py +++ b/functions/SOLWEIGpython/ground_surface.py @@ -35,9 +35,7 @@ def saturated_vp(T): return slope, qs -def initiate_groundScheme( - lc_grid, solweig_parameters, day, Ta, location, device -): +def initiate_groundScheme(lc_grid, solweig_parameters, day, Ta, location, device): """ Setup the maps used in the ground scheme calculations depending on the landcover @@ -74,9 +72,9 @@ def initiate_groundScheme( cap_grid[cap_grid == i] = solweig_parameters["Heat capacity"]["Value"][ solweig_parameters["Names"]["Value"][str((int(i.item())))] ] - diff_grid[diff_grid == i] = solweig_parameters["Thermal_diffusivity"][ - "Value" - ][solweig_parameters["Names"]["Value"][str((int(i.item())))]] + diff_grid[diff_grid == i] = solweig_parameters["Thermal_diffusivity"]["Value"][ + solweig_parameters["Names"]["Value"][str((int(i.item())))] + ] # Coefficients of the OHM per land cover mean_a1 = solweig_parameters["OHM_coefficients"]["Values"][ @@ -91,12 +89,12 @@ def initiate_groundScheme( * torch.sin(2 * torch.pi / 365.25 * day + phi_a1) * torch.sign(torch.tensor(location["latitude"], device=device)) ) - a2_grid[a2_grid == i] = solweig_parameters["OHM_coefficients"][ - "Values" - ][solweig_parameters["Names"]["Value"][str((int(i.item())))]][2] - a3_grid[a3_grid == i] = solweig_parameters["OHM_coefficients"][ - "Values" - ][solweig_parameters["Names"]["Value"][str((int(i.item())))]][3] + a2_grid[a2_grid == i] = solweig_parameters["OHM_coefficients"]["Values"][ + solweig_parameters["Names"]["Value"][str((int(i.item())))] + ][2] + a3_grid[a3_grid == i] = solweig_parameters["OHM_coefficients"]["Values"][ + solweig_parameters["Names"]["Value"][str((int(i.item())))] + ][3] # Initial ground surface temperature parameters offset_Tg = solweig_parameters["Tg_ini coefficients"]["Values"][ @@ -130,9 +128,7 @@ def initiate_groundScheme( + 1 / ratio_Tg * torch.sin(2 * torch.pi / 365.25 * day + phi_Tg) - * torch.sign( - torch.tensor(location["latitude"], device=device) - ) + * torch.sign(torch.tensor(location["latitude"], device=device)) ) + 4 ) @@ -155,9 +151,7 @@ def initiate_groundScheme( + 1 / ratio_Tg * torch.sin(2 * torch.pi / 365.25 * day + phi_Tg) - * torch.sign( - torch.tensor(location["latitude"], device=device) - ) + * torch.sign(torch.tensor(location["latitude"], device=device)) ) + 4 ) @@ -190,9 +184,7 @@ def initiate_groundScheme( + 1 / ratio_Tg * torch.sin(2 * torch.pi / 365.25 * day + phi_Tg) - * torch.sign( - torch.tensor(location["latitude"], device=device) - ) + * torch.sign(torch.tensor(location["latitude"], device=device)) ) + 2 ) @@ -291,9 +283,7 @@ def surfaceTemperature_calc( ) # Temporary outgoing longwave rad (W.m-2) Rn_temp = Kdown * (1 - alb) + Ldown - Lup_temp # Temporary net rad (W.m-2) RnStar_temp = (Rn_temp - Rn) / 1 # Temporary radiation rate (W.m-2.h-1) - G_temp = ( - a1 * Rn_temp + a2 * RnStar_temp + a3 - ) # Temporary ground heat flux (W.m-2) + G_temp = a1 * Rn_temp + a2 * RnStar_temp + a3 # Temporary ground heat flux (W.m-2) # Damping of the ground heat flux if it increases (or drops) too quickly deltaG = abs(G_temp - G) @@ -319,9 +309,7 @@ def surfaceTemperature_calc( # Damping of the ground heat flux if it increases (or decreases) too quickly deltaG = abs(G - G_past) - radCriterion = abs( - a1 * (Rn - Rn_past) - ) # Criterion regarding the radiation step + radCriterion = abs(a1 * (Rn - Rn_past)) # Criterion regarding the radiation step mask = torch.logical_and( deltaG > radCriterion, abs(shadow - shadow_past) > 0.5 ) # Grid of the pixels where the ground heat flux spikes @@ -335,10 +323,7 @@ def surfaceTemperature_calc( Rn_water = ( Kdown * (1 - alb) - * ( - beta - + (1 - beta) * (1 - torch.exp(torch.tensor(-1, device=Tg.device))) - ) + * (beta + (1 - beta) * (1 - torch.exp(torch.tensor(-1, device=Tg.device)))) + Ldown - Lup ) # Net radiation for the top water layer beta described the transmitted rad @@ -346,10 +331,7 @@ def surfaceTemperature_calc( E = 0.0858 * (es / 1000) * (1 - RH / 100) / 3600 / 1000 * rho * lamb deltaTg = torch.clone(lc_grid) deltaTg = ( - timestep - / cap - / thickness - * (Rn_water - E - diff * cap / thickness * (Tg - Tm)) + timestep / cap / thickness * (Rn_water - E - diff * cap / thickness * (Tg - Tm)) ) Tg[lc_grid == 7] = Tg_stored[lc_grid == 7] + deltaTg[lc_grid == 7] @@ -395,9 +377,7 @@ def outgoingLongwave_calc( # Assessment of the distance from a pixel at which most of the radiation are received (cf view factor Lambert) device = Tg.device - factor = torch.tensor( - 0.99, device=device - ) # Percentage of radiation accounted for + factor = torch.tensor(0.99, device=device) # Percentage of radiation accounted for zs = 1.1 # in m r_max = zs * torch.sqrt( factor / (1 - factor) @@ -451,9 +431,7 @@ def outgoingLongwave_calc( # Add the radiation from the pixel directly below, only for the total gvf # Do not take the roofs into account for now view_factor = (sizepx / 2) ** 2 / ((sizepx / 2) ** 2 + zs**2) - gvfLup = ( - gvfLup + (SBC * emis * (Tg + 273.15) ** 4) * view_factor * buildings - ) + gvfLup = gvfLup + (SBC * emis * (Tg + 273.15) ** 4) * view_factor * buildings gvfalbsun = gvfalbsun + albsunlit * view_factor * buildings gvfalbtot = gvfalbtot + alb * view_factor * buildings @@ -488,12 +466,8 @@ def outgoingLongwave_calc( # Translation ranges from 1/2 a pixel to the max radius r_max for r in torch.arange(sizepx / 2, r_max, step=step): # Longwave radiation grids both at the ground level and from the walls - Lup = ( - SBC * emis * (Tg + 273.15) ** 4 + Ldown * (1 - emis) - ) * building_copy - Lwall = ( - SBC * emis_wall * (Tgwall + Ta + 273.15) ** 4 * building_copy - ) + Lup = (SBC * emis * (Tg + 273.15) ** 4 + Ldown * (1 - emis)) * building_copy + Lwall = SBC * emis_wall * (Tgwall + Ta + 273.15) ** 4 * building_copy # Step of the raster translation dx = -torch.cos(azimuth) @@ -502,17 +476,9 @@ def outgoingLongwave_calc( # Scale so that the grid is at least translated from 1px if abs(dx) > abs(dy): dx = -r * torch.sign(torch.cos(azimuth)) - dy = ( - -r - * abs(torch.tan(azimuth)) - * torch.sign(torch.sin(azimuth)) - ) + dy = -r * abs(torch.tan(azimuth)) * torch.sign(torch.sin(azimuth)) else: - dx = ( - -r - / abs(torch.tan(azimuth)) - * torch.sign(torch.cos(azimuth)) - ) + dx = -r / abs(torch.tan(azimuth)) * torch.sign(torch.cos(azimuth)) dy = -r * torch.sign(torch.sin(azimuth)) # Select the interested part of the initial raster and the translated one from their four corners and @@ -611,27 +577,17 @@ def outgoingLongwave_calc( onlysunwall_temp = sunlitwall_temp * building_copy # Then add the radiation incoming from those walls - Lup_sum += ( - wall_temp * Lwall_temp * zs**2 / ((r + step) ** 2 + zs**2) / 20 - ) + Lup_sum += wall_temp * Lwall_temp * zs**2 / ((r + step) ** 2 + zs**2) / 20 albsun_sum += ( - onlysunwall_temp - * alb_wall - * zs**2 - / ((r + step) ** 2 + zs**2) - / 20 - ) - albtot_sum += ( - wall_temp * alb_wall * zs**2 / ((r + step) ** 2 + zs**2) / 20 + onlysunwall_temp * alb_wall * zs**2 / ((r + step) ** 2 + zs**2) / 20 ) + albtot_sum += wall_temp * alb_wall * zs**2 / ((r + step) ** 2 + zs**2) / 20 # Finally add the radiation received from the side dphi = torch.arctan((r + step) / zs) - torch.arctan(r / zs) dtrigo = zs / torch.sqrt(r**2 + zs**2) * r / torch.sqrt( r**2 + zs**2 - ) - zs / torch.sqrt((r + step) ** 2 + zs**2) * ( - r + step - ) / torch.sqrt( + ) - zs / torch.sqrt((r + step) ** 2 + zs**2) * (r + step) / torch.sqrt( (r + step) ** 2 + zs**2 ) diff --git a/functions/SOLWEIGpython/gvf_2015a.py b/functions/SOLWEIGpython/gvf_2015a.py index b73f426..1d9dcba 100644 --- a/functions/SOLWEIGpython/gvf_2015a.py +++ b/functions/SOLWEIGpython/gvf_2015a.py @@ -27,9 +27,7 @@ def gvf_2015a( landcover, ): - azimuthA = np.arange( - 5, 359, 20 - ) # Search directions for Ground View Factors (GVF) + azimuthA = np.arange(5, 359, 20) # Search directions for Ground View Factors (GVF) #### Ground View Factors #### gvfLup = np.zeros((rows, cols)) @@ -103,22 +101,10 @@ def gvf_2015a( gvfalb = gvfalb / azimuthA.__len__() gvfalbnosh = gvfalbnosh / azimuthA.__len__() - gvfLupE = ( - gvfLupE / (azimuthA.__len__() / 2) - + SBC * emis_grid * (Ta + 273.15) ** 4 - ) - gvfLupS = ( - gvfLupS / (azimuthA.__len__() / 2) - + SBC * emis_grid * (Ta + 273.15) ** 4 - ) - gvfLupW = ( - gvfLupW / (azimuthA.__len__() / 2) - + SBC * emis_grid * (Ta + 273.15) ** 4 - ) - gvfLupN = ( - gvfLupN / (azimuthA.__len__() / 2) - + SBC * emis_grid * (Ta + 273.15) ** 4 - ) + gvfLupE = gvfLupE / (azimuthA.__len__() / 2) + SBC * emis_grid * (Ta + 273.15) ** 4 + gvfLupS = gvfLupS / (azimuthA.__len__() / 2) + SBC * emis_grid * (Ta + 273.15) ** 4 + gvfLupW = gvfLupW / (azimuthA.__len__() / 2) + SBC * emis_grid * (Ta + 273.15) ** 4 + gvfLupN = gvfLupN / (azimuthA.__len__() / 2) + SBC * emis_grid * (Ta + 273.15) ** 4 gvfalbE = gvfalbE / (azimuthA.__len__() / 2) gvfalbS = gvfalbS / (azimuthA.__len__() / 2) diff --git a/functions/SOLWEIGpython/gvf_2018a.py b/functions/SOLWEIGpython/gvf_2018a.py index d3bbfd1..fade1f8 100644 --- a/functions/SOLWEIGpython/gvf_2018a.py +++ b/functions/SOLWEIGpython/gvf_2018a.py @@ -27,9 +27,7 @@ def gvf_2018a( lc_grid, landcover, ): - azimuthA = np.arange( - 5, 359, 20 - ) # Search directions for Ground View Factors (GVF) + azimuthA = np.arange(5, 359, 20) # Search directions for Ground View Factors (GVF) #### Ground View Factors #### gvfLup = np.zeros((rows, cols)) @@ -105,22 +103,10 @@ def gvf_2018a( gvfalb = gvfalb / azimuthA.__len__() gvfalbnosh = gvfalbnosh / azimuthA.__len__() - gvfLupE = ( - gvfLupE / (azimuthA.__len__() / 2) - + SBC * emis_grid * (Ta + 273.15) ** 4 - ) - gvfLupS = ( - gvfLupS / (azimuthA.__len__() / 2) - + SBC * emis_grid * (Ta + 273.15) ** 4 - ) - gvfLupW = ( - gvfLupW / (azimuthA.__len__() / 2) - + SBC * emis_grid * (Ta + 273.15) ** 4 - ) - gvfLupN = ( - gvfLupN / (azimuthA.__len__() / 2) - + SBC * emis_grid * (Ta + 273.15) ** 4 - ) + gvfLupE = gvfLupE / (azimuthA.__len__() / 2) + SBC * emis_grid * (Ta + 273.15) ** 4 + gvfLupS = gvfLupS / (azimuthA.__len__() / 2) + SBC * emis_grid * (Ta + 273.15) ** 4 + gvfLupW = gvfLupW / (azimuthA.__len__() / 2) + SBC * emis_grid * (Ta + 273.15) ** 4 + gvfLupN = gvfLupN / (azimuthA.__len__() / 2) + SBC * emis_grid * (Ta + 273.15) ** 4 gvfalbE = gvfalbE / (azimuthA.__len__() / 2) gvfalbS = gvfalbS / (azimuthA.__len__() / 2) diff --git a/functions/SOLWEIGpython/patch_characteristics.py b/functions/SOLWEIGpython/patch_characteristics.py index 2a1ce27..c97dac5 100644 --- a/functions/SOLWEIGpython/patch_characteristics.py +++ b/functions/SOLWEIGpython/patch_characteristics.py @@ -66,9 +66,7 @@ def define_patch_characteristics( Lside_sky += temp_sky * Lsky_side[idx, 2] # Calculations for patches that are vegetation, vegshmat = 0 = shade from vegetation - temp_vegsh = (vegshmat[:, :, idx] == 0) | ( - vbshvegshmat[:, :, idx] == 0 - ) + temp_vegsh = (vegshmat[:, :, idx] == 0) | (vbshvegshmat[:, :, idx] == 0) # Longwave radiation from vegetation surface (considered vertical) vegetation_surface = (ewall * SBC * ((Ta + 273.15) ** 4)) / np.pi @@ -157,14 +155,12 @@ def define_patch_characteristics( and (solar_altitude > 0) ): # Calculate which patches defined as buildings that are sunlit or shaded - sunlit_patches, shaded_patches = ( - sunlit_shaded_patches.shaded_or_sunlit( - solar_altitude, - solar_azimuth, - patch_altitude[idx], - patch_azimuth[idx], - asvf, - ) + sunlit_patches, shaded_patches = sunlit_shaded_patches.shaded_or_sunlit( + solar_altitude, + solar_azimuth, + patch_altitude[idx], + patch_azimuth[idx], + asvf, ) # Calculate longwave radiation from sunlit walls to vertical surface diff --git a/functions/SOLWEIGpython/patch_radiation.py b/functions/SOLWEIGpython/patch_radiation.py index 4787f1f..16d18a9 100644 --- a/functions/SOLWEIGpython/patch_radiation.py +++ b/functions/SOLWEIGpython/patch_radiation.py @@ -2,9 +2,7 @@ from . import sunlit_shaded_patches -def shortwave_from_sky( - sky, angle_of_incidence, lumChi, steradian, patch_azimuth, cyl -): +def shortwave_from_sky(sky, angle_of_incidence, lumChi, steradian, patch_azimuth, cyl): """Calculates the amount of diffuse shortwave radiation from the sky for a patch with: angle of incidence = angle_of_incidence luminance = lumChi @@ -72,14 +70,10 @@ def longwave_from_veg( vegetation_surface = (ewall * SBC * ((Ta + 273.15) ** 4)) / np.pi # Longwave radiation reaching a vertical surface - Lside_veg = ( - vegetation_surface * steradian * angle_of_incidence * vegetation - ) + Lside_veg = vegetation_surface * steradian * angle_of_incidence * vegetation # Longwave radiation reaching a horizontal surface - Ldown_veg = ( - vegetation_surface * steradian * angle_of_incidence_h * vegetation - ) + Ldown_veg = vegetation_surface * steradian * angle_of_incidence_h * vegetation # Least = np.zeros((vegetation.shape[0], vegetation.shape[1])) @@ -165,19 +159,11 @@ def longwave_from_buildings( # Calculate longwave radiation from sunlit walls to vertical surface Lside_sun = ( - sunlit_surface - * sunlit_patches - * steradian - * angle_of_incidence - * building + sunlit_surface * sunlit_patches * steradian * angle_of_incidence * building ) # Calculate longwave radiation from shaded walls to vertical surface Lside_sh = ( - shaded_surface - * shaded_patches - * steradian - * angle_of_incidence - * building + shaded_surface * shaded_patches * steradian * angle_of_incidence * building ) # Calculate longwave radiation from sunlit walls to horizontal surface @@ -407,33 +393,19 @@ def reflected_longwave( # Reflected longwave radiation reaching vertical surfaces Lside_ref = ( - reflected_radiation - * steradian - * angle_of_incidence - * reflecting_surface + reflected_radiation * steradian * angle_of_incidence * reflecting_surface ) # Reflected longwave radiation reaching horizontal surfaces Ldown_ref = ( - reflected_radiation - * steradian - * angle_of_incidence_h - * reflecting_surface + reflected_radiation * steradian * angle_of_incidence_h * reflecting_surface ) # - Least = np.zeros( - (reflecting_surface.shape[0], reflecting_surface.shape[1]) - ) - Lsouth = np.zeros( - (reflecting_surface.shape[0], reflecting_surface.shape[1]) - ) - Lwest = np.zeros( - (reflecting_surface.shape[0], reflecting_surface.shape[1]) - ) - Lnorth = np.zeros( - (reflecting_surface.shape[0], reflecting_surface.shape[1]) - ) + Least = np.zeros((reflecting_surface.shape[0], reflecting_surface.shape[1])) + Lsouth = np.zeros((reflecting_surface.shape[0], reflecting_surface.shape[1])) + Lwest = np.zeros((reflecting_surface.shape[0], reflecting_surface.shape[1])) + Lnorth = np.zeros((reflecting_surface.shape[0], reflecting_surface.shape[1])) # Portion into cardinal directions to be used for standing box or POI output if (patch_azimuth > 360) or (patch_azimuth < 180): @@ -489,17 +461,13 @@ def patch_steradians(L_patches): for i in range(patch_altitude.shape[0]): # If there are more than one patch in a band if skyalt_c[skyalt == patch_altitude[i]] > 1: - steradian[i] = ( - (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad - ) * ( + steradian[i] = ((360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad) * ( np.sin((patch_altitude[i] + patch_altitude[0]) * deg2rad) - np.sin((patch_altitude[i] - patch_altitude[0]) * deg2rad) ) # If there is only one patch in band, i.e. 90 degrees else: - steradian[i] = ( - (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad - ) * ( + steradian[i] = ((360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad) * ( np.sin((patch_altitude[i]) * deg2rad) - np.sin((patch_altitude[i - 1] + patch_altitude[0]) * deg2rad) ) diff --git a/functions/SOLWEIGpython/sunonsurface_2018a.py b/functions/SOLWEIGpython/sunonsurface_2018a.py index 0acbc6c..96b114e 100644 --- a/functions/SOLWEIGpython/sunonsurface_2018a.py +++ b/functions/SOLWEIGpython/sunonsurface_2018a.py @@ -48,8 +48,7 @@ def sunonsurface_2018a( Tg[lc_grid == 3] = Twater - Ta # Setting water temperature Lwall = ( - SBC * ewall * (Tgwall + Ta + 273.15) ** 4 - - SBC * ewall * (Ta + 273.15) ** 4 + SBC * ewall * (Tgwall + Ta + 273.15) ** 4 - SBC * ewall * (Ta + 273.15) ** 4 ) # +Ta albshadow = alb_grid * shadow alb = alb_grid @@ -170,9 +169,7 @@ def sunonsurface_2018a( weightsumalbwall_first = weightsumalbwall / ind # *albedo_b weightsumalbsh_first = weightsumalbsh / ind - weightsumalbwallnosh_first = ( - weightsumalbwallnosh / ind - ) # *albedo_b + weightsumalbwallnosh_first = weightsumalbwallnosh / ind # *albedo_b weightsumalbnosh_first = weightsumalbnosh / ind wallinfluence_first = weightsumalbwallnosh_first > 0 # gvf1=(weightsumwall+weightsumsh)/first; @@ -200,9 +197,7 @@ def sunonsurface_2018a( ) # (SHADOW) # check for the -1 elif azilow > 0 and azihigh >= 2 * np.pi: # 270 to 360 azihigh = azihigh - 2 * np.pi - facesh = ( - np.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 - ) # (SHADOW) + facesh = np.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 # (SHADOW) # removing walls in self shadoing keep = (weightsumwall == second) - facesh @@ -215,11 +210,9 @@ def sunonsurface_2018a( wallsuninfluence_first * -1 + 1 ) weightsumwall[keep == 1] = 0 - gvf2 = ( - (weightsumwall + weightsumsh) / (second + 1) - ) * wallsuninfluence_second + (weightsumsh) / (second) * ( - wallsuninfluence_second * -1 + 1 - ) + gvf2 = ((weightsumwall + weightsumsh) / (second + 1)) * wallsuninfluence_second + ( + weightsumsh + ) / (second) * (wallsuninfluence_second * -1 + 1) gvf2[gvf2 > 1.0] = 1.0 diff --git a/functions/SOLWEIGpython/wallOfInterest.py b/functions/SOLWEIGpython/wallOfInterest.py index 60c1771..81cea70 100644 --- a/functions/SOLWEIGpython/wallOfInterest.py +++ b/functions/SOLWEIGpython/wallOfInterest.py @@ -163,9 +163,7 @@ def fillWallOfInterest( # Num format for output file data # woi_numformat = '%d %d %d %d %.5f %.2f %.2f' + ' %.2f' * temp_wall.shape[0] - woi_numformat = ( - "%d %d %d %d %.5f %.2f %.2f" + " %.2f" * temp_all.shape[0] - ) + woi_numformat = "%d %d %d %d %.5f %.2f %.2f" + " %.2f" * temp_all.shape[0] # Open file, add data, save f_handle = open(data_out, "ab") np.savetxt(f_handle, wall_data, fmt=woi_numformat) diff --git a/functions/SOLWEIGpython/wall_cover.py b/functions/SOLWEIGpython/wall_cover.py index da81e5c..e3e483e 100644 --- a/functions/SOLWEIGpython/wall_cover.py +++ b/functions/SOLWEIGpython/wall_cover.py @@ -25,14 +25,9 @@ def get_wall_cover(voxelTable, lcgrid, dsm, lc_params): # Loop through all wall pixels in voxel table for i in range(voxelTable.shape[0]): # Temporary lc_grid based on kernel and wall y and x position - temp_lc = ( - lcgrid[ypos[i] - 1 : ypos[i] + 2, xpos[i] - 1 : xpos[i] + 2] - * domain - ) + temp_lc = lcgrid[ypos[i] - 1 : ypos[i] + 2, xpos[i] - 1 : xpos[i] + 2] * domain # Temporary dsm based on kernel and wall y and x position - temp_dsm = ( - dsm[ypos[i] - 1 : ypos[i] + 2, xpos[i] - 1 : xpos[i] + 2] * domain - ) + temp_dsm = dsm[ypos[i] - 1 : ypos[i] + 2, xpos[i] - 1 : xpos[i] + 2] * domain # Temporary code based on highest pixel in temp_dsm where temp_lc is a building temp_code = temp_lc[((temp_lc > 99) & (temp_dsm == temp_dsm.max()))] diff --git a/functions/SOLWEIGpython/wall_surface_temperature.py b/functions/SOLWEIGpython/wall_surface_temperature.py index b49bec8..dbfac97 100644 --- a/functions/SOLWEIGpython/wall_surface_temperature.py +++ b/functions/SOLWEIGpython/wall_surface_temperature.py @@ -84,11 +84,7 @@ def load_walls( # tmp[tmp < 0.] = 0. # %matlab crazyness around 0 # svfalfa = np.arcsin(np.exp((np.log((1. - tmp)) / 2.))) - tmp = ( - voxelTable["SVF_fix"].to_numpy() - + voxelTable["svfveg"].to_numpy() - - 1.0 - ) + tmp = voxelTable["SVF_fix"].to_numpy() + voxelTable["svfveg"].to_numpy() - 1.0 tmp[tmp < 0.0] = 0.0 voxelTable["svfalfa"] = np.arcsin(np.exp((np.log((1.0 - tmp)) / 2.0))) @@ -107,17 +103,14 @@ def load_walls( temp_aspect = wallaspect[ temp_table[i, 1].astype(int), temp_table[i, 2].astype(int) ] - voxelTable.loc[ - voxelTable["wallId"] == temp_table[i, 0], "wallAspect" - ] = temp_aspect + voxelTable.loc[voxelTable["wallId"] == temp_table[i, 0], "wallAspect"] = ( + temp_aspect + ) temp_building = ( voxelTable.loc[ ( (voxelTable["wallId"] == temp_table[i, 0]) - & ( - voxelTable["voxelHeight"] - == voxelTable["voxelHeight"].min() - ) + & (voxelTable["voxelHeight"] == voxelTable["voxelHeight"].min()) ), "svfbu", ] @@ -128,10 +121,7 @@ def load_walls( voxelTable.loc[ ( (voxelTable["wallId"] == temp_table[i, 0]) - & ( - voxelTable["voxelHeight"] - == voxelTable["voxelHeight"].min() - ) + & (voxelTable["voxelHeight"] == voxelTable["voxelHeight"].min()) ), "svfaveg", ] @@ -141,22 +131,18 @@ def load_walls( temp_albedo = albedo_grid[ temp_table[i, 1].astype(int), temp_table[i, 2].astype(int) ] - temp_dsm = dsm[ - temp_table[i, 1].astype(int), temp_table[i, 2].astype(int) - ] + temp_dsm = dsm[temp_table[i, 1].astype(int), temp_table[i, 2].astype(int)] - voxelTable.loc[ - voxelTable["wallId"] == temp_table[i, 0], "svfbu_at_ground" - ] = temp_building + voxelTable.loc[voxelTable["wallId"] == temp_table[i, 0], "svfbu_at_ground"] = ( + temp_building + ) voxelTable.loc[ voxelTable["wallId"] == temp_table[i, 0], "svfaveg_at_ground" ] = temp_veg - voxelTable.loc[ - voxelTable["wallId"] == temp_table[i, 0], "groundAlbedo" - ] = temp_albedo - voxelTable.loc[ - voxelTable["wallId"] == temp_table[i, 0], "voxelHeightMasl" - ] = ( + voxelTable.loc[voxelTable["wallId"] == temp_table[i, 0], "groundAlbedo"] = ( + temp_albedo + ) + voxelTable.loc[voxelTable["wallId"] == temp_table[i, 0], "voxelHeightMasl"] = ( voxelTable.loc[ voxelTable["wallId"] == temp_table[i, 0], "voxelHeight" ].to_numpy() @@ -189,9 +175,7 @@ def load_walls( unique_walls = unique_landcover[unique_landcover > 99].astype(int) # Get wall properties for wall surface temperature scheme if unique_walls.size > 1: - voxelTable = get_wall_cover( - voxelTable, lcgrid, dsm, solweig_parameters - ) + voxelTable = get_wall_cover(voxelTable, lcgrid, dsm, solweig_parameters) elif unique_walls.size == 1: # Specific heat capacity wallTc = solweig_parameters["Specific_heat"]["Value"][ @@ -220,9 +204,9 @@ def load_walls( # voxelTable['wallEmissivity'] = solweig_parameters['Emissivity']['Value'][solweig_parameters['Names']['Value'][str(unique_walls[0])]] voxelTable["wallEmissivity"] = emissivity_b # Set thickness - voxelTable["wallThickness"] = solweig_parameters["Wall_thickness"][ - "Value" - ][solweig_parameters["Names"]["Value"][str(unique_walls[0])]] + voxelTable["wallThickness"] = solweig_parameters["Wall_thickness"]["Value"][ + solweig_parameters["Names"]["Value"][str(unique_walls[0])] + ] else: landcover = 0 @@ -254,17 +238,15 @@ def load_walls( # voxelTable['wallEmissivity'] = solweig_parameters['Emissivity']['Value'][solweig_parameters['Names']['Value'][wall_type]] voxelTable["wallEmissivity"] = emissivity_b # Get wall thickness - voxelTable["wallThickness"] = solweig_parameters["Wall_thickness"][ - "Value" - ][solweig_parameters["Names"]["Value"][wall_type]] + voxelTable["wallThickness"] = solweig_parameters["Wall_thickness"]["Value"][ + solweig_parameters["Names"]["Value"][wall_type] + ] eqTime = True ### REMEMEBER TO TURN OFF FOR KOLUMBUS ### if eqTime: - voxelTable["timeStep"] = voxelTable[ - "wallThickness" - ].to_numpy() ** 2 / ( + voxelTable["timeStep"] = voxelTable["wallThickness"].to_numpy() ** 2 / ( np.pi**2 * voxelTable["thermalDiffusivity"].to_numpy() ) @@ -276,9 +258,7 @@ def step_heating(q, e, t): return (2 * q) / e * np.sqrt(t / np.pi) -def surface_temperature_calc( - effusivity, t, Kin, Lin, Ta, wall_emissivity, Ts_previous -): +def surface_temperature_calc(effusivity, t, Kin, Lin, Ta, wall_emissivity, Ts_previous): """Function to get surface temperature""" dT = np.zeros((effusivity.shape[0])) @@ -358,9 +338,9 @@ def wall_surface_temperature( "wallShade", ] = 1 # Add wall shade height to pandas dataframe - voxelTable.loc[ - (voxelTable["wallId"] == unique_wall), "wallShadeHeight" - ] = temp_sh + voxelTable.loc[(voxelTable["wallId"] == unique_wall), "wallShadeHeight"] = ( + temp_sh + ) # If sun is below horizon, everything is in "shade" else: voxelTable["wallShadeHeight"] = voxelTable["wallHeight_exact"] @@ -508,8 +488,7 @@ def wall_surface_temperature( + K_down * voxelTable["wallAlbedo"].to_numpy() * voxelTable["building_fraction"] - + (K_down * voxelTable["groundAlbedo"]) - * voxelTable["ground_fraction"] + + (K_down * voxelTable["groundAlbedo"]) * voxelTable["ground_fraction"] ) # If sun below horizon else: diff --git a/functions/SOLWEIGpython/wallsAsNetCDF.py b/functions/SOLWEIGpython/wallsAsNetCDF.py index 2d2e91e..dd70c29 100644 --- a/functions/SOLWEIGpython/wallsAsNetCDF.py +++ b/functions/SOLWEIGpython/wallsAsNetCDF.py @@ -21,8 +21,7 @@ def walls_as_netcdf( levels = ( voxelTable.loc[ - voxelTable["voxelHeightMasl"] - == voxelTable["voxelHeightMasl"].max(), + voxelTable["voxelHeightMasl"] == voxelTable["voxelHeightMasl"].max(), "voxelHeightMasl", ] .to_numpy()[0] @@ -66,9 +65,7 @@ def walls_as_netcdf( data_vars=dict( wall_temperature=(["lon", "lat", "height", "time"], temp_data), ), - coords=dict( - lon=lon, lat=lat, height=height_levels, time=timeSlots - ), + coords=dict(lon=lon, lat=lat, height=height_levels, time=timeSlots), attrs={"crs": raster_file.rio.crs.to_string()}, ) diff --git a/functions/TreeGenerator/makevegdems.py b/functions/TreeGenerator/makevegdems.py index 7bd7fd0..20e0cd8 100644 --- a/functions/TreeGenerator/makevegdems.py +++ b/functions/TreeGenerator/makevegdems.py @@ -215,9 +215,7 @@ def imcircle(n): width = np.append(np.fliplr(width), width, axis=1) for k in range(0, int(DIAMETER)): - semicircle[k, 0 : int(width[0, k])] = np.ones( - [1, int(width[0, k])] - ) + semicircle[k, 0 : int(width[0, k])] = np.ones([1, int(width[0, k])]) y = np.append(np.fliplr(semicircle), semicircle, axis=1) @@ -248,19 +246,13 @@ def imcircle(n): np.mean([width[0, index - 1], width[0, index + 1]]) ) - width1 = np.append( - np.fliplr(width), np.ones([1, 1]) * np.max(width), axis=1 - ) + width1 = np.append(np.fliplr(width), np.ones([1, 1]) * np.max(width), axis=1) width = np.append(width1, width, axis=1) for k in range(0, int(DIAMETER)): - semicircle[k, 0 : int(width[0, k])] = np.ones( - [1, int(width[0, k])] - ) + semicircle[k, 0 : int(width[0, k])] = np.ones([1, int(width[0, k])]) - y = np.append( - np.fliplr(semicircle), np.ones([int(DIAMETER), 1]), axis=1 - ) + y = np.append(np.fliplr(semicircle), np.ones([int(DIAMETER), 1]), axis=1) y = np.append(y, semicircle, axis=1) return y diff --git a/functions/TreePlanter/SOLWEIG1D/Kside1D_veg_v2019a.py b/functions/TreePlanter/SOLWEIG1D/Kside1D_veg_v2019a.py index c52dbe4..ed2c09e 100644 --- a/functions/TreePlanter/SOLWEIG1D/Kside1D_veg_v2019a.py +++ b/functions/TreePlanter/SOLWEIG1D/Kside1D_veg_v2019a.py @@ -51,38 +51,22 @@ def Kside_veg_v2019a( KnorthI = 0 else: # Kside with weights ### if azimuth > (360 - t) or azimuth <= (180 - t): - KeastI = ( - radI - * shadow - * np.cos(altitude * deg2rad) - * np.sin(aziE * deg2rad) - ) + KeastI = radI * shadow * np.cos(altitude * deg2rad) * np.sin(aziE * deg2rad) else: KeastI = 0 if azimuth > (90 - t) and azimuth <= (270 - t): KsouthI = ( - radI - * shadow - * np.cos(altitude * deg2rad) - * np.sin(aziS * deg2rad) + radI * shadow * np.cos(altitude * deg2rad) * np.sin(aziS * deg2rad) ) else: KsouthI = 0 if azimuth > (180 - t) and azimuth <= (360 - t): - KwestI = ( - radI - * shadow - * np.cos(altitude * deg2rad) - * np.sin(aziW * deg2rad) - ) + KwestI = radI * shadow * np.cos(altitude * deg2rad) * np.sin(aziW * deg2rad) else: KwestI = 0 if azimuth <= (90 - t) or azimuth > (270 - t): KnorthI = ( - radI - * shadow - * np.cos(altitude * deg2rad) - * np.sin(aziN * deg2rad) + radI * shadow * np.cos(altitude * deg2rad) * np.sin(aziN * deg2rad) ) else: KnorthI = 0 @@ -128,8 +112,7 @@ def Kside_veg_v2019a( aziDel = 360 phiVar[ix] = (aziDel * deg2rad) * ( - np.sin((aniAlt[ix] + 6) * deg2rad) - - np.sin((aniAlt[ix] - 6) * deg2rad) + np.sin((aniAlt[ix] + 6) * deg2rad) - np.sin((aniAlt[ix] - 6) * deg2rad) ) # Solid angle / Steradian radTot = radTot + ( @@ -149,20 +132,16 @@ def Kside_veg_v2019a( KsideD + diffsh[idx] * lumChi[idx] * anglIncC * phiVar[idx] ) # Diffuse vertical radiation Keast = ( - albedo * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) - + KupE + albedo * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) + KupE ) * 0.5 Ksouth = ( - albedo * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) - + KupS + albedo * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) + KupS ) * 0.5 Kwest = ( - albedo * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) - + KupW + albedo * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + KupW ) * 0.5 Knorth = ( - albedo * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) - + KupN + albedo * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + KupN ) * 0.5 else: # Box diffRadE = np.zeros((rows, cols)) @@ -174,95 +153,67 @@ def Kside_veg_v2019a( if aniAzi[idx] <= (180): anglIncE = np.cos(aniAlt[idx] * deg2rad) * np.cos( (90 - aniAzi[idx]) * deg2rad - ) * np.sin(np.pi / 2) + np.sin( - aniAlt[idx] * deg2rad - ) * np.cos( + ) * np.sin(np.pi / 2) + np.sin(aniAlt[idx] * deg2rad) * np.cos( np.pi / 2 ) diffRadE = ( - diffRadE - + diffsh[idx] * lumChi[idx] * anglIncE * phiVar[idx] + diffRadE + diffsh[idx] * lumChi[idx] * anglIncE * phiVar[idx] ) # * 0.5 if aniAzi[idx] > (90) and aniAzi[idx] <= (270): anglIncS = np.cos(aniAlt[idx] * deg2rad) * np.cos( (180 - aniAzi[idx]) * deg2rad - ) * np.sin(np.pi / 2) + np.sin( - aniAlt[idx] * deg2rad - ) * np.cos( + ) * np.sin(np.pi / 2) + np.sin(aniAlt[idx] * deg2rad) * np.cos( np.pi / 2 ) diffRadS = ( - diffRadS - + diffsh[idx] * lumChi[idx] * anglIncS * phiVar[idx] + diffRadS + diffsh[idx] * lumChi[idx] * anglIncS * phiVar[idx] ) # * 0.5 if aniAzi[idx] > (180) and aniAzi[idx] <= (360): anglIncW = np.cos(aniAlt[idx] * deg2rad) * np.cos( (270 - aniAzi[idx]) * deg2rad - ) * np.sin(np.pi / 2) + np.sin( - aniAlt[idx] * deg2rad - ) * np.cos( + ) * np.sin(np.pi / 2) + np.sin(aniAlt[idx] * deg2rad) * np.cos( np.pi / 2 ) diffRadW = ( - diffRadW - + diffsh[idx] * lumChi[idx] * anglIncW * phiVar[idx] + diffRadW + diffsh[idx] * lumChi[idx] * anglIncW * phiVar[idx] ) # * 0.5 if aniAzi[idx] > (270) or aniAzi[idx] <= (90): anglIncN = np.cos(aniAlt[idx] * deg2rad) * np.cos( (0 - aniAzi[idx]) * deg2rad - ) * np.sin(np.pi / 2) + np.sin( - aniAlt[idx] * deg2rad - ) * np.cos( + ) * np.sin(np.pi / 2) + np.sin(aniAlt[idx] * deg2rad) * np.cos( np.pi / 2 ) diffRadN = ( - diffRadN - + diffsh[idx] * lumChi[idx] * anglIncN * phiVar[idx] + diffRadN + diffsh[idx] * lumChi[idx] * anglIncN * phiVar[idx] ) # * 0.5 KeastDG = ( diffRadE - + ( - albedo - * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) - + KupE - ) + + (albedo * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) + KupE) * 0.5 ) Keast = KeastI + KeastDG KsouthDG = ( diffRadS - + ( - albedo - * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) - + KupS - ) + + (albedo * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) + KupS) * 0.5 ) Ksouth = KsouthI + KsouthDG KwestDG = ( diffRadW - + ( - albedo - * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) - + KupW - ) + + (albedo * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + KupW) * 0.5 ) Kwest = KwestI + KwestDG KnorthDG = ( diffRadN - + ( - albedo - * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) - + KupN - ) + + (albedo * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + KupN) * 0.5 ) Knorth = KnorthI + KnorthDG diff --git a/functions/TreePlanter/SOLWEIG1D/SOLWEIG1D_2023a.py b/functions/TreePlanter/SOLWEIG1D/SOLWEIG1D_2023a.py index a91a45b..72f503e 100644 --- a/functions/TreePlanter/SOLWEIG1D/SOLWEIG1D_2023a.py +++ b/functions/TreePlanter/SOLWEIG1D/SOLWEIG1D_2023a.py @@ -144,8 +144,7 @@ def tmrt_1d_fun(metfilepath, infolder, tau, lon, lat, dsm, r_range, outputDir): # load landcover file sitein = ( - os.path.dirname(os.path.abspath(__file__)) - + "/landcoverclasses_2018a_orig.txt" + os.path.dirname(os.path.abspath(__file__)) + "/landcoverclasses_2018a_orig.txt" ) f = open(sitein) lin = f.readlines() diff --git a/functions/TreePlanter/SOLWEIG1D/SOLWEIG_1D.py b/functions/TreePlanter/SOLWEIG1D/SOLWEIG_1D.py index 99ae37e..109928f 100644 --- a/functions/TreePlanter/SOLWEIG1D/SOLWEIG_1D.py +++ b/functions/TreePlanter/SOLWEIG1D/SOLWEIG_1D.py @@ -144,8 +144,7 @@ def tmrt_1d_fun(metfilepath, infolder, tau, lon, lat, dsm, r_range): # load landcover file sitein = ( - os.path.dirname(os.path.abspath(__file__)) - + "/landcoverclasses_2018a_orig.txt" + os.path.dirname(os.path.abspath(__file__)) + "/landcoverclasses_2018a_orig.txt" ) f = open(sitein) lin = f.readlines() diff --git a/functions/TreePlanter/SOLWEIG1D/Solweig1D_2019a_calc.py b/functions/TreePlanter/SOLWEIG1D/Solweig1D_2019a_calc.py index f58ca0b..a80bf10 100644 --- a/functions/TreePlanter/SOLWEIG1D/Solweig1D_2019a_calc.py +++ b/functions/TreePlanter/SOLWEIG1D/Solweig1D_2019a_calc.py @@ -133,9 +133,7 @@ def Solweig1D_2019a_calc( aniLum + diffsh[idx] * lv[0][idx][2] ) # Total relative luminance from sky into each cell - dRad = ( - aniLum * radD - ) # Total diffuse radiation from sky into each cell + dRad = aniLum * radD # Total diffuse radiation from sky into each cell else: dRad = radD * svf diff --git a/functions/TreePlanter/SOLWEIG1D/Solweig1D_2023a_calc.py b/functions/TreePlanter/SOLWEIG1D/Solweig1D_2023a_calc.py index bf99a7c..81f6a1c 100644 --- a/functions/TreePlanter/SOLWEIG1D/Solweig1D_2023a_calc.py +++ b/functions/TreePlanter/SOLWEIG1D/Solweig1D_2023a_calc.py @@ -145,9 +145,7 @@ def Solweig1D_2019a_calc( aniLum + skyp[idx] * lv[idx, 2] ) # Total relative luminance from sky into each cell - dRad = ( - aniLum * radD - ) # Total diffuse radiation from sky into each cell + dRad = aniLum * radD # Total diffuse radiation from sky into each cell else: dRad = radD * svf @@ -160,10 +158,7 @@ def Solweig1D_2019a_calc( # Tgampwall = (TgK_wall * altmax - (Tstart_wall)) + (Tstart_wall) # Old Tgampwall = TgK_wall * altmax + Tstart_wall Tg = Tgamp * np.sin( - ( - ((dectime - np.floor(dectime)) - SNUP / 24) - / (TmaxLST / 24 - SNUP / 24) - ) + (((dectime - np.floor(dectime)) - SNUP / 24) / (TmaxLST / 24 - SNUP / 24)) * np.pi / 2 ) # 2015 a, based on max sun altitude diff --git a/functions/TreePlanter/SOLWEIG1D/anisotropic_sky.py b/functions/TreePlanter/SOLWEIG1D/anisotropic_sky.py index 63aa195..a7b91a5 100644 --- a/functions/TreePlanter/SOLWEIG1D/anisotropic_sky.py +++ b/functions/TreePlanter/SOLWEIG1D/anisotropic_sky.py @@ -147,9 +147,7 @@ def anisotropic_sky( # Radiance fraction normalization for i in np.arange(patch_altitude.shape[0]): radTot += ( - patch_luminance[i] - * steradians[i] - * np.sin(patch_altitude[i] * deg2rad) + patch_luminance[i] * steradians[i] * np.sin(patch_altitude[i] * deg2rad) ) # Radiance fraction normalization lumChi = (patch_luminance * radD) / radTot @@ -170,14 +168,12 @@ def anisotropic_sky( # temp_sh_roof = temp_sh * (voxelMaps[:, :, i] == 0) # Estimate sunlit and shaded patches - sunlit_patches, shaded_patches = ( - sunlit_shaded_patches.shaded_or_sunlit( - solar_altitude, - solar_azimuth, - patch_altitude[i], - patch_azimuth[i], - asvf, - ) + sunlit_patches, shaded_patches = sunlit_shaded_patches.shaded_or_sunlit( + solar_altitude, + solar_azimuth, + patch_altitude[i], + patch_azimuth[i], + asvf, ) if cyl == 1: @@ -298,9 +294,7 @@ def anisotropic_sky( ### CALCULATIONS FOR SHORTWAVE RADIATION ### if solar_altitude > 0: # Shortwave radiation from sky - KsideD += ( - skyp[i] * lumChi[i] * angle_of_incidence * steradians[i] - ) + KsideD += skyp[i] * lumChi[i] * angle_of_incidence * steradians[i] if skyp[i]: Keast_temp, Ksouth_temp, Kwest_temp, Knorth_temp = ( @@ -318,18 +312,14 @@ def anisotropic_sky( # Shortwave reflected on sunlit surfaces # sunlit_surface = ((albedo * radG) / np.pi) sunlit_surface = ( - albedo * (radI * np.cos(solar_altitude * deg2rad)) - + (radD * 0.5) + albedo * (radI * np.cos(solar_altitude * deg2rad)) + (radD * 0.5) ) / np.pi # Shortwave reflected on shaded surfaces and vegetation shaded_surface = (albedo * radD * 0.5) / np.pi # Shortwave radiation from vegetation Kref_veg += ( - shaded_surface - * vegp[i] - * steradians[i] - * angle_of_incidence + shaded_surface * vegp[i] * steradians[i] * angle_of_incidence ) if vegp[i]: diff --git a/functions/TreePlanter/SOLWEIG1D/emissivity_models.py b/functions/TreePlanter/SOLWEIG1D/emissivity_models.py index 1762e50..6a3acf0 100644 --- a/functions/TreePlanter/SOLWEIG1D/emissivity_models.py +++ b/functions/TreePlanter/SOLWEIG1D/emissivity_models.py @@ -78,9 +78,7 @@ def model2(sky_patches, esky, Ta): # b_c = 0.1 # Estimate emissivites at different altitudes/zenith angles - esky_band = 1 - (1 - esky) * np.exp( - b_c * (1.7 - (1 / np.cos(skyzen * deg2rad))) - ) + esky_band = 1 - (1 - esky) * np.exp(b_c * (1.7 - (1 / np.cos(skyzen * deg2rad)))) # Altitudes of the Robinson & Stone patches p_alt = sky_patches[:, 0] diff --git a/functions/TreePlanter/SOLWEIG1D/patch_radiation.py b/functions/TreePlanter/SOLWEIG1D/patch_radiation.py index c031d86..2e2bf5d 100644 --- a/functions/TreePlanter/SOLWEIG1D/patch_radiation.py +++ b/functions/TreePlanter/SOLWEIG1D/patch_radiation.py @@ -1,9 +1,7 @@ import numpy as np -def shortwave_from_sky( - sky, angle_of_incidence, lumChi, steradian, patch_azimuth, cyl -): +def shortwave_from_sky(sky, angle_of_incidence, lumChi, steradian, patch_azimuth, cyl): """Calculates the amount of diffuse shortwave radiation from the sky for a patch with: angle of incidence = angle_of_incidence luminance = lumChi @@ -71,14 +69,10 @@ def longwave_from_veg( vegetation_surface = (ewall * SBC * ((Ta + 273.15) ** 4)) / np.pi # Longwave radiation reaching a vertical surface - Lside_veg = ( - vegetation_surface * steradian * angle_of_incidence * vegetation - ) + Lside_veg = vegetation_surface * steradian * angle_of_incidence * vegetation # Longwave radiation reaching a horizontal surface - Ldown_veg = ( - vegetation_surface * steradian * angle_of_incidence_h * vegetation - ) + Ldown_veg = vegetation_surface * steradian * angle_of_incidence_h * vegetation # Least = 0 @@ -164,19 +158,11 @@ def longwave_from_buildings( # Calculate longwave radiation from sunlit walls to vertical surface Lside_sun = ( - sunlit_surface - * sunlit_patches - * steradian - * angle_of_incidence - * building + sunlit_surface * sunlit_patches * steradian * angle_of_incidence * building ) # Calculate longwave radiation from shaded walls to vertical surface Lside_sh = ( - shaded_surface - * shaded_patches - * steradian - * angle_of_incidence - * building + shaded_surface * shaded_patches * steradian * angle_of_incidence * building ) # Calculate longwave radiation from sunlit walls to horizontal surface @@ -340,18 +326,12 @@ def reflected_longwave( # Reflected longwave radiation reaching vertical surfaces Lside_ref = ( - reflected_radiation - * steradian - * angle_of_incidence - * reflecting_surface + reflected_radiation * steradian * angle_of_incidence * reflecting_surface ) # Reflected longwave radiation reaching horizontal surfaces Ldown_ref = ( - reflected_radiation - * steradian - * angle_of_incidence_h - * reflecting_surface + reflected_radiation * steradian * angle_of_incidence_h * reflecting_surface ) # @@ -414,17 +394,13 @@ def patch_steradians(L_patches): for i in range(patch_altitude.shape[0]): # If there are more than one patch in a band if skyalt_c[skyalt == patch_altitude[i]] > 1: - steradian[i] = ( - (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad - ) * ( + steradian[i] = ((360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad) * ( np.sin((patch_altitude[i] + patch_altitude[0]) * deg2rad) - np.sin((patch_altitude[i] - patch_altitude[0]) * deg2rad) ) # If there is only one patch in band, i.e. 90 degrees else: - steradian[i] = ( - (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad - ) * ( + steradian[i] = ((360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad) * ( np.sin((patch_altitude[i]) * deg2rad) - np.sin((patch_altitude[i - 1] + patch_altitude[0]) * deg2rad) ) @@ -463,38 +439,22 @@ def cardinal_shortwave( if (patch_azimuth > 360) or (patch_azimuth < 180): angle_of_incidence = np.cos(patch_altitude * deg2rad) * np.cos( (90 - patch_azimuth) * deg2rad - ) * np.sin(np.pi / 2) + np.sin(patch_altitude * deg2rad) * np.cos( - np.pi / 2 - ) - Keast = ( - radiation * steradian * angle_of_incidence * patch_type * sunshade - ) + ) * np.sin(np.pi / 2) + np.sin(patch_altitude * deg2rad) * np.cos(np.pi / 2) + Keast = radiation * steradian * angle_of_incidence * patch_type * sunshade if (patch_azimuth > 90) and (patch_azimuth < 270): angle_of_incidence = np.cos(patch_altitude * deg2rad) * np.cos( (180 - patch_azimuth) * deg2rad - ) * np.sin(np.pi / 2) + np.sin(patch_altitude * deg2rad) * np.cos( - np.pi / 2 - ) - Ksouth = ( - radiation * steradian * angle_of_incidence * patch_type * sunshade - ) + ) * np.sin(np.pi / 2) + np.sin(patch_altitude * deg2rad) * np.cos(np.pi / 2) + Ksouth = radiation * steradian * angle_of_incidence * patch_type * sunshade if (patch_azimuth > 180) and (patch_azimuth < 360): angle_of_incidence = np.cos(patch_altitude * deg2rad) * np.cos( (270 - patch_azimuth) * deg2rad - ) * np.sin(np.pi / 2) + np.sin(patch_altitude * deg2rad) * np.cos( - np.pi / 2 - ) - Kwest = ( - radiation * steradian * angle_of_incidence * patch_type * sunshade - ) + ) * np.sin(np.pi / 2) + np.sin(patch_altitude * deg2rad) * np.cos(np.pi / 2) + Kwest = radiation * steradian * angle_of_incidence * patch_type * sunshade if (patch_azimuth > 270) or (patch_azimuth < 90): angle_of_incidence = np.cos(patch_altitude * deg2rad) * np.cos( (0 - patch_azimuth) * deg2rad - ) * np.sin(np.pi / 2) + np.sin(patch_altitude * deg2rad) * np.cos( - np.pi / 2 - ) - Knorth = ( - radiation * steradian * angle_of_incidence * patch_type * sunshade - ) + ) * np.sin(np.pi / 2) + np.sin(patch_altitude * deg2rad) * np.cos(np.pi / 2) + Knorth = radiation * steradian * angle_of_incidence * patch_type * sunshade return Keast, Ksouth, Kwest, Knorth diff --git a/functions/TreePlanter/TreeGeneratorTempold/makevegdems.py b/functions/TreePlanter/TreeGeneratorTempold/makevegdems.py index 27c3541..2d5b0c4 100644 --- a/functions/TreePlanter/TreeGeneratorTempold/makevegdems.py +++ b/functions/TreePlanter/TreeGeneratorTempold/makevegdems.py @@ -77,9 +77,7 @@ def vegunitsgeneration( or col1 + rowmax - 1 > vegdem.shape[1] ): # cutting tree at dem edge - trees = trees[ - int(rowcutmin) : int(rowcutmax), int(colcutmin) : int(colcutmax) - ] + trees = trees[int(rowcutmin) : int(rowcutmax), int(colcutmin) : int(colcutmax)] treetrunkunder = treetrunkunder[ int(rowcutmin) : int(rowcutmax), int(colcutmin) : int(colcutmax) ] @@ -211,9 +209,7 @@ def imcircle(n): width = np.append(np.fliplr(width), width, axis=1) for k in range(0, int(DIAMETER)): - semicircle[k, 0 : int(width[0, k])] = np.ones( - [1, int(width[0, k])] - ) + semicircle[k, 0 : int(width[0, k])] = np.ones([1, int(width[0, k])]) y = np.append(np.fliplr(semicircle), semicircle, axis=1) @@ -244,19 +240,13 @@ def imcircle(n): np.mean([width[0, index - 1], width[0, index + 1]]) ) - width1 = np.append( - np.fliplr(width), np.ones([1, 1]) * np.max(width), axis=1 - ) + width1 = np.append(np.fliplr(width), np.ones([1, 1]) * np.max(width), axis=1) width = np.append(width1, width, axis=1) for k in range(0, int(DIAMETER)): - semicircle[k, 0 : int(width[0, k])] = np.ones( - [1, int(width[0, k])] - ) + semicircle[k, 0 : int(width[0, k])] = np.ones([1, int(width[0, k])]) - y = np.append( - np.fliplr(semicircle), np.ones([int(DIAMETER), 1]), axis=1 - ) + y = np.append(np.fliplr(semicircle), np.ones([int(DIAMETER), 1]), axis=1) y = np.append(y, semicircle, axis=1) return y diff --git a/functions/TreePlanter/TreePlanter/GreedyAlgorithm.py b/functions/TreePlanter/TreePlanter/GreedyAlgorithm.py index d274224..b119569 100644 --- a/functions/TreePlanter/TreePlanter/GreedyAlgorithm.py +++ b/functions/TreePlanter/TreePlanter/GreedyAlgorithm.py @@ -73,9 +73,7 @@ def greedyplanter(treeinput, treedata, treerasters, tmrt_1d, trees, feedback): ) ts_temp1 = np.zeros((treeinput.rows, treeinput.cols)) - ts_temp1[yslice2, xslice2] = treerasters.treeshade[ - yslice1, xslice1 - ] + ts_temp1[yslice2, xslice2] = treerasters.treeshade[yslice1, xslice1] # Estimating Tmrt in tree shade and in sun for j in range(tmrt_1d.__len__()): @@ -105,9 +103,7 @@ def greedyplanter(treeinput, treedata, treerasters, tmrt_1d, trees, feedback): str(possible_locations) + " possible locations for trees..." ) - temp_y, temp_x = np.where( - treerasters.d_tmrt == np.max(treerasters.d_tmrt) - ) + temp_y, temp_x = np.where(treerasters.d_tmrt == np.max(treerasters.d_tmrt)) tmrt_max += np.max(treerasters.d_tmrt) @@ -134,22 +130,12 @@ def greedyplanter(treeinput, treedata, treerasters, tmrt_1d, trees, feedback): tmrt_copy[:, :, j] = tmrt_copy[:, :, j] * temp_shadow # Determine where to recalcaulate d_tmrt - y1 = np.int_( - temp_y[0] - treerasters.buffer_y[0] - treerasters.buffer_y[1] - ) - y2 = np.int_( - temp_y[0] + treerasters.buffer_y[1] + treerasters.buffer_y[0] - ) - x1 = np.int_( - temp_x[0] - treerasters.buffer_x[0] - treerasters.buffer_x[1] - ) - x2 = np.int_( - temp_x[0] + treerasters.buffer_x[1] + treerasters.buffer_x[0] - ) + y1 = np.int_(temp_y[0] - treerasters.buffer_y[0] - treerasters.buffer_y[1]) + y2 = np.int_(temp_y[0] + treerasters.buffer_y[1] + treerasters.buffer_y[0]) + x1 = np.int_(temp_x[0] - treerasters.buffer_x[0] - treerasters.buffer_x[1]) + x2 = np.int_(temp_x[0] + treerasters.buffer_x[1] + treerasters.buffer_x[0]) - _, __, yslice2, xslice2 = tree_slice( - y1, y2, x1, x2, treeinput, treerasters - ) + _, __, yslice2, xslice2 = tree_slice(y1, y2, x1, x2, treeinput, treerasters) recalc_positions = np.zeros((bld_copy.shape[0], bld_copy.shape[1])) recalc_positions[yslice2, xslice2] = 1 diff --git a/functions/TreePlanter/TreePlanter/HillClimberAlgorithm.py b/functions/TreePlanter/TreePlanter/HillClimberAlgorithm.py index 47fd3d3..fa54f53 100644 --- a/functions/TreePlanter/TreePlanter/HillClimberAlgorithm.py +++ b/functions/TreePlanter/TreePlanter/HillClimberAlgorithm.py @@ -130,15 +130,9 @@ def topt( y_n, x_n, treerasters, treeinput ) # Boolean tree shadows for trees that are not moving - if ( - compare == 0 - ): # If none of the none-moving trees overlap, go in here - treesh_bool_mt = tsh_gen_mt1( - t_yx[:, 0], t_yx[:, 1], treerasters, treeinput - ) - if np.any( - ((treesh_bool_mt == 1) & (treesh_ts_bool_pad_large == 1)) - ): + if compare == 0: # If none of the none-moving trees overlap, go in here + treesh_bool_mt = tsh_gen_mt1(t_yx[:, 0], t_yx[:, 1], treerasters, treeinput) + if np.any(((treesh_bool_mt == 1) & (treesh_ts_bool_pad_large == 1))): for j in range(t_yx.shape[0]): if ( e_bool_sh[j] == 1 @@ -149,8 +143,8 @@ def topt( x_t = np.array( [t_yx[j, 1]] ) # X-position of the current position of the currently moving tree - treesh_bool_mt_pad, treesh_bool_mt_pad_large, _ = ( - tsh_gen_ts(y_t, x_t, treerasters, treeinput) + treesh_bool_mt_pad, treesh_bool_mt_pad_large, _ = tsh_gen_ts( + y_t, x_t, treerasters, treeinput ) # Boolean tree shadows for moving tree if np.any( ( @@ -169,9 +163,7 @@ def topt( * (treeinput.shadow[:, :, ij] == 1) * (treeinput.buildings == 1) ) - tree_tmrt[j, 0] += np.sum( - temp_bool * tmrt_1d[ij, 0] - ) + tree_tmrt[j, 0] += np.sum(temp_bool * tmrt_1d[ij, 0]) tree_tmrt[j, 1] += np.sum( temp_bool * treeinput.tmrt_ts[:, :, ij] ) @@ -224,9 +216,7 @@ def topt( * treeinput.buildings ) tree_tmrt[i, 0] += np.sum(tree_bool_temp * tmrt_1d[j, 0]) - tree_tmrt[i, 1] += np.sum( - tree_bool_temp * treeinput.tmrt_ts[:, :, j] - ) + tree_tmrt[i, 1] += np.sum(tree_bool_temp * treeinput.tmrt_ts[:, :, j]) tree_tmrt[i, 0] += treerasters.tmrt_shade[y_t, x_t] tree_tmrt[i, 1] += treerasters.tmrt_sun[y_t, x_t] tree_tmrt[i, 2] = tree_tmrt[i, 1] - tree_tmrt[i, 0] @@ -235,12 +225,8 @@ def topt( elif (compare == 0) & (compare_mt[i] == 0): start_time = time.time() for j in range(y_n.shape[0]): - tree_tmrt[i, 0] += treerasters.tmrt_shade[ - y_n[j], x_n[j] - ] # Tree shade - tree_tmrt[i, 1] += treerasters.tmrt_sun[ - y_n[j], x_n[j] - ] # Sunlit + tree_tmrt[i, 0] += treerasters.tmrt_shade[y_n[j], x_n[j]] # Tree shade + tree_tmrt[i, 1] += treerasters.tmrt_sun[y_n[j], x_n[j]] # Sunlit tree_tmrt[i, 2] += treerasters.d_tmrt[ y_n[j], x_n[j] ] # Sunlit - Tree shade diff --git a/functions/TreePlanter/TreePlanter/StartingPositions.py b/functions/TreePlanter/TreePlanter/StartingPositions.py index 4148e58..acfbfbc 100644 --- a/functions/TreePlanter/TreePlanter/StartingPositions.py +++ b/functions/TreePlanter/TreePlanter/StartingPositions.py @@ -44,9 +44,7 @@ def genetic_start( # Random for first run if counter == 0: - tree_pos, tp_c, break_loop = random_start( - pos, trees, tree_pos_all, r_iters - ) + tree_pos, tp_c, break_loop = random_start(pos, trees, tree_pos_all, r_iters) # Take either x or y position from previous local optimum for each tree, make the other coordinate random else: @@ -111,9 +109,7 @@ def genetic_start( exists[i] = 1 # Euclidean distance between random positions so that trees are not too close to each other - yxp = tuple( - itertools.combinations(np.arange(tree_pos.shape[0]), 2) - ) + yxp = tuple(itertools.combinations(np.arange(tree_pos.shape[0]), 2)) eucl_dist = np.zeros((yxp.__len__())) for j in range(yxp.__len__()): diff --git a/functions/TreePlanter/TreePlanter/TreePlanterClasses.py b/functions/TreePlanter/TreePlanter/TreePlanterClasses.py index 438be4c..7e2b6bb 100644 --- a/functions/TreePlanter/TreePlanter/TreePlanterClasses.py +++ b/functions/TreePlanter/TreePlanter/TreePlanterClasses.py @@ -90,36 +90,22 @@ class Inputdata: "gt", ) - def __init__( - self, r_range, sh_fl, tmrt_fl, infolder, inputPolygonlayer, feedback - ): + def __init__(self, r_range, sh_fl, tmrt_fl, infolder, inputPolygonlayer, feedback): self.dataSet = gdal.Open(infolder + "/buildings.tif") # GIS data - self.buildings = self.dataSet.ReadAsArray().astype( - float - ) # Building raster + self.buildings = self.dataSet.ReadAsArray().astype(float) # Building raster self.buildings = self.buildings == 1.0 - self.rows = self.buildings.shape[ - 0 - ] # Rows of input rasters from SOLWEIG - self.cols = self.buildings.shape[ - 1 - ] # Cols of input rasters from SOLWEIG - self.cdsm = np.zeros( - (self.rows, self.cols) - ) # Canopy digital surface model - self.cdsm_b = np.zeros( - (self.rows, self.cols) - ) # Canopy digital surface model + self.rows = self.buildings.shape[0] # Rows of input rasters from SOLWEIG + self.cols = self.buildings.shape[1] # Cols of input rasters from SOLWEIG + self.cdsm = np.zeros((self.rows, self.cols)) # Canopy digital surface model + self.cdsm_b = np.zeros((self.rows, self.cols)) # Canopy digital surface model self.shadow = np.zeros( (self.rows, self.cols, r_range.__len__()) ) # Shadow rasters self.tmrt_ts = np.zeros( (self.rows, self.cols, r_range.__len__()) ) # Tmrt for each timestep - self.tmrt_s = np.zeros( - (self.rows, self.cols) - ) # Sum of tmrt for all timesteps + self.tmrt_s = np.zeros((self.rows, self.cols)) # Sum of tmrt for all timesteps # Loading DEm, DSM (and CDSM) rasters dataSet = gdal.Open(infolder + "/DSM.tif") @@ -223,9 +209,7 @@ class Treerasters: "d_tmrt", ) - def __init__( - self, treeshade, treeshade_rg, treeshade_bool, cdsm, treedata - ): + def __init__(self, treeshade, treeshade_rg, treeshade_bool, cdsm, treedata): # Find min and max rows and cols where there are shadows shy, shx = np.where((treeshade > 0) | (cdsm > 0)) shy_min = np.min(shy) @@ -239,9 +223,7 @@ def __init__( # Cropping to only where there is a shadow self.treeshade = treeshade[shy_min:shy_max, shx_min:shx_max] self.treeshade_rg = treeshade_rg[shy_min:shy_max, shx_min:shx_max] - self.treeshade_bool = ( - 1 - treeshade_bool[shy_min:shy_max, shx_min:shx_max, :] - ) + self.treeshade_bool = 1 - treeshade_bool[shy_min:shy_max, shx_min:shx_max, :] self.cdsm = cdsm[shy_min:shy_max, shx_min:shx_max] # y, x = np.where(cdsm_clip == treedata.height) # Position of tree in clipped shadow image self.buffer_y = np.zeros((2)) @@ -260,12 +242,8 @@ def __init__( a = np.array((self.tpy, self.tpx)) b = np.zeros((4, 2)) b[0, :] = np.array((0, 0)) # Upper left corner - b[1, :] = np.array( - (0, self.treeshade.shape[0] - 1) - ) # Lower left corner - b[2, :] = np.array( - (self.treeshade.shape[1] - 1, 0) - ) # Upper right corner + b[1, :] = np.array((0, self.treeshade.shape[0] - 1)) # Lower left corner + b[2, :] = np.array((self.treeshade.shape[1] - 1, 0)) # Upper right corner b[3, :] = np.array( (self.treeshade.shape[0] - 1, self.treeshade.shape[1] - 1) ) # Lower right corner @@ -335,9 +313,7 @@ def __init__(self, range_, shadow_, shadow_ts, tmrt): shade_max = np.max(shade_u) # Maximum value of unique values for i in range(1, shade_u.shape[0]): # Loop over all unique values - shade_b = ( - shadow_ == shade_u[i] - ) # Boolean shadow for each timestep i + shade_b = shadow_ == shade_u[i] # Boolean shadow for each timestep i shade_r = label(shade_b) # Create regional groups shade_r_u = np.unique( shade_r[0] diff --git a/functions/TreePlanter/TreePlanter/TreePlanterHillClimber.py b/functions/TreePlanter/TreePlanter/TreePlanterHillClimber.py index 3e7de79..13bd2f1 100644 --- a/functions/TreePlanter/TreePlanter/TreePlanterHillClimber.py +++ b/functions/TreePlanter/TreePlanter/TreePlanterHillClimber.py @@ -53,9 +53,7 @@ def treeoptinit( # sa = 1 # Genetic x or y random # sa = 2 # Genetic parents - percentage_progress = np.array( - [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9] - ) + percentage_progress = np.array([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]) iters_progress = np.int_(percentage_progress * r_iters) progress_counter = itertools.cycle(range(percentage_progress.shape[0])) progress = progress_counter.__next__() @@ -240,9 +238,7 @@ def treeoptinit( if i_tmrt[counter] > i_tmrt_max: i_tmrt_max = np.max(i_tmrt) for idx in range(trees): - d_tmrt_p[idx] = treerasters.d_tmrt[ - tree_pos_y[idx], tree_pos_x[idx] - ] + d_tmrt_p[idx] = treerasters.d_tmrt[tree_pos_y[idx], tree_pos_x[idx]] else: d_tmrt_temp = np.zeros((trees)) for idx in range(trees): diff --git a/functions/TreePlanter/TreePlanter/TreePlanterPrepare.py b/functions/TreePlanter/TreePlanter/TreePlanterPrepare.py index 7153648..f445933 100644 --- a/functions/TreePlanter/TreePlanter/TreePlanterPrepare.py +++ b/functions/TreePlanter/TreePlanter/TreePlanterPrepare.py @@ -68,9 +68,7 @@ def treeplanter(treeinput, treedata, treerasters, tmrt_1d): # Gör samma som i TreePlanterOptimizer (regional groups, etc) for j in range(tmrt_1d.__len__()): ts_temp2 = np.zeros((treeinput.rows, treeinput.cols)) - ts_temp2[yslice2, xslice2] = treerasters.treeshade_bool[ - yslice1, xslice1, j - ] + ts_temp2[yslice2, xslice2] = treerasters.treeshade_bool[yslice1, xslice1, j] sum_tmrt[res_y[i], res_x[i]] += np.sum( ts_temp2 * treeinput.buildings diff --git a/functions/TreePlanter/TreePlanter/TreePlanterTreeshade.py b/functions/TreePlanter/TreePlanter/TreePlanterTreeshade.py index c367db9..62057f7 100644 --- a/functions/TreePlanter/TreePlanter/TreePlanterTreeshade.py +++ b/functions/TreePlanter/TreePlanter/TreePlanterTreeshade.py @@ -20,9 +20,7 @@ def tsh_gen(y, x, treerasters, treeinput): y1, y2, x1, x2, treeinput, treerasters ) - tsh_pos_pad[yslice2, xslice2, i] = treerasters.treeshade_rg[ - yslice1, xslice1 - ] + tsh_pos_pad[yslice2, xslice2, i] = treerasters.treeshade_rg[yslice1, xslice1] tsh_pos_pad[:, :, i] = tsh_pos_pad[:, :, i] * treeinput.buildings_pad tsh_pos_bool_pad[:, :, i] = tsh_pos_pad[:, :, i] > 0 tsh_pos_bool_pad_all += tsh_pos_bool_pad[:, :, i] diff --git a/functions/TreePlanter/TreePlanter/adjustments.py b/functions/TreePlanter/TreePlanter/adjustments.py index 830e083..320c24f 100644 --- a/functions/TreePlanter/TreePlanter/adjustments.py +++ b/functions/TreePlanter/TreePlanter/adjustments.py @@ -118,16 +118,13 @@ def treenudge( tsh_bool_nc_t[:, :, j] * tmrt_1d[j, 0] ) # Tree shade tmrt_nc[iy, 1] += np.sum( - tsh_bool_nc_t[:, :, j] - * treeinput.tmrt_ts[:, :, j] + tsh_bool_nc_t[:, :, j] * treeinput.tmrt_ts[:, :, j] ) # Sunlit tmrt_nc[iy, 2] = ( tmrt_nc[iy, 1] - tmrt_nc[iy, 0] ) # New Tmrt (sunlit - tree shade) - if np.around( - np.max(tmrt_nc[:, 2]), decimals=1 - ) > np.around( + if np.around(np.max(tmrt_nc[:, 2]), decimals=1) > np.around( i_tmrt[counter], decimals=1 ): # If any new Tmrt decrease is higher, continue nc_max_r = np.where( @@ -154,9 +151,7 @@ def treenudge( t1[0, 3] = y_out[i] t1[0, 4] = x_out[i] - tp_nc[nc_i_r, 0] = ( - 0 # Reset tp_nc, i.e. trees have moved - ) + tp_nc[nc_i_r, 0] = 0 # Reset tp_nc, i.e. trees have moved tp_nc_a[nc_i_r] = ( 1 # Reset tp_nc_a, i.e. trees have been adjusted ) @@ -186,24 +181,16 @@ def tree_adjust(i_y, i_x, i_tmrt, counter, trees, treerasters, treeinput): y_max = tmrt_temp[:] != np.min( tmrt_temp[:] ) # Bool of positions of the other trees (highest) - y_te = i_y[ - counter, : - ] # Current y-positions of all trees for current run - x_te = i_x[ - counter, : - ] # Current x-positions of all trees for current run + y_te = i_y[counter, :] # Current y-positions of all trees for current run + x_te = i_x[counter, :] # Current x-positions of all trees for current run y_high = np.int_( y_te[y_max] ) # y-positions of trees with highest decrease in Tmrt x_high = np.int_( x_te[y_max] ) # x-positions of trees with highest decrease in Tmrt - y_low = np.int_( - y_te[y_min] - ) # y-position of tree with lowest decrease in Tmrt - x_low = np.int_( - x_te[y_min] - ) # x-position of tree with lowest decrease in Tmrt + y_low = np.int_(y_te[y_min]) # y-position of tree with lowest decrease in Tmrt + x_low = np.int_(x_te[y_min]) # x-position of tree with lowest decrease in Tmrt tsh_rg_temp, tsh_bool_temp, comp_ = tsh_gen_ts( y_high, x_high, treerasters, treeinput @@ -235,9 +222,7 @@ def tree_adjust(i_y, i_x, i_tmrt, counter, trees, treerasters, treeinput): (tsh_bool_all == 1) & (tsh_bool_min == 1) ): # If not, proceed. If they overlap, adjustment for overlap needs to be made, etc. - d_tmrt_vec = ( - d_tmrt_pad.flatten() - ) # Flatten d_tmrt_pad and create vector + d_tmrt_vec = d_tmrt_pad.flatten() # Flatten d_tmrt_pad and create vector d_tmrt_vec_s = -np.sort( -d_tmrt_vec @@ -252,9 +237,7 @@ def tree_adjust(i_y, i_x, i_tmrt, counter, trees, treerasters, treeinput): a_nc = 0 # Adjustment change parameter for ix in range(d_tmrt_vec_s.shape[0]): - tmrt_adjust = d_tmrt_vec_s[ - ix - ] # Trying values from d_tmrt_vec_s + tmrt_adjust = d_tmrt_vec_s[ix] # Trying values from d_tmrt_vec_s y_adjust, x_adjust = np.where( d_tmrt_pad == tmrt_adjust ) # Find coordinates for tmrt_adjust diff --git a/functions/URock/CalculatesIndicators.py b/functions/URock/CalculatesIndicators.py index 5b277d6..b4d1503 100644 --- a/functions/URock/CalculatesIndicators.py +++ b/functions/URock/CalculatesIndicators.py @@ -344,9 +344,7 @@ def zoneProperties(cursor, obstaclePropertiesTable, prefix=PREFIX_NAME): return zoneLengthTable -def studyAreaProperties( - cursor, upwindTable, stackedBlockTable, vegetationTable -): +def studyAreaProperties(cursor, upwindTable, stackedBlockTable, vegetationTable): """Calculates roughness height (z0) and displacement length (d) of the study area for a wind coming from North (thus you first need to rotate your obstacles to make them facing north if you @@ -394,16 +392,14 @@ def studyAreaProperties( print("Calculates study area properties") # Calculate the area of the study area - cursor.execute( - safe(""" + cursor.execute(safe(""" SELECT ST_AREA(ST_BUFFER(ST_EXTENT({0}), 15)) FROM (SELECT {0} FROM {1} UNION ALL SELECT {0} FROM {2}) AS STUDY_AREA_AREA_TAB - """).format(GEOM_FIELD, stackedBlockTable, vegetationTable) - ) # nosec B608 + """).format(GEOM_FIELD, stackedBlockTable, vegetationTable)) # nosec B608 area = cursor.fetchall()[0][0] # Calculates the obstacle (stacked blocks and vegetation) diff --git a/functions/URock/DataUtil.py b/functions/URock/DataUtil.py index 7f88333..5618157 100644 --- a/functions/URock/DataUtil.py +++ b/functions/URock/DataUtil.py @@ -13,9 +13,7 @@ import re -def decompressZip( - dirPath, inputFileName, outputFileBaseName=None, deleteZip=False -): +def decompressZip(dirPath, inputFileName, outputFileBaseName=None, deleteZip=False): """ Decompress zip file. @@ -57,9 +55,7 @@ def decompressZip( if err.errno != errno.EEXIST: raise continue - with open(target_path, "wb") as outfile, zfile.open( - member - ) as infile: + with open(target_path, "wb") as outfile, zfile.open(member) as infile: shutil.copyfileobj(infile, outfile) return None @@ -252,14 +248,11 @@ def windDirectionFromXY(windSpeedEast, windSpeedNorth): radAngle[windSpeedEast == 0] = 0 if type(windSpeedEast) == type(pd.Series()): radAngle[windSpeedEast != 0] = np.arctan( - windSpeedNorth[windSpeedEast != 0].divide( - windSpeedEast[windSpeedEast != 0] - ) + windSpeedNorth[windSpeedEast != 0].divide(windSpeedEast[windSpeedEast != 0]) ) else: radAngle[windSpeedEast != 0] = np.arctan( - windSpeedNorth[windSpeedEast != 0] - / windSpeedEast[windSpeedEast != 0] + windSpeedNorth[windSpeedEast != 0] / windSpeedEast[windSpeedEast != 0] ) # Add or subtract pi.2 for left side trigonometric circle vectors @@ -317,9 +310,7 @@ def getExtremumPoint( Return the table containing the expected extremum point for each polygon """ # Output base name - outputBaseName = safe("{0}_{1}_{2}_POINTS").format( - pointsTable, axis, extremum - ) + outputBaseName = safe("{0}_{1}_{2}_POINTS").format(pointsTable, axis, extremum) # Name of the output table extremumPointTable = prefix(outputBaseName, prefix=prefix_name) @@ -355,9 +346,7 @@ def getExtremumPoint( fieldName=ID_FIELD_STACKED_BLOCK, isSpatial=False, ), - createIndex( - tableName=pointsTable, fieldName=axis, isSpatial=False - ), + createIndex(tableName=pointsTable, fieldName=axis, isSpatial=False), createIndex( tableName=pointsTable, fieldName=extremumField, isSpatial=False ), @@ -409,9 +398,7 @@ def locate_py(): if path_pybin.exists(): return path_pybin else: - raise RuntimeError( - "UMEP cannot locate the Python interpreter used by QGIS!" - ) + raise RuntimeError("UMEP cannot locate the Python interpreter used by QGIS!") def validate_sql_inputs( @@ -449,9 +436,7 @@ def validate_srid(srid, name): # Validate file paths: ensure no single quotes (basic check) def validate_file_path(file_path, name): if file_path and "'" in file_path: - raise ValueError( - f"Invalid {name}: {file_path} (contains single quotes)" - ) + raise ValueError(f"Invalid {name}: {file_path} (contains single quotes)") validate_file_path(lines_file, "lines_file") validate_file_path(polygons_file, "polygons_file") diff --git a/functions/URock/H2gisConnection.py b/functions/URock/H2gisConnection.py index dfc8d6b..c066e72 100644 --- a/functions/URock/H2gisConnection.py +++ b/functions/URock/H2gisConnection.py @@ -74,18 +74,13 @@ def downloadH2gis(dbDirectory): # Get the zip file name and create the local file directory zipFileName = H2GIS_URL.split("/")[-1] localH2ZipDir = (dbDirectory + os.sep + zipFileName).encode("utf-8") - localH2JarDir = (dbDirectory + os.sep + H2GIS_UNZIPPED_NAME).encode( - "utf-8" - ) + localH2JarDir = (dbDirectory + os.sep + H2GIS_UNZIPPED_NAME).encode("utf-8") # Test whether the .jar already downloaded if os.path.exists(localH2ZipDir) or os.path.exists(localH2JarDir): print("H2GIS version %s already downloaded" % (H2GIS_VERSION)) else: - print( - "Downloading H2GIS version %s at %s..." - % (H2GIS_URL, H2GIS_VERSION) - ) + print("Downloading H2GIS version %s at %s..." % (H2GIS_URL, H2GIS_VERSION)) # Download the archive file and save it into the 'dbDirectory' http = urllib3.PoolManager() r = http.request("GET", url=H2GIS_URL, preload_content=False) @@ -382,9 +377,7 @@ def getJavaHome(os_type): ) output, err = proc.communicate() # Identify the string corresponding to the java_home in the resulting line - javaPath = os.path.abspath( - str(output).split("java.home = ")[1].split("\\n")[0] - ) + javaPath = os.path.abspath(str(output).split("java.home = ")[1].split("\\n")[0]) else: import winreg diff --git a/functions/URock/InitWindField.py b/functions/URock/InitWindField.py index 3d5fb1c..fa1429d 100644 --- a/functions/URock/InitWindField.py +++ b/functions/URock/InitWindField.py @@ -168,9 +168,7 @@ def createGrid( # Gather all tables in one gatherQuery = [ - safe("""SELECT {0} AS {0} FROM {1}""").format( - GEOM_FIELD, dicOfInputTables[t] - ) + safe("""SELECT {0} AS {0} FROM {1}""").format(GEOM_FIELD, dicOfInputTables[t]) for t in dicOfInputTables.keys() ] @@ -233,10 +231,8 @@ def affectsPointToBuildZone( verticalLineTable: String Vertical lines (not along z but along the grid "north-south") useful for future calculations""" - print( - """Affects each grid point to a building Rockle zone and calculates needed - variables for 3D wind speed""" - ) + print("""Affects each grid point to a building Rockle zone and calculates needed + variables for 3D wind speed""") # Name of the output tables dicOfOutputTables = { @@ -246,9 +242,7 @@ def affectsPointToBuildZone( ) for t in dicOfBuildRockleZoneTable } - verticalLineTable = DataUtil.prefix( - tableName="VERTICAL_LINES", prefix=prefix - ) + verticalLineTable = DataUtil.prefix(tableName="VERTICAL_LINES", prefix=prefix) # Temporary tables (and prefix for temporary tables) tempoCavity = DataUtil.postfix("TEMPO_CAVITY") @@ -308,10 +302,8 @@ def affectsPointToBuildZone( ID_POINT, queryKeepY, idZone[t], HEIGHT_FIELD ) if t == STREET_CANYON_NAME: - columnsToKeepQuery = safe( - """b.{0}, {1} a.{2}, a.{3}, a.{4}, a.{5}, a.{6} - """ - ).format( + columnsToKeepQuery = safe("""b.{0}, {1} a.{2}, a.{3}, a.{4}, a.{5}, a.{6} + """).format( ID_POINT, queryKeepY, idZone[t], @@ -394,9 +386,7 @@ def affectsPointToBuildZone( DataUtil.createIndex( tableName=gridTable, fieldName=ID_POINT_Y, isSpatial=False ), - DataUtil.createIndex( - tableName=gridTable, fieldName=GEOM_FIELD, isSpatial=True - ), + DataUtil.createIndex(tableName=gridTable, fieldName=GEOM_FIELD, isSpatial=True), X, ) @@ -1031,10 +1021,8 @@ def affectsPointToVegZone( Dictionary having as key the type of vegetation Rockle zone and as value the name of the table containing points corresponding to the vegetation zone """ - print( - """Affects each grid point to a vegetation Rockle zone and calculates - needed variables for 3D wind speed""" - ) + print("""Affects each grid point to a vegetation Rockle zone and calculates + needed variables for 3D wind speed""") # Name of the output tables dicOfOutputTables = { @@ -1109,9 +1097,7 @@ def affectsPointToVegZone( return dicOfOutputTables -def removeBuildZonePoints( - cursor, dicOfInitBuildZoneGridPoint, prefix=PREFIX_NAME -): +def removeBuildZonePoints(cursor, dicOfInitBuildZoneGridPoint, prefix=PREFIX_NAME): """Remove some of the Röckle zone points when there are specific zone overlapping. Currently, two major deletions are implemented: 1. downwind building zone deletion: if a part of a building is entirely @@ -1276,9 +1262,7 @@ def removeBuildZonePoints( fieldName=ID_POINT, isSpatial=False, ), - DataUtil.createIndex( - cavityFirstPoint, fieldName=ID_POINT, isSpatial=False - ), + DataUtil.createIndex(cavityFirstPoint, fieldName=ID_POINT, isSpatial=False), DataUtil.createIndex( cavityFirstPoint, fieldName=ID_FIELD_STACKED_BLOCK, @@ -1668,9 +1652,7 @@ def removeBuildZonePoints( safe(""" DROP TABLE IF EXISTS {0}; ALTER TABLE {1} RENAME TO {0} - """).format( - dicOfBuildZoneGridPoint[t], dicOfInitBuildZoneGridPoint[t] - ) + """).format(dicOfBuildZoneGridPoint[t], dicOfInitBuildZoneGridPoint[t]) for t in nonModifiedTables ] ) @@ -2110,29 +2092,21 @@ def calculates3dBuildWindFactor( # Identify the maximum height where wind speed may be affected by building obstacles maxHeightQuery = { - DISPLACEMENT_NAME: "MAX({0}) AS MAX_HEIGHT".format( - UPPER_VERTICAL_THRESHOLD - ), + DISPLACEMENT_NAME: "MAX({0}) AS MAX_HEIGHT".format(UPPER_VERTICAL_THRESHOLD), DISPLACEMENT_VORTEX_NAME: "MAX({0}) AS MAX_HEIGHT".format( UPPER_VERTICAL_THRESHOLD ), CAVITY_NAME: "MAX({0}) AS MAX_HEIGHT".format(UPPER_VERTICAL_THRESHOLD), WAKE_NAME: "MAX({0}) AS MAX_HEIGHT".format(UPPER_VERTICAL_THRESHOLD), - STREET_CANYON_NAME: "MAX({0}) AS MAX_HEIGHT".format( - UPPER_VERTICAL_THRESHOLD - ), + STREET_CANYON_NAME: "MAX({0}) AS MAX_HEIGHT".format(UPPER_VERTICAL_THRESHOLD), ROOFTOP_PERP_NAME: "MAX({0}+{1}) AS MAX_HEIGHT".format( ROOFTOP_PERP_VAR_HEIGHT, HEIGHT_FIELD ), ROOFTOP_CORN_NAME: "MAX({0}+{1}) AS MAX_HEIGHT".format( ROOFTOP_CORNER_VAR_HEIGHT, HEIGHT_FIELD ), - CAVITY_BACKWARD_NAME: "MAX({0}) AS MAX_HEIGHT".format( - UPPER_VERTICAL_THRESHOLD - ), - WAKE_BACKWARD_NAME: "MAX({0}) AS MAX_HEIGHT".format( - UPPER_VERTICAL_THRESHOLD - ), + CAVITY_BACKWARD_NAME: "MAX({0}) AS MAX_HEIGHT".format(UPPER_VERTICAL_THRESHOLD), + WAKE_BACKWARD_NAME: "MAX({0}) AS MAX_HEIGHT".format(UPPER_VERTICAL_THRESHOLD), } cursor.execute( safe(""" SELECT MAX(MAX_HEIGHT) AS MAX_HEIGHT @@ -2347,9 +2321,7 @@ def calculates3dBuildWindFactor( # Defines the WHERE clause (on z-axis values) for each point of each zone whereQuery = { DISPLACEMENT_NAME: "b.{0} < a.{1}".format(Z, UPPER_VERTICAL_THRESHOLD), - DISPLACEMENT_VORTEX_NAME: "b.{0} < a.{1}".format( - Z, UPPER_VERTICAL_THRESHOLD - ), + DISPLACEMENT_VORTEX_NAME: "b.{0} < a.{1}".format(Z, UPPER_VERTICAL_THRESHOLD), CAVITY_NAME: "b.{0} < a.{1}".format(Z, UPPER_VERTICAL_THRESHOLD), WAKE_NAME: "b.{0} < a.{1} AND b.{0} >= a.{2}".format( Z, @@ -2368,9 +2340,7 @@ def calculates3dBuildWindFactor( AND b.{0} > a.{1}""".format( Z, HEIGHT_FIELD, ROOFTOP_CORNER_VAR_HEIGHT ), - CAVITY_BACKWARD_NAME: "b.{0} < a.{1}".format( - Z, UPPER_VERTICAL_THRESHOLD - ), + CAVITY_BACKWARD_NAME: "b.{0} < a.{1}".format(Z, UPPER_VERTICAL_THRESHOLD), WAKE_BACKWARD_NAME: "b.{0} < a.{1} AND b.{0} >= a.{2}".format( Z, UPPER_VERTICAL_THRESHOLD, @@ -2464,9 +2434,7 @@ def calculates3dVegWindFactor( outputBaseName = "VEGETATION_WEIGHTING_FACTORS" # Name of the output table - vegetationWeightFactorTable = DataUtil.prefix( - outputBaseName, prefix=prefix - ) + vegetationWeightFactorTable = DataUtil.prefix(outputBaseName, prefix=prefix) # Temporary tables (and prefix for temporary tables) zValueTable = DataUtil.postfix("Z_VALUES") @@ -2485,15 +2453,11 @@ def calculates3dVegWindFactor( dz, ) ] - cursor.execute( - safe(""" + cursor.execute(safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0}({2} BIGINT AUTO_INCREMENT, {3} DOUBLE); INSERT INTO {0} VALUES (DEFAULT, {1}) - """).format( - zValueTable, "), (DEFAULT, ".join(listOfZ), ID_POINT_Z, Z - ) - ) + """).format(zValueTable, "), (DEFAULT, ".join(listOfZ), ID_POINT_Z, Z)) # Calculation of the wind speed depending on vegetation location (open or building zone) # d is actually calculated by Equation 18a from Hanna and Britter (2002) @@ -2734,9 +2698,7 @@ def manageSuperimposition( # Temporary tables (and prefix for temporary tables) tempoPrioritiesAll = DataUtil.postfix("TEMPO_PRIORITY_ALL") tempoPrioritiesWeighted = DataUtil.postfix("TEMPO_PRIORITY_WEIGHTED") - tempoPrioritiesWeightedAll = DataUtil.postfix( - "TEMPO_PRIORITY_WEIGHTED_ALL" - ) + tempoPrioritiesWeightedAll = DataUtil.postfix("TEMPO_PRIORITY_WEIGHTED_ALL") tempoBackwardWeights = DataUtil.postfix("TEMPO_BACWARD_WEIGHTS") dicBackwardWeighted = { t: DataUtil.postfix(DataUtil.prefix(t, prefix="TEMPO_WEIGHTED")) @@ -2745,9 +2707,7 @@ def manageSuperimposition( tempoPrioritiesWeightedAllPlusBack = DataUtil.postfix( "TEMPO_PRIORITY_WEIGHTED_ALL_PLUS_BACK" ) - tempoUpstreamAndDownstream = DataUtil.postfix( - "TEMPO_UPSTREAM_AND_DOWNSTREAM" - ) + tempoUpstreamAndDownstream = DataUtil.postfix("TEMPO_UPSTREAM_AND_DOWNSTREAM") # Give feedback to user if feedback: @@ -3131,9 +3091,7 @@ def manageUpstreamSuperimposition( cursor=cursor, dicAllWeightFactorsTables=dicAllWeightFactorsTables, tablesToConsider=upstreamPriorityTables.reindex( - upstreamPriorityTables.index.difference( - pd.Index(upstreamWeightingTables) - ) + upstreamPriorityTables.index.difference(pd.Index(upstreamWeightingTables)) ), prefix="TEMPO_PRIORITIES", upstream=upstreamPriorities, @@ -3437,9 +3395,7 @@ def identifyUpstreamer( selectQueryDownstream[t] = """ SELECT CAST((row_number() over()) as Integer) AS {0}, {1}, {2}, {3}, {4}, - """.format( - ID_3D_POINT, ID_POINT, ID_POINT_Z, HEIGHT_FIELD, Y_WALL - ) + """.format(ID_3D_POINT, ID_POINT, ID_POINT_Z, HEIGHT_FIELD, Y_WALL) # If priorities should be used, add columns to keep if type(tablesToConsider) == type(pd.DataFrame()): @@ -3474,9 +3430,7 @@ def identifyUpstreamer( wind_factor_names[i] ) selectQueryDownstream[t] = ( - selectQueryDownstream[t][0:-2] - + " FROM " - + dicAllWeightFactorsTables[t] + selectQueryDownstream[t][0:-2] + " FROM " + dicAllWeightFactorsTables[t] ) # Gather all data for the upstream weighting into a same table @@ -3684,10 +3638,7 @@ def getVerticalProfile( verticalProfileFile = kwargs.get("verticalProfileFile", None) if profileType == "power": verticalWindProfile = pd.Series( - [ - V_ref * (z / z_ref) ** (0.12 * z0 + 0.18) - for z in pointHeightList - ], + [V_ref * (z / z_ref) ** (0.12 * z0 + 0.18) for z in pointHeightList], index=pointHeightList, ) elif profileType == "urban": @@ -3697,10 +3648,7 @@ def getVerticalProfile( pointHeighAbove = pointHeightIndex[pointHeightIndex >= H] speedAtCanopyHeight = V_ref * np.log((H - d) / z0) / np.log(z_ref / z0) verticalProfileWithin = pd.Series( - [ - speedAtCanopyHeight * np.exp(A * (z / H - 1)) - for z in pointHeighCanopy - ], + [speedAtCanopyHeight * np.exp(A * (z / H - 1)) for z in pointHeighCanopy], index=pointHeighCanopy, ) verticalProfileAbove = pd.Series( @@ -3844,12 +3792,8 @@ def setInitialWindField( # Temporary tables (and prefix for temporary tables) tempoVerticalProfileTable = DataUtil.postfix("TEMPO_VERTICAL_PROFILE_WIND") - tempoBuildingHeightWindTable = DataUtil.postfix( - "TEMPO_BUILDING_HEIGHT_WIND" - ) - tempoZoneWindSpeedFactorTable = DataUtil.postfix( - "TEMPO_ZONE_WIND_SPEED_FACTOR" - ) + tempoBuildingHeightWindTable = DataUtil.postfix("TEMPO_BUILDING_HEIGHT_WIND") + tempoZoneWindSpeedFactorTable = DataUtil.postfix("TEMPO_ZONE_WIND_SPEED_FACTOR") # Set a list of the level height and get their horizontal wind speed levelHeightList = [ @@ -3898,9 +3842,7 @@ def setInitialWindField( """).format(HEIGHT_FIELD, initializedWindFactorTable)) buildingHeightList = cursor.fetchall() if len(buildingHeightList) > 0: - df_buildingHeightList = pd.Series( - pd.DataFrame(buildingHeightList)[0].values - ) + df_buildingHeightList = pd.Series(pd.DataFrame(buildingHeightList)[0].values) buildingHeightWindSpeed = getVerticalProfile( cursor=cursor, pointHeightList=df_buildingHeightList, @@ -3917,9 +3859,7 @@ def setInitialWindField( # ... and insert it into a table valuesForEachRowBuilding = [ str(i) + "," + str(j) - for i, j in buildingHeightWindSpeed.set_index(Z)[ - HORIZ_WIND_SPEED - ].items() + for i, j in buildingHeightWindSpeed.set_index(Z)[HORIZ_WIND_SPEED].items() ] cursor.execute( safe(""" @@ -3943,9 +3883,7 @@ def setInitialWindField( V_ref = verticalWindSpeedProfile.loc[ verticalWindSpeedProfile.index[-1], HORIZ_WIND_SPEED ] - z_ref = verticalWindSpeedProfile.loc[ - verticalWindSpeedProfile.index[-1], Z - ] + z_ref = verticalWindSpeedProfile.loc[verticalWindSpeedProfile.index[-1], Z] # Calculates the initial wind speed field according to each point rule # and join to the table x and y coordinates @@ -4100,11 +4038,7 @@ def setInitialWindField( df_wind0.loc[idx[:, :, z_i], :] = ( df_wind0.loc[idx[:, :, z_i], :] * verticalWindSpeedProfile.loc[z_i, HORIZ_WIND_SPEED] - / df_wind0.loc[idx[:, :, z_i], :] - .pow(2) - .sum(axis=1) - .pow(0.5) - .mean() + / df_wind0.loc[idx[:, :, z_i], :].pow(2).sum(axis=1).pow(0.5).mean() ) # Set to 0 wind speed within buildings... @@ -4308,19 +4242,9 @@ def identifyBuildPoints( ind2remove = ( df_wall_left.intersection(df_wall_right) .intersection(df_wall_behind) - .union( - df_wall_left.intersection(df_wall_right).intersection(df_wall_face) - ) - .union( - df_wall_left.intersection(df_wall_behind).intersection( - df_wall_face - ) - ) - .union( - df_wall_right.intersection(df_wall_behind).intersection( - df_wall_face - ) - ) + .union(df_wall_left.intersection(df_wall_right).intersection(df_wall_face)) + .union(df_wall_left.intersection(df_wall_behind).intersection(df_wall_face)) + .union(df_wall_right.intersection(df_wall_behind).intersection(df_wall_face)) .difference(df_gridBuil.index) ) df_gridBuil.index = df_gridBuil.index.append(ind2remove) diff --git a/functions/URock/MainCalculation.py b/functions/URock/MainCalculation.py index 95f6547..e71ff20 100644 --- a/functions/URock/MainCalculation.py +++ b/functions/URock/MainCalculation.py @@ -156,20 +156,14 @@ def main( outputDataRel["point3D_BuildZone"] = os.path.join( tmp_dir_unique, "point3D_BuildZone" ) - outputDataRel["point3D_VegZone"] = os.path.join( - tmp_dir_unique, "point3D_VegZone" - ) + outputDataRel["point3D_VegZone"] = os.path.join(tmp_dir_unique, "point3D_VegZone") outputDataRel["point3D_All"] = os.path.join(tmp_dir_unique, "point3D_All") # Put 2D grid points in the output directory - outputDataRel["point_2DRockleZone"] = os.path.join( - outputFilePath, "Rockle_zones" - ) + outputDataRel["point_2DRockleZone"] = os.path.join(outputFilePath, "Rockle_zones") # Convert relative to absolute paths - outputDataAbs = { - i: os.path.abspath(outputDataRel[i]) for i in outputDataRel - } + outputDataAbs = {i: os.path.abspath(outputDataRel[i]) for i in outputDataRel} ############################################################################ ################################ SCRIPT #################################### @@ -218,9 +212,7 @@ def main( # 2. CREATES OBSTACLE GEOMETRIES ---------------------------------------------------- # ----------------------------------------------------------------------------------- if feedback: - feedback.setProgressText( - "Creates the stacked blocks used as obstacles" - ) + feedback.setProgressText("Creates the stacked blocks used as obstacles") if feedback.isCanceled(): cursor.close() feedback.setProgressText("Calculation cancelled by user") @@ -389,13 +381,11 @@ def main( # zonePropertiesTable = zonePropertiesTable, # srid = srid, # prefix = prefix) - displacementZonesTable, displacementVortexZonesTable = ( - Zones.displacementZones2( - cursor=cursor, - upwindWithPropTable=upwindTable, - srid=srid, - prefix=prefix, - ) + displacementZonesTable, displacementVortexZonesTable = Zones.displacementZones2( + cursor=cursor, + upwindWithPropTable=upwindTable, + srid=srid, + prefix=prefix, ) # # Creates the displacement zone (upwind) # displacementZonesTable = \ @@ -601,9 +591,7 @@ def main( # Creates the grid of points gridPoint = InitWindField.createGrid( cursor=cursor, - dicOfInputTables=dict( - dicOfBuildRockleZoneTable, **dicOfVegRockleZoneTable - ), + dicOfInputTables=dict(dicOfBuildRockleZoneTable, **dicOfVegRockleZoneTable), srid=srid, alongWindZoneExtend=alongWindZoneExtend, crossWindZoneExtend=crossWindZoneExtend, @@ -637,18 +625,16 @@ def main( ) # Manage backward cavity and wake zones in the leeward zone of tall buildings - dicOfBuildZoneGridPoint, facadeWithinCavity = ( - InitWindField.manageBackwardZones( - cursor=cursor, - dicOfBuildZoneGridPoint=dicOfBuildZoneGridPoint, - cavity2dInitPoints=dicOfInitBuildZoneGridPoint[CAVITY_NAME], - wake2dInitPoints=dicOfInitBuildZoneGridPoint[WAKE_NAME], - streetCanyonTable=streetCanyonTable, - gridTable=gridPoint, - meshSize=meshSize, - dz=dz, - prefix=prefix, - ) + dicOfBuildZoneGridPoint, facadeWithinCavity = InitWindField.manageBackwardZones( + cursor=cursor, + dicOfBuildZoneGridPoint=dicOfBuildZoneGridPoint, + cavity2dInitPoints=dicOfInitBuildZoneGridPoint[CAVITY_NAME], + wake2dInitPoints=dicOfInitBuildZoneGridPoint[WAKE_NAME], + streetCanyonTable=streetCanyonTable, + gridTable=gridPoint, + meshSize=meshSize, + dz=dz, + prefix=prefix, ) # ----------------------------------------------------------------------------------- @@ -766,9 +752,7 @@ def main( # ---------------------------------------------------------------- # Calculates the final weighting factor for each point, dealing with duplicates (superimposition) dicAllWeightFactorsTables = dicOfBuildZone3DWindFactor.copy() - dicAllWeightFactorsTables[ALL_VEGETATION_NAME] = ( - vegetationWeightFactorTable - ) + dicAllWeightFactorsTables[ALL_VEGETATION_NAME] = vegetationWeightFactorTable allZonesPointFactor = InitWindField.manageSuperimposition( cursor=cursor, dicAllWeightFactorsTables=dicAllWeightFactorsTables, @@ -867,9 +851,7 @@ def main( nx, ny, nz = nPoints.values() df_gridBuil = df_gridBuil.reindex( df_gridBuil.index.append( - pd.MultiIndex.from_product( - [range(1, nx - 1), range(1, ny - 1), [0]] - ) + pd.MultiIndex.from_product([range(1, nx - 1), range(1, ny - 1), [0]]) ) ) @@ -883,15 +865,9 @@ def main( buildGrid3D = np.array( [buildGrid3D.xs(i, level=0).unstack().values for i in range(0, nx)] ) - u0 = np.array( - [df_wind0[U].xs(i, level=0).unstack().values for i in range(0, nx)] - ) - v0 = -np.array( - [df_wind0[V].xs(i, level=0).unstack().values for i in range(0, nx)] - ) - w0 = np.array( - [df_wind0[W].xs(i, level=0).unstack().values for i in range(0, nx)] - ) + u0 = np.array([df_wind0[U].xs(i, level=0).unstack().values for i in range(0, nx)]) + v0 = -np.array([df_wind0[V].xs(i, level=0).unstack().values for i in range(0, nx)]) + w0 = np.array([df_wind0[W].xs(i, level=0).unstack().values for i in range(0, nx)]) # Identify all cells needing to be updated by the wind solver and store # their coordinates in a 1D array @@ -915,25 +891,19 @@ def main( w0[:, :, 1:nz] = (w0[:, :, 0 : nz - 1] + w0[:, :, 1:nz]) / 2 # Reset input and output wind speed to zero for building cells - u0[ - buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] - ] = 0 + u0[buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2]] = 0 u0[ buildingCoordinates[0] + 1, buildingCoordinates[1], buildingCoordinates[2], ] = 0 - v0[ - buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] - ] = 0 + v0[buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2]] = 0 v0[ buildingCoordinates[0], buildingCoordinates[1] + 1, buildingCoordinates[2], ] = 0 - w0[ - buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] - ] = 0 + w0[buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2]] = 0 w0[ buildingCoordinates[0], buildingCoordinates[1], @@ -1004,37 +974,22 @@ def main( w[0 : nx - 1, 0 : ny - 1, 0 : nz - 1] + w[0 : nx - 1, 0 : ny - 1, 1:nz] ) / 2 u0[0 : nx - 1, 0 : ny - 1, 0 : nz - 1] = ( - u0[0 : nx - 1, 0 : ny - 1, 0 : nz - 1] - + u0[1:nx, 0 : ny - 1, 0 : nz - 1] + u0[0 : nx - 1, 0 : ny - 1, 0 : nz - 1] + u0[1:nx, 0 : ny - 1, 0 : nz - 1] ) / 2 v0[0 : nx - 1, 0 : ny - 1, 0 : nz - 1] = ( - v0[0 : nx - 1, 0 : ny - 1, 0 : nz - 1] - + v0[0 : nx - 1, 1:ny, 0 : nz - 1] + v0[0 : nx - 1, 0 : ny - 1, 0 : nz - 1] + v0[0 : nx - 1, 1:ny, 0 : nz - 1] ) / 2 w0[0 : nx - 1, 0 : ny - 1, 0 : nz - 1] = ( - w0[0 : nx - 1, 0 : ny - 1, 0 : nz - 1] - + w0[0 : nx - 1, 0 : ny - 1, 1:nz] + w0[0 : nx - 1, 0 : ny - 1, 0 : nz - 1] + w0[0 : nx - 1, 0 : ny - 1, 1:nz] ) / 2 # Reset input and output wind speed to zero for building cells - u[ - buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] - ] = 0 - v[ - buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] - ] = 0 - w[ - buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] - ] = 0 - u0[ - buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] - ] = 0 - v0[ - buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] - ] = 0 - w0[ - buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] - ] = 0 + u[buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2]] = 0 + v[buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2]] = 0 + w[buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2]] = 0 + u0[buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2]] = 0 + v0[buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2]] = 0 + w0[buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2]] = 0 # ------------------------------------------------------------------- # 11. ROTATE THE WIND FIELD TO THE INITIAL DISPOSITION -------------- @@ -1219,11 +1174,7 @@ def rotateData(theta, nx, ny, nz, x, y, x_rot, y_rot, u, v): x_rot[i, j] = (xmax - x[i]) * rot[0, 0] + (ymax - y[j]) * rot[0, 1] y_rot[i, j] = (xmax - x[i]) * rot[1, 0] + (ymax - y[j]) * rot[1, 1] for k in range(nz): - u_rot[i, j, k] = ( - u[i, j, k] * rot[0, 0] + v[i, j, k] * rot[0, 1] - ) - v_rot[i, j, k] = ( - u[i, j, k] * rot[1, 0] + v[i, j, k] * rot[1, 1] - ) + u_rot[i, j, k] = u[i, j, k] * rot[0, 0] + v[i, j, k] * rot[0, 1] + v_rot[i, j, k] = u[i, j, k] * rot[1, 0] + v[i, j, k] * rot[1, 1] return x_rot, y_rot, u_rot, v_rot diff --git a/functions/URock/Obstacles.py b/functions/URock/Obstacles.py index 613d072..7e25ceb 100644 --- a/functions/URock/Obstacles.py +++ b/functions/URock/Obstacles.py @@ -205,9 +205,7 @@ def createsBlocks( """).format(correlTable, ID_FIELD_BLOCK, HEIGHT_FIELD)) listOfHeight = cursor.fetchall() if len(listOfHeight) > 0: - df_listOfHeight = ( - pd.DataFrame(listOfHeight).dropna()[0].astype(int).values - ) + df_listOfHeight = pd.DataFrame(listOfHeight).dropna()[0].astype(int).values # Create stacked blocks according to building blocks and height listOfSqlQueries = [ # nosec B608 @@ -259,9 +257,7 @@ def createsBlocks( if not DEBUG: # Drop intermediate tables - cursor.execute( - "DROP TABLE IF EXISTS {0}".format(",".join([correlTable])) - ) + cursor.execute("DROP TABLE IF EXISTS {0}".format(",".join([correlTable]))) return blockTable, stackedBlockTable @@ -296,9 +292,7 @@ def identifyBlockAndCavityBase(cursor, stackedBlockTable, prefix=PREFIX_NAME): tempoAllCavityStacked = DataUtil.postfix("tempo_all_cavity_stacked_table") # Creates final table - stackedBlockPropTable = DataUtil.prefix( - "stacked_block_prop_table", prefix=prefix - ) + stackedBlockPropTable = DataUtil.prefix("stacked_block_prop_table", prefix=prefix) # Identify each block base height and ratio of area between the stacked and its base block cursor.execute( @@ -697,9 +691,7 @@ def updateUpwindFacadeBase(cursor, upwindTable, prefix=PREFIX_NAME): if not DEBUG: # Drop intermediate tables - cursor.execute( - "DROP TABLE IF EXISTS {0}".format(",".join([tempoUpwind])) - ) + cursor.execute("DROP TABLE IF EXISTS {0}".format(",".join([tempoUpwind]))) return updatedUpwindBaseTable diff --git a/functions/URock/WindSolver.py b/functions/URock/WindSolver.py index 26beb6f..4e1f91c 100644 --- a/functions/URock/WindSolver.py +++ b/functions/URock/WindSolver.py @@ -396,11 +396,7 @@ def solver( if eps < thresholdIterations: break else: - print( - " eps = {0} >= {1}".format( - np.round(eps, 6), thresholdIterations - ) - ) + print(" eps = {0} >= {1}".format(np.round(eps, 6), thresholdIterations)) # Feedback to QGIS every 50 iterations if (N % 50 == 0) & (feedback is not None): textToSend = """Iteration {0} (max {1}) - eps = {2} >= {3} @@ -461,25 +457,19 @@ def solver( ) # Reset input and output wind speed to zero for building cells - u[ - buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] - ] = 0 + u[buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2]] = 0 u[ buildingCoordinates[0] + 1, buildingCoordinates[1], buildingCoordinates[2], ] = 0 - v[ - buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] - ] = 0 + v[buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2]] = 0 v[ buildingCoordinates[0], buildingCoordinates[1] + 1, buildingCoordinates[2], ] = 0 - w[ - buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2] - ] = 0 + w[buildingCoordinates[0], buildingCoordinates[1], buildingCoordinates[2]] = 0 w[ buildingCoordinates[0], buildingCoordinates[1], diff --git a/functions/URock/WriteMetadataURock.py b/functions/URock/WriteMetadataURock.py index 4abad44..7e23d91 100644 --- a/functions/URock/WriteMetadataURock.py +++ b/functions/URock/WriteMetadataURock.py @@ -49,14 +49,10 @@ def writeRunInfo( if veg_file is not None: file.write("Vegetation layer: " + veg_file) file.write("\n") - file.write( - "Vegetation top height (attribute name): " + topHeightVeg - ) + file.write("Vegetation top height (attribute name): " + topHeightVeg) file.write("\n") if baseHeightVeg is not None: - file.write( - "Vegetation base height (attribute name): " + baseHeightVeg - ) + file.write("Vegetation base height (attribute name): " + baseHeightVeg) else: file.write( "Vegetation base height (fraction of top height): " @@ -65,8 +61,7 @@ def writeRunInfo( file.write("\n") if attenuationVeg is not None: file.write( - "Attenuation though vegetation (attribute name): " - + attenuationVeg + "Attenuation though vegetation (attribute name): " + attenuationVeg ) else: file.write( @@ -89,9 +84,7 @@ def writeRunInfo( else: file.write("Reference height for wind (m): " + str(z_ref)) file.write("\n") - file.write( - "Reference wind speed at ref height (m/s): " + str(v_ref) - ) + file.write("Reference wind speed at ref height (m/s): " + str(v_ref)) file.write("\n") file.write("Wind direction (° from North): " + str(windDirection)) file.write("\n") diff --git a/functions/URock/Zones.py b/functions/URock/Zones.py index 1d28600..c1dea46 100644 --- a/functions/URock/Zones.py +++ b/functions/URock/Zones.py @@ -56,9 +56,7 @@ def displacementZones2(cursor, upwindWithPropTable, srid, prefix=PREFIX_NAME): # Output base names outputZoneTableNames = { - DISPLACEMENT_NAME: DataUtil.prefix( - "DISPLACEMENT_ZONES", prefix=prefix - ), + DISPLACEMENT_NAME: DataUtil.prefix("DISPLACEMENT_ZONES", prefix=prefix), DISPLACEMENT_VORTEX_NAME: DataUtil.prefix( "DISPLACEMENT_VORTEX_ZONES", prefix=prefix ), @@ -67,17 +65,13 @@ def displacementZones2(cursor, upwindWithPropTable, srid, prefix=PREFIX_NAME): # Create temporary table names (for tables that will be removed at the end of the process) densifiedLinePoints = DataUtil.postfix("DENSIFIED_LINE_POINTS") ZonePoints = { - DISPLACEMENT_NAME: ( - DISPLACEMENT_NAME + DataUtil.postfix("_ZONE_POINTS") - ), + DISPLACEMENT_NAME: (DISPLACEMENT_NAME + DataUtil.postfix("_ZONE_POINTS")), DISPLACEMENT_VORTEX_NAME: ( DISPLACEMENT_VORTEX_NAME + DataUtil.postfix("_ZONE_POINTS") ), } ZonePolygons = { - DISPLACEMENT_NAME: ( - DISPLACEMENT_NAME + DataUtil.postfix("_ZONE_POLYGONS") - ), + DISPLACEMENT_NAME: (DISPLACEMENT_NAME + DataUtil.postfix("_ZONE_POLYGONS")), DISPLACEMENT_VORTEX_NAME: ( DISPLACEMENT_VORTEX_NAME + DataUtil.postfix("_ZONE_POLYGONS") ), @@ -166,8 +160,7 @@ def displacementZones2(cursor, upwindWithPropTable, srid, prefix=PREFIX_NAME): } # Create the zone from the half ellipse and the densified line and then join missing columns - cursor.execute( - safe(";").join([f""" + cursor.execute(safe(";").join([f""" {DataUtil.createIndex(tableName=ZonePoints[z], fieldName=UPWIND_FACADE_FIELD, isSpatial=False)} @@ -194,8 +187,7 @@ def displacementZones2(cursor, upwindWithPropTable, srid, prefix=PREFIX_NAME): FROM {ZonePolygons[z]} AS a LEFT JOIN {upwindWithPropTable} AS b ON a.{UPWIND_FACADE_FIELD} = b.{UPWIND_FACADE_FIELD} WHERE ST_AREA(a.{GEOM_FIELD}) > 0 AND {whereCond[z]}; - """ for z in variablesNames.index]) # nosec B608 # nosec B608 - ) # nosec B608 + """ for z in variablesNames.index])) # nosec B608 # nosec B608 # nosec B608 if not DEBUG: # Drop intermediate tables @@ -813,9 +805,7 @@ def rooftopZones(cursor, upwindTable, zonePropertiesTable, prefix=PREFIX_NAME): # Name of the output tables roofPerpZonesTable = DataUtil.prefix(outputBaseNameroofPerp, prefix=prefix) - RoofCornerZonesTable = DataUtil.prefix( - outputBaseNameroofCorner, prefix=prefix - ) + RoofCornerZonesTable = DataUtil.prefix(outputBaseNameroofCorner, prefix=prefix) # Create temporary table names (for tables that will be removed at the end of the IProcess) temporaryRooftopPerp = DataUtil.postfix("temporary_rooftop_perp") @@ -919,9 +909,7 @@ def rooftopZones(cursor, upwindTable, zonePropertiesTable, prefix=PREFIX_NAME): # Queries to limit the rooftop zones to the rooftop of the stacked block... extraFieldToKeep = { - "perp": "b.{0}, b.{1},".format( - ROOFTOP_PERP_LENGTH, ROOFTOP_PERP_HEIGHT - ), + "perp": "b.{0}, b.{1},".format(ROOFTOP_PERP_LENGTH, ROOFTOP_PERP_HEIGHT), "corner": """a.{0}, a.{1}, a.{2}, b.{3}, a.GEOM_CORNER_POINT,""".format( ROOFTOP_CORNER_LENGTH, @@ -991,9 +979,7 @@ def rooftopZones(cursor, upwindTable, zonePropertiesTable, prefix=PREFIX_NAME): return roofPerpZonesTable, RoofCornerZonesTable -def vegetationZones( - cursor, vegetationTable, wakeZonesTable, prefix=PREFIX_NAME -): +def vegetationZones(cursor, vegetationTable, wakeZonesTable, prefix=PREFIX_NAME): """Identify vegetation zones which are in "built up" areas and those being in "open areas". Vegetation is considered in a built up area when it intersects with build wake zone. @@ -1031,12 +1017,8 @@ def vegetationZones( outputBaseNameBuilt = "BUILTUP_VEGETATION_ZONES" # Name of the output tables - vegetationOpenZoneTable = DataUtil.prefix( - outputBaseNameOpen, prefix=prefix - ) - vegetationBuiltZoneTable = DataUtil.prefix( - outputBaseNameBuilt, prefix=prefix - ) + vegetationOpenZoneTable = DataUtil.prefix(outputBaseNameOpen, prefix=prefix) + vegetationBuiltZoneTable = DataUtil.prefix(outputBaseNameBuilt, prefix=prefix) # Create temporary table names (for tables that will be removed at the end of the IProcess) temporary_built_vegetation = DataUtil.postfix("temporary_built_vegetation") @@ -1218,9 +1200,7 @@ def identifyImpactingStackedBlocks( Name of the table used to save selected stacked blocks """ - print( - "Identify the buildings concerned by the impacted zone chosen by the user" - ) + print("Identify the buildings concerned by the impacted zone chosen by the user") # Name of the output tables dicOfSelectedBuildZones = { @@ -1231,9 +1211,7 @@ def identifyImpactingStackedBlocks( t: dicOfVegRockleZoneTable[t] + SELECTED_SUFFIX for t in dicOfVegRockleZoneTable.keys() } - outputStackedBlocks = DataUtil.prefix( - "IMPACTING_STACKED_BLOCKS", prefix=prefix - ) + outputStackedBlocks = DataUtil.prefix("IMPACTING_STACKED_BLOCKS", prefix=prefix) outputVegetation = DataUtil.prefix("IMPACTING_VEGETATION", prefix=prefix) # Create temporary table names (for tables that will be removed at the end of the IProcess) @@ -1485,9 +1463,7 @@ def identifyImpactingStackedBlocks( # Drop intermediate tables cursor.execute( safe("DROP TABLE IF EXISTS {0}").format( - ",".join( - [tabTempStack, tabTempBlock, tabCrossExtBox, tabTempBlock2] - ) + ",".join([tabTempStack, tabTempBlock, tabCrossExtBox, tabTempBlock2]) ) ) diff --git a/functions/URock/loadData.py b/functions/URock/loadData.py index b4fa3ac..b391181 100644 --- a/functions/URock/loadData.py +++ b/functions/URock/loadData.py @@ -75,9 +75,7 @@ def loadData( inputDataRel["cadTriangles"] = os.path.join( inputDirectory, prefix, inputGeometries["cadTriangles"] ) - inputDataAbs["cadTriangles"] = os.path.abspath( - inputDataRel["cadTriangles"] - ) + inputDataAbs["cadTriangles"] = os.path.abspath(inputDataRel["cadTriangles"]) # Load CAD triangles into H2GIS DB loadFile( @@ -180,9 +178,7 @@ def loadData( importQuery += """ ALTER TABLE {0} DROP COLUMN IF EXISTS {2}; ALTER TABLE {0} RENAME COLUMN {1} TO {2}; - """.format( - buildTablePreSrid, buildingHeightField, HEIGHT_FIELD - ) + """.format(buildTablePreSrid, buildingHeightField, HEIGHT_FIELD) else: importQuery += """ DROP TABLE IF EXISTS {0}; @@ -216,10 +212,7 @@ def loadData( idVegetation = ID_VEGETATION # Create an attenuation attribute with default 'DEFAULT_VEG_ATTEN_FACT' # if no column - if ( - vegetationAttenuationFactor is None - or vegetationAttenuationFactor == "" - ): + if vegetationAttenuationFactor is None or vegetationAttenuationFactor == "": cursor.execute( safe(""" ALTER TABLE {0} DROP COLUMN IF EXISTS {1}; @@ -249,10 +242,7 @@ def loadData( vegetationBaseHeight = VEGETATION_CROWN_BASE_HEIGHT # Load vegetation data and rename fields to generic names - if ( - vegetationBaseHeight.upper() - != VEGETATION_CROWN_BASE_HEIGHT.upper() - ): + if vegetationBaseHeight.upper() != VEGETATION_CROWN_BASE_HEIGHT.upper(): importQuery += """ ALTER TABLE {0} DROP COLUMN IF EXISTS {2}; ALTER TABLE {0} RENAME COLUMN {1} TO {2}; @@ -261,10 +251,7 @@ def loadData( vegetationBaseHeight, VEGETATION_CROWN_BASE_HEIGHT, ) - if ( - vegetationTopHeight.upper() - != VEGETATION_CROWN_TOP_HEIGHT.upper() - ): + if vegetationTopHeight.upper() != VEGETATION_CROWN_TOP_HEIGHT.upper(): importQuery += """ ALTER TABLE {0} DROP COLUMN IF EXISTS {2}; ALTER TABLE {0} RENAME COLUMN {1} TO {2}; @@ -593,17 +580,13 @@ def fromShp3dTo2_5( else: # Convert building triangles to to 2.5D polygons - cursor.execute( - safe(""" + cursor.execute(safe(""" DROP TABLE IF EXISTS {0}; CREATE TABLE {0} AS SELECT ID, ST_FORCE2D({1}) AS {1}, CAST(ST_ZMAX({1}) AS INT) AS {2} FROM {3} - """).format( - buildings2d, GEOM_FIELD, HEIGHT_FIELD, trianglesWithId - ) - ) + """).format(buildings2d, GEOM_FIELD, HEIGHT_FIELD, trianglesWithId)) # Identify unique building triangles keeping only the highest one whenever # 2 triangles are superimposed diff --git a/functions/URock/saveData.py b/functions/URock/saveData.py index 57a548f..1cfa33a 100644 --- a/functions/URock/saveData.py +++ b/functions/URock/saveData.py @@ -165,9 +165,7 @@ def saveBasicOutputs( df = pd.DataFrame( { HORIZ_WIND_SPEED: ((ufin**2 + vfin**2) ** 0.5).flatten("F"), - WIND_SPEED: ( - ((ufin**2 + vfin**2 + wfin**2) ** 0.5).flatten("F") - ), + WIND_SPEED: (((ufin**2 + vfin**2 + wfin**2) ** 0.5).flatten("F")), HORIZ_WIND_DIRECTION: ( radToDeg(windDirectionFromXY(ufin, vfin)).flatten("F") ), @@ -193,12 +191,8 @@ def saveBasicOutputs( LEFT JOIN {9} AS b ON a.{3} = b.{3} """).format( - createIndex( - tableName=gridName, fieldName=ID_POINT, isSpatial=False - ), - createIndex( - tableName=tempoTable, fieldName=ID_POINT, isSpatial=False - ), + createIndex(tableName=gridName, fieldName=ID_POINT, isSpatial=False), + createIndex(tableName=tempoTable, fieldName=ID_POINT, isSpatial=False), horizOutputUrock[z_i], ID_POINT, GEOM_FIELD, @@ -226,8 +220,7 @@ def saveBasicOutputs( tableName=horizOutputUrock[z_i], filedir=os.path.join( outputDir_zi, - prefix(outputFilename, prefix_name) - + OUTPUT_VECTOR_EXTENSION, + prefix(outputFilename, prefix_name) + OUTPUT_VECTOR_EXTENSION, ), delete=DELETE_OUTPUT_IF_EXISTS, ) @@ -321,15 +314,9 @@ def saveToNetCDF( z = wind3dGrp.createVariable(Z, "f4", "z") lon = wind3dGrp.createVariable(LON, "f8", ("rlon", "rlat")) lat = wind3dGrp.createVariable(LAT, "f8", ("rlon", "rlat")) - windSpeed_x = wind3dGrp.createVariable( - WINDSPEED_X, "f4", ("rlon", "rlat", "z") - ) - windSpeed_y = wind3dGrp.createVariable( - WINDSPEED_Y, "f4", ("rlon", "rlat", "z") - ) - windSpeed_z = wind3dGrp.createVariable( - WINDSPEED_Z, "f4", ("rlon", "rlat", "z") - ) + windSpeed_x = wind3dGrp.createVariable(WINDSPEED_X, "f4", ("rlon", "rlat", "z")) + windSpeed_y = wind3dGrp.createVariable(WINDSPEED_Y, "f4", ("rlon", "rlat", "z")) + windSpeed_z = wind3dGrp.createVariable(WINDSPEED_Z, "f4", ("rlon", "rlat", "z")) # Fill the variables rlon[:] = x @@ -457,9 +444,7 @@ def saveTable( output_filedir = filedir # Write files cursor.execute( - safe("""CALL {0}('{1}','{2}')""").format( - h2_function, output_filedir, tableName - ) + safe("""CALL {0}('{1}','{2}')""").format(h2_function, output_filedir, tableName) ) return output_filedir @@ -510,11 +495,9 @@ def saveRasterFile( # Define output path name outputFilePathAndNameBaseRaster = outputFilePathAndNameBase + var2save # If delete = False, add a suffix to the filename - if ( - os.path.isfile( - outputFilePathAndNameBaseRaster + OUTPUT_RASTER_EXTENSION - ) - ) and (not DELETE_OUTPUT_IF_EXISTS): + if (os.path.isfile(outputFilePathAndNameBaseRaster + OUTPUT_RASTER_EXTENSION)) and ( + not DELETE_OUTPUT_IF_EXISTS + ): outputFilePathAndNameBaseRaster = renameFileIfExists( filedir=outputFilePathAndNameBaseRaster, extension=OUTPUT_RASTER_EXTENSION, @@ -533,9 +516,7 @@ def saveRasterFile( ymax = outputRasterExtent.yMaximum() xmax = outputRasterExtent.xMaximum() ymin = outputRasterExtent.yMinimum() - tmp_file = os.path.join( - tmp_dir, f"interp_before_fillna_{var2save}.tif" - ) + tmp_file = os.path.join(tmp_dir, f"interp_before_fillna_{var2save}.tif") # If a single output raster cell contains more than 4 points, average instead of interpolate if resX * resY > 4 * meshSize**2: Grid( @@ -547,9 +528,7 @@ def saveRasterFile( width=outputRaster.width(), height=outputRaster.height(), outputBounds=[xmin, ymax, xmax, ymin], - algorithm="average:radius1={0}:radius2={0}".format( - 1.1 * meshSize - ), + algorithm="average:radius1={0}:radius2={0}".format(1.1 * meshSize), ), ) else: @@ -557,9 +536,7 @@ def saveRasterFile( interp_vec_to_rast( outputVectorFile=outputVectorFile, stacked_blocks=stacked_blocks, - outputFilePathAndNameBaseRaster=".".join( - tmp_file.split(".")[0:-1] - ), + outputFilePathAndNameBaseRaster=".".join(tmp_file.split(".")[0:-1]), extent=f"{xmin},{xmax},{ymin},{ymax} [EPSG:{srid}]", resX=resX, resY=resY, @@ -811,16 +788,12 @@ def interp_vec_to_rast( "RTYPE": 5, "OPTIONS": "", "EXTRA": "", - "OUTPUT": ( - outputFilePathAndNameBaseRaster + OUTPUT_RASTER_EXTENSION - ), + "OUTPUT": (outputFilePathAndNameBaseRaster + OUTPUT_RASTER_EXTENSION), }, )["OUTPUT"] # Else directly save the result of the interpolation else: - output_file_path = ( - outputFilePathAndNameBaseRaster + OUTPUT_RASTER_EXTENSION - ) + output_file_path = outputFilePathAndNameBaseRaster + OUTPUT_RASTER_EXTENSION shutil.copy2(src=interp_out, dst=output_file_path) return output_file_path @@ -879,9 +852,7 @@ def saveRockleZones( GEOM_FIELD, gridPoint, dicOfBuildZoneGridPoint[t], - createIndex( - tableName=gridPoint, fieldName=ID_POINT, isSpatial=False - ), + createIndex(tableName=gridPoint, fieldName=ID_POINT, isSpatial=False), createIndex( tableName=dicOfBuildZoneGridPoint[t], fieldName=ID_POINT, @@ -892,9 +863,7 @@ def saveRockleZones( saveTable( cursor=cursor, tableName="point_Buildzone_" + t, - filedir=os.path.join( - outputDataAbs["point_2DRockleZone"], t + ".geojson" - ), + filedir=os.path.join(outputDataAbs["point_2DRockleZone"], t + ".geojson"), delete=True, rotationCenterCoordinates=rotationCenterCoordinates, rotateAngle=-windDirection, @@ -905,9 +874,7 @@ def saveRockleZones( saveTable( cursor=cursor, tableName=dicOfVegZoneGridPoint[t], - filedir=os.path.join( - outputDataAbs["point_2DRockleZone"], t + ".geojson" - ), + filedir=os.path.join(outputDataAbs["point_2DRockleZone"], t + ".geojson"), delete=True, rotationCenterCoordinates=rotationCenterCoordinates, rotateAngle=-windDirection, diff --git a/functions/URock/urock_analyser_functions.py b/functions/URock/urock_analyser_functions.py index c863691..7aeec15 100644 --- a/functions/URock/urock_analyser_functions.py +++ b/functions/URock/urock_analyser_functions.py @@ -98,14 +98,10 @@ def plotSectionalViews( # Temporary files are declared pointsDir = os.path.join(TEMPO_DIRECTORY, "urock_allPoints.csv") outputPointsDir = os.path.join(TEMPO_DIRECTORY, "urock_selectedPoints.csv") - outputPolygonsDir = os.path.join( - TEMPO_DIRECTORY, "urock_selectedPolygons.csv" - ) + outputPolygonsDir = os.path.join(TEMPO_DIRECTORY, "urock_selectedPolygons.csv") if feedback: - feedback.setProgressText( - "Load NetCDF file in Python and save as csv file..." - ) + feedback.setProgressText("Load NetCDF file in Python and save as csv file...") # Load the group of the NetCDF file containing the wind speed field ds = xr.open_dataset(inputWindFile, group=WIND_GROUP) @@ -228,9 +224,7 @@ def plotSectionalViews( ) # Back to dataframe for the plotting the mean wind speed for each level - df_selectedPolygons = pd.read_csv( - outputPolygonsDir, index_col=None, header=0 - ) + df_selectedPolygons = pd.read_csv(outputPolygonsDir, index_col=None, header=0) windList = pd.Series( [ "Wind speed along x-axis (m/s)", @@ -266,9 +260,7 @@ def plotSectionalViews( plt.legend() if savePlot: fig_poly[w].savefig( - os.path.join( - outputDirectory, simulationName + "_" + w + ".png" - ) + os.path.join(outputDirectory, simulationName + "_" + w + ".png") ) # DEAL WITH LINES (ALONG LINE VERTICAL PROFILES) @@ -342,14 +334,10 @@ def plotSectionalViews( ) # Back to dataframe for the plotting of the wind speed for each level - df_selectedPoints = pd.read_csv( - outputPointsDir, index_col=None, header=0 - ) + df_selectedPoints = pd.read_csv(outputPointsDir, index_col=None, header=0) df_selectedPoints["PROJECTED_HORIZ_WIND"] = df_selectedPoints[ WINDSPEED_X.upper() - ] * np.cos( - df_selectedPoints["AZIMUTH"] - np.pi / 2 - ) + df_selectedPoints[ + ] * np.cos(df_selectedPoints["AZIMUTH"] - np.pi / 2) + df_selectedPoints[ WINDSPEED_Y.upper() ] * np.cos( df_selectedPoints["AZIMUTH"] @@ -370,9 +358,9 @@ def plotSectionalViews( # Need to reindex regularly values for stream plot if isStream: uniques_z[uniques_z == 0] = 0 - float(horiz_res) / 2 - df_selectedPoints[Z.upper()] = df_selectedPoints[ - Z.upper() - ].replace(0, 0 - float(horiz_res) / 2) + df_selectedPoints[Z.upper()] = df_selectedPoints[Z.upper()].replace( + 0, 0 - float(horiz_res) / 2 + ) dic_all = { id_line: { zval: ( @@ -487,13 +475,9 @@ def plotSectionalViews( if not scale.get(line): if np.max(np.abs(wind_d)) > 3 * np.median(np.abs(wind_d)): - scale[line] = np.max(np.abs(wind_d)) / ( - 1.5 * horiz_res - ) + scale[line] = np.max(np.abs(wind_d)) / (1.5 * horiz_res) else: - scale[line] = np.median(np.abs(wind_d)) / ( - 1.5 * horiz_res - ) + scale[line] = np.median(np.abs(wind_d)) / (1.5 * horiz_res) Q = ax[line].quiver( D, z, @@ -547,9 +531,7 @@ def plotSectionalViews( rec_z0 = sorted_z[loc_z] - 0.5 * ( sorted_z[loc_z] - sorted_z[loc_z - 1] ) - rec_height = 0.5 * ( - sorted_z[loc_z + 1] - sorted_z[loc_z - 1] - ) + rec_height = 0.5 * (sorted_z[loc_z + 1] - sorted_z[loc_z - 1]) # Get starting distance and width of building rectangle loc_d = sorted_dist.get_loc(bcell[1]) if loc_d == 0: @@ -558,17 +540,11 @@ def plotSectionalViews( ) rec_width = sorted_dist[loc_d + 1] - sorted_dist[loc_d] elif loc_d == sorted_dist.size - 1: - rec_d0 = 0.5 * ( - sorted_dist[loc_d - 1] + sorted_dist[loc_d] - ) + rec_d0 = 0.5 * (sorted_dist[loc_d - 1] + sorted_dist[loc_d]) rec_width = sorted_dist[loc_d] - sorted_dist[loc_d - 1] else: - rec_d0 = 0.5 * ( - sorted_dist[loc_d - 1] + sorted_dist[loc_d] - ) - rec_width = 0.5 * ( - sorted_dist[loc_d + 1] - sorted_dist[loc_d - 1] - ) + rec_d0 = 0.5 * (sorted_dist[loc_d - 1] + sorted_dist[loc_d]) + rec_width = 0.5 * (sorted_dist[loc_d + 1] - sorted_dist[loc_d - 1]) # Define and plot the building rectangle rect[i] = Rectangle( @@ -603,8 +579,6 @@ def conditional_interpolate(df, cols, limit=2): .where(df[cols].isnull().prod(axis=1).astype(bool)) ) - result = df.interpolate(limit_area="inside", method="slinear").mask( - m >= limit - ) + result = df.interpolate(limit_area="inside", method="slinear").mask(m >= limit) return result diff --git a/functions/URock/urock_processing_algorithm_dep.py b/functions/URock/urock_processing_algorithm_dep.py index 63dcfd6..95d7891 100644 --- a/functions/URock/urock_processing_algorithm_dep.py +++ b/functions/URock/urock_processing_algorithm_dep.py @@ -62,9 +62,7 @@ try: path_pybin = DataUtil.locate_py() - subprocess.check_call( - [str(path_pybin), "-m", "pip", "install", "jaydebeapi"] - ) + subprocess.check_call([str(path_pybin), "-m", "pip", "install", "jaydebeapi"]) import jaydebeapi except Exception: QMessageBox.critical( @@ -139,9 +137,7 @@ def initAlgorithm(self, config): # Get the default value of the Java environment path if already exists javaDirDefault = getJavaDir(plugin_directory) - if ( - not javaDirDefault - ): # Raise an error if could not find a Java installation + if not javaDirDefault: # Raise an error if could not find a Java installation raise QgsProcessingException("No Java installation found") elif ("Program Files (x86)" in javaDirDefault) and ( struct.calcsize("P") * 8 != 32 @@ -154,9 +150,7 @@ def initAlgorithm(self, config): ) else: # Set a Java dir if not exist and save it into a file in the plugin repository setJavaDir(javaDirDefault) - saveJavaDir( - javaPath=javaDirDefault, pluginDirectory=plugin_directory - ) + saveJavaDir(javaPath=javaDirDefault, pluginDirectory=plugin_directory) # We add the input parameters # First the layers used as input and output @@ -385,21 +379,13 @@ def processAlgorithm(self, parameters, context, feedback): plugin_directory = self.plugin_dir = os.path.dirname(__file__) # Defines inputs - javaEnvVar = self.parameterAsString( - parameters, self.JAVA_PATH, context - ) - z_ref = self.parameterAsDouble( - parameters, self.INPUT_WIND_HEIGHT, context - ) - v_ref = self.parameterAsDouble( - parameters, self.INPUT_WIND_SPEED, context - ) + javaEnvVar = self.parameterAsString(parameters, self.JAVA_PATH, context) + z_ref = self.parameterAsDouble(parameters, self.INPUT_WIND_HEIGHT, context) + v_ref = self.parameterAsDouble(parameters, self.INPUT_WIND_SPEED, context) windDirection = self.parameterAsDouble( parameters, self.INPUT_WIND_DIRECTION, context ) - meshSize = self.parameterAsInt( - parameters, self.HORIZONTAL_RESOLUTION, context - ) + meshSize = self.parameterAsInt(parameters, self.HORIZONTAL_RESOLUTION, context) dz = self.parameterAsInt(parameters, self.VERTICAL_RESOLUTION, context) profileType = self.LIST_OF_PROFILES.loc[ self.parameterAsInt(parameters, self.INPUT_PROFILE_TYPE, context) @@ -472,9 +458,7 @@ def processAlgorithm(self, parameters, context, feedback): # prefix = self.parameterAsString(parameters, self.PREFIX, context) # Defines outputs - z_out = self.parameterAsString( - parameters, self.WIND_HEIGHT, context - ).split(",") + z_out = self.parameterAsString(parameters, self.WIND_HEIGHT, context).split(",") z_out = [float(i) for i in z_out] outputDirectory = self.parameterAsString( parameters, self.OUTPUT_DIRECTORY, context @@ -482,18 +466,10 @@ def processAlgorithm(self, parameters, context, feedback): outputFilename = self.parameterAsString( parameters, self.OUTPUT_FILENAME, context ) - saveRaster = self.parameterAsBool( - parameters, self.SAVE_RASTER, context - ) - saveVector = self.parameterAsBool( - parameters, self.SAVE_VECTOR, context - ) - saveNetcdf = self.parameterAsBool( - parameters, self.SAVE_NETCDF, context - ) - loadOutput = self.parameterAsBool( - parameters, self.LOAD_OUTPUT, context - ) + saveRaster = self.parameterAsBool(parameters, self.SAVE_RASTER, context) + saveVector = self.parameterAsBool(parameters, self.SAVE_VECTOR, context) + saveNetcdf = self.parameterAsBool(parameters, self.SAVE_NETCDF, context) + loadOutput = self.parameterAsBool(parameters, self.LOAD_OUTPUT, context) # Creates the output folder if it does not exist if not os.path.exists(outputDirectory): @@ -514,12 +490,10 @@ def processAlgorithm(self, parameters, context, feedback): "Coordinate system of input building layer and output Raster layer differ!" ) xres = ( - outputRaster.extent().xMaximum() - - outputRaster.extent().xMinimum() + outputRaster.extent().xMaximum() - outputRaster.extent().xMinimum() ) / outputRaster.width() yres = ( - outputRaster.extent().yMaximum() - - outputRaster.extent().yMinimum() + outputRaster.extent().yMaximum() - outputRaster.extent().yMinimum() ) / outputRaster.height() # If there is a raster and no meshSize, take the mean of x and y raster resolution if not meshSize: @@ -637,9 +611,7 @@ def processAlgorithm(self, parameters, context, feedback): os.path.join( outputDirectory, "z{0}".format(str(z_i).replace(".", "_")), - outputFilename - + WIND_SPEED - + OUTPUT_RASTER_EXTENSION, + outputFilename + WIND_SPEED + OUTPUT_RASTER_EXTENSION, ), "Wind speed at {0} m".format(z_i), "gdal", diff --git a/functions/dailyshading.py b/functions/dailyshading.py index 24ae0c0..f0bde79 100644 --- a/functions/dailyshading.py +++ b/functions/dailyshading.py @@ -177,15 +177,10 @@ def dailyshading( # shtot = shtot + sh if onetime == 0: - filename = ( - folder + "/Shadow_ground_" + timestr + "_LST.tif" - ) + filename = folder + "/Shadow_ground_" + timestr + "_LST.tif" saveraster(gdal_data, filename, sh) filenamewallsh = ( - folder - + "/Facadeshadow_frombuilding_" - + timestr - + "_LST.tif" + folder + "/Facadeshadow_frombuilding_" + timestr + "_LST.tif" ) saveraster(gdal_data, filenamewallsh, wallsh) @@ -235,10 +230,7 @@ def dailyshading( saveraster(gdal_data, filenamewallsh, wallsh) if usevegdem == 1: filenamewallshve = ( - folder - + "/Facadeshadow_fromvegetation_" - + timestr - + "_LST.tif" + folder + "/Facadeshadow_fromvegetation_" + timestr + "_LST.tif" ) saveraster(gdal_data, filenamewallshve, wallshve) diff --git a/functions/svf_for_voxels.py b/functions/svf_for_voxels.py index b8a69e6..c2d8050 100644 --- a/functions/svf_for_voxels.py +++ b/functions/svf_for_voxels.py @@ -31,9 +31,7 @@ def wallscheme_prepare( torch.tensor([[1, 1, 1], [1, 0, 1], [1, 1, 1]], device=dsm.device), ) walls_copy = torch.clone(walls) - aspect = wa.filter1Goodwin_as_aspect_v3( - walls_copy, scale, dsm, feedback, 100 - ) + aspect = wa.filter1Goodwin_as_aspect_v3(walls_copy, scale, dsm, feedback, 100) walls_exact = walls.clone() walls_round = torch.ceil(walls).int() @@ -73,9 +71,7 @@ def wallscheme_prepare( v_ids = torch.arange(wall2d_id_tensor.shape[0], device=device) # Find the starting index of each repeated wall sequence cum_voxels = torch.cumsum(number_of_voxels, dim=0) - start_indices = torch.cat( - [torch.tensor([0], device=device), cum_voxels[:-1]] - ) + start_indices = torch.cat([torch.tensor([0], device=device), cum_voxels[:-1]]) shift = torch.repeat_interleave(start_indices, number_of_voxels) local_voxel_index = v_ids - shift + 1 @@ -87,16 +83,12 @@ def wallscheme_prepare( wall2d_id_tensor = torch.cat([wall2d_id_tensor, zero_val]) voxel_height_tensor = torch.cat([voxel_height_tensor, zero_val.float()]) wall_height_tensor = torch.cat([wall_height_tensor, zero_val]) - wall_height_exact_tensor = torch.cat( - [wall_height_exact_tensor, zero_val.float()] - ) + wall_height_exact_tensor = torch.cat([wall_height_exact_tensor, zero_val.float()]) y_position_tensor = torch.cat([y_position_tensor, zero_val]) x_position_tensor = torch.cat([x_position_tensor, zero_val]) # 7. Construct outputs - voxelId_list = torch.arange( - 1, wall2d_id_tensor.shape[0] + 1, device=device - ) + voxelId_list = torch.arange(1, wall2d_id_tensor.shape[0] + 1, device=device) voxelTable = torch.column_stack( [ @@ -112,9 +104,7 @@ def wallscheme_prepare( # 8. Construct dictionary mapping (Kept native Python as requested by return type) # We do this from the extracted wall tensors to avoid looping over the voxel table - wall_dict = dict( - zip(wall_indices.tolist(), walls_exact_extracted.tolist()) - ) + wall_dict = dict(zip(wall_indices.tolist(), walls_exact_extracted.tolist())) wall_dict[0] = 0.0 # Convert specific lists to match original return signature formats if downstream requires it @@ -175,9 +165,7 @@ def svf_for_voxels( # Counter to feedback current iteration counter = 1 # How many iterations are required to calculate svf for all voxels - loop_range = torch.arange( - svf_height, maxWallHeight + svf_height, svf_height - ) + loop_range = torch.arange(svf_height, maxWallHeight + svf_height, svf_height) # Loop for svf calculations of all voxel heights for i in loop_range: @@ -189,9 +177,7 @@ def svf_for_voxels( + str(int(loop_range.shape[0])) ) - feedback.setProgressText( - "Increasing ground level with " + str(i) + " meters." - ) + feedback.setProgressText("Increasing ground level with " + str(i) + " meters.") # Elevate ground in dsm temp_dsm = ((dsm + i) * ground) + (dsm * (1 - ground)) @@ -233,9 +219,7 @@ def svf_for_voxels( svftotal = svfbu - (1 - svfveg) * (1 - trans) # Get svf for each voxel - voxel_y = torch.where( - voxelTable[:, 1] == i + svf_height - ) # +svf_height) + voxel_y = torch.where(voxelTable[:, 1] == i + svf_height) # +svf_height) for temp_y in voxel_y[0]: svf_array[temp_y] = svftotal[ int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) @@ -328,9 +312,9 @@ def svf_kmeans( labels = kmeans.fit_predict(data_reshaped) # Reshape the labels back into a torch tensor on the selected device - kmeans_clusters = torch.from_numpy( - labels.reshape(dsm.shape[0], dsm.shape[1]) - ).to(device) + kmeans_clusters = torch.from_numpy(labels.reshape(dsm.shape[0], dsm.shape[1])).to( + device + ) # Remove cluster representing ground areas, i.e. where dsm - dem = 0 cluster_range = torch.arange(clusters, device=device) @@ -414,9 +398,7 @@ def svf_kmeans( svftotal = svfbu - (1 - svfveg) * (1 - trans) # Get svf for each voxel - voxel_y = torch.where( - voxelTable[:, 1] == i + svf_height - ) # +svf_height) + voxel_y = torch.where(voxelTable[:, 1] == i + svf_height) # +svf_height) for temp_y in voxel_y[0]: svf_array[temp_y] = svftotal[ int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) @@ -441,15 +423,12 @@ def svf_kmeans( ) # Get their unique wall ids for slicing for ( unique_wall - ) in ( - unique_walls - ): # Loop over all unique walls lower than lowest cluster + ) in unique_walls: # Loop over all unique walls lower than lowest cluster temp_wall = temp_data[temp_data[:, 4] == unique_wall, :][ :, 1 ].max() # Max height of highest voxel in unique_wall temp_y = torch.where( - (voxelTable[:, 4] == unique_wall) - & (voxelTable[:, 1] == temp_wall) + (voxelTable[:, 4] == unique_wall) & (voxelTable[:, 1] == temp_wall) )[ 0 ] # Get row of unique_wall and highest voxel in voxelTable @@ -486,15 +465,12 @@ def svf_kmeans( ) # Get their unique wall ids for slicing for ( unique_wall - ) in ( - unique_walls - ): # Loop over all unique walls lower than lowest cluster + ) in unique_walls: # Loop over all unique walls lower than lowest cluster temp_wall = temp_data[temp_data[:, 4] == unique_wall, :][ :, 1 ].max() # Max height of highest voxel in unique_wall temp_y = torch.where( - (voxelTable[:, 4] == unique_wall) - & (voxelTable[:, 1] == temp_wall) + (voxelTable[:, 4] == unique_wall) & (voxelTable[:, 1] == temp_wall) )[ 0 ] # Get row of unique_wall and highest voxel in voxelTable diff --git a/functions/svf_functions.py b/functions/svf_functions.py index c245218..1f14b6a 100644 --- a/functions/svf_functions.py +++ b/functions/svf_functions.py @@ -140,9 +140,7 @@ def svfForProcessing153( ): if device is None: device = ( - torch.device("cuda") - if torch.cuda.is_available() - else torch.device("cpu") + torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") ) dsm = _to_tensor(dsm, device) @@ -212,9 +210,7 @@ def svfForProcessing153( ) iazimuth = torch.zeros(int(torch.sum(aziinterval).item()), device=device) - shmat = torch.zeros( - (rows, cols, int(torch.sum(aziinterval).item())), device=device - ) + shmat = torch.zeros((rows, cols, int(torch.sum(aziinterval).item())), device=device) vegshmat = torch.zeros( (rows, cols, int(torch.sum(aziinterval).item())), device=device ) @@ -239,15 +235,9 @@ def svfForProcessing153( ) # Rasters to fill with values in loop - all_buildIDSeen = torch.zeros( - (rows, cols, skyvaultalt.shape[0]), device=device - ) - all_voxelHeight = torch.zeros( - (rows, cols, skyvaultalt.shape[0]), device=device - ) - all_voxelId = torch.zeros( - (rows, cols, skyvaultalt.shape[0]), device=device - ) + all_buildIDSeen = torch.zeros((rows, cols, skyvaultalt.shape[0]), device=device) + all_voxelHeight = torch.zeros((rows, cols, skyvaultalt.shape[0]), device=device) + all_voxelId = torch.zeros((rows, cols, skyvaultalt.shape[0]), device=device) else: voxelTable = 0 allbuildIDSeen = 0 @@ -269,12 +259,8 @@ def svfForProcessing153( if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") break - altitude = torch.tensor( - float(skyvaultaltint[int(i)].item()), device=device - ) - azimuth = torch.tensor( - float(iazimuth[int(index)].item()), device=device - ) + altitude = torch.tensor(float(skyvaultaltint[int(i)].item()), device=device) + azimuth = torch.tensor(float(iazimuth[int(index)].item()), device=device) # Casting shadow if wallScheme: @@ -342,9 +328,7 @@ def svfForProcessing153( ) vegsh = torch.tensor(shadowresult["vegsh"], device=device) - vbshvegsh = torch.tensor( - shadowresult["vbshvegsh"], device=device - ) + vbshvegsh = torch.tensor(shadowresult["vbshvegsh"], device=device) vegshmat[:, :, index] = vegsh vbshvegshmat[:, :, index] = vbshvegsh sh = torch.tensor(shadowresult["sh"], device=device) @@ -491,9 +475,7 @@ def svfForProcessing655( ): if device is None: device = ( - torch.device("cuda") - if torch.cuda.is_available() - else torch.device("cpu") + torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") ) dsm = _to_tensor(dsm, device) vegdem = _to_tensor(vegdem, device) @@ -572,9 +554,7 @@ def svfForProcessing655( ) vegsh = torch.tensor(shadowresult["vegsh"], device=device) - vbshvegsh = torch.tensor( - shadowresult["vbshvegsh"], device=device - ) + vbshvegsh = torch.tensor(shadowresult["vbshvegsh"], device=device) sh = torch.tensor(shadowresult["sh"], device=device) else: sh = shadow.shadowingfunctionglobalradiation( diff --git a/functions/wallalgorithms.py b/functions/wallalgorithms.py index b6f8048..803e388 100644 --- a/functions/wallalgorithms.py +++ b/functions/wallalgorithms.py @@ -7,11 +7,11 @@ import torch import torch.nn.functional as F - # import scipy.misc as sc import scipy.ndimage as sc from scipy.ndimage import maximum_filter + def findwalls_sp(arr_dsm, walllimit, device, footprint=None): if isinstance(arr_dsm, torch.Tensor): dsm_tensor = arr_dsm @@ -19,11 +19,7 @@ def findwalls_sp(arr_dsm, walllimit, device, footprint=None): dsm_tensor = torch.tensor(arr_dsm, device=device) if footprint is None or footprint is False: - footprint = torch.tensor([ - [0, 1, 0], - [1, 1, 1], - [0, 1, 0] - ], device=device) + footprint = torch.tensor([[0, 1, 0], [1, 1, 1], [0, 1, 0]], device=device) else: footprint = footprint.to(device=device) @@ -34,7 +30,7 @@ def findwalls_sp(arr_dsm, walllimit, device, footprint=None): padded_a = F.pad(padded_a, pad=(pad_w, pad_w, pad_h, pad_h), mode="replicate") padded_a = padded_a.squeeze(0).squeeze(0) - max_neighbors = torch.full_like(dsm_tensor, float('-inf')) + max_neighbors = torch.full_like(dsm_tensor, float("-inf")) y_indices, x_indices = torch.where(footprint == 1) @@ -107,7 +103,7 @@ def filter1Goodwin_as_aspect_v3( :return: Tensor (H, W), Wall aspect directions """ row, col = a.shape - + # 1. Compute kernel footprint based on scale factor filtersize = torch.floor((scale + 0.0000000001) * 9) if filtersize <= 2: @@ -133,7 +129,7 @@ def filter1Goodwin_as_aspect_v3( buildfilt2_list = [] # 2. Pre-calculate all 180 directional filters on CPU or GPU - with torch.no_grad(): + with torch.no_grad(): for h in range(180): filtmatrix1temp = sc.rotate( filtmatrix.numpy(), h, order=1, reshape=False, mode="nearest" @@ -167,11 +163,11 @@ def filter1Goodwin_as_aspect_v3( all_kernels_walls = torch.cat(filtmatrix_list, dim=0) all_kernels_dsm1 = torch.cat(buildfilt1_list, dim=0) all_kernels_dsm2 = torch.cat(buildfilt2_list, dim=0) - + # 3. Setup global output allocation arrays final_y = torch.zeros((row, col), device=device) final_x = torch.zeros((row, col), device=device) - + walls_binary = (walls_for_dir > 0).float().to(device) a_device = a.float().to(device) @@ -184,10 +180,10 @@ def filter1Goodwin_as_aspect_v3( # 4. Loop through spatial tiles for r_start in range(0, row, tile_size): r_end = min(r_start + tile_size, row) - + for c_start in range(0, col, tile_size): c_end = min(c_start + tile_size, col) - + if feedback is not None and feedback.isCanceled(): return final_y @@ -196,32 +192,42 @@ def filter1Goodwin_as_aspect_v3( pad_bottom = min(row - r_end, filthalvefloor) pad_left = min(c_start, filthalvefloor) pad_right = min(col - c_end, filthalvefloor) - + # Slice tile out with padding included - tile_a = a_device[ - (r_start - pad_top):(r_end + pad_bottom), - (c_start - pad_left):(c_end + pad_right) - ].unsqueeze(0).unsqueeze(0) - - tile_walls = walls_binary[ - (r_start - pad_top):(r_end + pad_bottom), - (c_start - pad_left):(c_end + pad_right) - ].unsqueeze(0).unsqueeze(0) - + tile_a = ( + a_device[ + (r_start - pad_top) : (r_end + pad_bottom), + (c_start - pad_left) : (c_end + pad_right), + ] + .unsqueeze(0) + .unsqueeze(0) + ) + + tile_walls = ( + walls_binary[ + (r_start - pad_top) : (r_end + pad_bottom), + (c_start - pad_left) : (c_end + pad_right), + ] + .unsqueeze(0) + .unsqueeze(0) + ) + # Local allocation sizes for this specific tile (including pads) tile_rows, tile_cols = tile_a.shape[2], tile_a.shape[3] - + # Running Maximum Setup for this tile z_max = torch.full((tile_rows, tile_cols), -1.0, device=device) - h_best = torch.zeros((tile_rows, tile_cols), dtype=torch.long, device=device) + h_best = torch.zeros( + (tile_rows, tile_cols), dtype=torch.long, device=device + ) dsm_best1 = torch.zeros((tile_rows, tile_cols), device=device) dsm_best2 = torch.zeros((tile_rows, tile_cols), device=device) # 5. Process angles in small chunks inside this single spatial tile - chunk_size = 10 + chunk_size = 10 for idx in range(0, 180, chunk_size): end_idx = min(idx + chunk_size, 180) - + k_walls = all_kernels_walls[idx:end_idx].to(device) k_dsm1 = all_kernels_dsm1[idx:end_idx].to(device) k_dsm2 = all_kernels_dsm2[idx:end_idx].to(device) @@ -233,48 +239,80 @@ def filter1Goodwin_as_aspect_v3( # Account for spatial shrinkage since F.conv2d with padding=0 trims edges c_rows, c_cols = walls_conv.shape[1], walls_conv.shape[2] - + # Align running arrays dynamically to conv output window - z_max_crop = z_max[filthalvefloor:filthalvefloor+c_rows, filthalvefloor:filthalvefloor+c_cols] - + z_max_crop = z_max[ + filthalvefloor : filthalvefloor + c_rows, + filthalvefloor : filthalvefloor + c_cols, + ] + chunk_max, chunk_h_local = torch.max(walls_conv, dim=0) is_new_max = chunk_max >= z_max_crop - + if is_new_max.any(): # Update local trackers - z_max[filthalvefloor:filthalvefloor+c_rows, filthalvefloor:filthalvefloor+c_cols] = torch.where(is_new_max, chunk_max, z_max_crop) - + z_max[ + filthalvefloor : filthalvefloor + c_rows, + filthalvefloor : filthalvefloor + c_cols, + ] = torch.where(is_new_max, chunk_max, z_max_crop) + chunk_h_absolute = chunk_h_local + idx - h_best_crop = h_best[filthalvefloor:filthalvefloor+c_rows, filthalvefloor:filthalvefloor+c_cols] - h_best[filthalvefloor:filthalvefloor+c_rows, filthalvefloor:filthalvefloor+c_cols] = torch.where(is_new_max, chunk_h_absolute, h_best_crop) + h_best_crop = h_best[ + filthalvefloor : filthalvefloor + c_rows, + filthalvefloor : filthalvefloor + c_cols, + ] + h_best[ + filthalvefloor : filthalvefloor + c_rows, + filthalvefloor : filthalvefloor + c_cols, + ] = torch.where(is_new_max, chunk_h_absolute, h_best_crop) h_local_unsqueeze = chunk_h_local.unsqueeze(0) - chunk_dsm1 = torch.gather(dsm_conv1, dim=0, index=h_local_unsqueeze).squeeze(0) - chunk_dsm2 = torch.gather(dsm_conv2, dim=0, index=h_local_unsqueeze).squeeze(0) - - dsm_best1_crop = dsm_best1[filthalvefloor:filthalvefloor+c_rows, filthalvefloor:filthalvefloor+c_cols] - dsm_best2_crop = dsm_best2[filthalvefloor:filthalvefloor+c_rows, filthalvefloor:filthalvefloor+c_cols] - - dsm_best1[filthalvefloor:filthalvefloor+c_rows, filthalvefloor:filthalvefloor+c_cols] = torch.where(is_new_max, chunk_dsm1, dsm_best1_crop) - dsm_best2[filthalvefloor:filthalvefloor+c_rows, filthalvefloor:filthalvefloor+c_cols] = torch.where(is_new_max, chunk_dsm2, dsm_best2_crop) + chunk_dsm1 = torch.gather( + dsm_conv1, dim=0, index=h_local_unsqueeze + ).squeeze(0) + chunk_dsm2 = torch.gather( + dsm_conv2, dim=0, index=h_local_unsqueeze + ).squeeze(0) + + dsm_best1_crop = dsm_best1[ + filthalvefloor : filthalvefloor + c_rows, + filthalvefloor : filthalvefloor + c_cols, + ] + dsm_best2_crop = dsm_best2[ + filthalvefloor : filthalvefloor + c_rows, + filthalvefloor : filthalvefloor + c_cols, + ] + + dsm_best1[ + filthalvefloor : filthalvefloor + c_rows, + filthalvefloor : filthalvefloor + c_cols, + ] = torch.where(is_new_max, chunk_dsm1, dsm_best1_crop) + dsm_best2[ + filthalvefloor : filthalvefloor + c_rows, + filthalvefloor : filthalvefloor + c_cols, + ] = torch.where(is_new_max, chunk_dsm2, dsm_best2_crop) # Un-pad results to extract the pure valid window of this tile tile_y = 270.0 - h_best.float() tile_x = torch.where(dsm_best1 > dsm_best2, 1, 2) - - valid_tile_y = tile_y[pad_top : tile_rows - pad_bottom, pad_left : tile_cols - pad_right] - valid_tile_x = tile_x[pad_top : tile_rows - pad_bottom, pad_left : tile_cols - pad_right] - + + valid_tile_y = tile_y[ + pad_top : tile_rows - pad_bottom, pad_left : tile_cols - pad_right + ] + valid_tile_x = tile_x[ + pad_top : tile_rows - pad_bottom, pad_left : tile_cols - pad_right + ] + # Write back cleanly into the global array without overlap seams final_y[r_start:r_end, c_start:c_end] = valid_tile_y final_x[r_start:r_end, c_start:c_end] = valid_tile_x - + # Progress handling tile_count += 1 if feedback is not None: feedback.setProgress(int((tile_count / total_tiles) * total * 0.9)) - # 6. Global Post-processing calculations + # 6. Global Post-processing calculations border_mask = torch.zeros((row, col), dtype=torch.bool, device=device) start = filthalveceil - 1 end_row = row - filthalveceil - 1 @@ -290,15 +328,22 @@ def filter1Goodwin_as_aspect_v3( # Incorporate derivative fallback values for flat results grad, asp = get_ders(a, scale) - asp_device = torch.from_numpy(asp).to(device) if not isinstance(asp, torch.Tensor) else asp.to(device) - - final_y = final_y + ((walls_binary == 1) * 1) * ((final_y == 0) * 1) * (asp_device / (math.pi / 180.0)) + asp_device = ( + torch.from_numpy(asp).to(device) + if not isinstance(asp, torch.Tensor) + else asp.to(device) + ) + + final_y = final_y + ((walls_binary == 1) * 1) * ((final_y == 0) * 1) * ( + asp_device / (math.pi / 180.0) + ) if feedback is not None: feedback.setProgress(int(total)) return final_y + def cart2pol(x, y, units="deg"): radius = torch.sqrt(x**2 + y**2) theta = torch.arctan2(y, x) diff --git a/plugin_upload.py b/plugin_upload.py index a2a2b96..a417ebe 100644 --- a/plugin_upload.py +++ b/plugin_upload.py @@ -30,15 +30,13 @@ def main(parameters, arguments): :param parameters: Command line parameters. :param arguments: Command line arguments. """ - address = ( - "{protocol}://{username}:{password}@{server}:{port}{endpoint}".format( - protocol=PROTOCOL, - username=parameters.username, - password=parameters.password, - server=parameters.server, - port=parameters.port, - endpoint=ENDPOINT, - ) + address = "{protocol}://{username}:{password}@{server}:{port}{endpoint}".format( + protocol=PROTOCOL, + username=parameters.username, + password=parameters.password, + server=parameters.server, + port=parameters.port, + endpoint=ENDPOINT, ) print("Connecting to: %s" % hide_password(address)) diff --git a/postprocessor/solwieganalyzer_algorithm.py b/postprocessor/solwieganalyzer_algorithm.py index d524270..82d0675 100644 --- a/postprocessor/solwieganalyzer_algorithm.py +++ b/postprocessor/solwieganalyzer_algorithm.py @@ -145,25 +145,13 @@ def initAlgorithm(self, config): def processAlgorithm(self, parameters, context, feedback): # InputParameters - solweigDir = self.parameterAsString( - parameters, self.SOLWEIG_DIR, context - ) + solweigDir = self.parameterAsString(parameters, self.SOLWEIG_DIR, context) variaIn = self.parameterAsString(parameters, self.VARIA_IN, context) - buildings = self.parameterAsRasterLayer( - parameters, self.BUILDINGS, context - ) - statTypeStr = self.parameterAsString( - parameters, self.STAT_TYPE, context - ) - thresTypeStr = self.parameterAsString( - parameters, self.THRES_TYPE, context - ) - thresNum = self.parameterAsDouble( - parameters, self.TMRT_THRES_NUM, context - ) - outputStat = self.parameterAsOutputLayer( - parameters, self.STAT_OUT, context - ) + buildings = self.parameterAsRasterLayer(parameters, self.BUILDINGS, context) + statTypeStr = self.parameterAsString(parameters, self.STAT_TYPE, context) + thresTypeStr = self.parameterAsString(parameters, self.THRES_TYPE, context) + thresNum = self.parameterAsDouble(parameters, self.TMRT_THRES_NUM, context) + outputStat = self.parameterAsOutputLayer(parameters, self.STAT_OUT, context) outputTMRT = None feedback.setProgressText("Initializing...") @@ -232,9 +220,7 @@ def processAlgorithm(self, parameters, context, feedback): # Diurnal mean if statType == 0: - feedback.setProgressText( - "Calculating " + self.var + " diurnal mean." - ) + feedback.setProgressText("Calculating " + self.var + " diurnal mean.") index = 0 for i in self.posAll: gdal_dsm = gdal.Open(solweigDir + "/" + self.l[i]) @@ -255,9 +241,7 @@ def processAlgorithm(self, parameters, context, feedback): # Daytime mean if statType == 1: - feedback.setProgressText( - "Calculating " + self.var + " daytime mean." - ) + feedback.setProgressText("Calculating " + self.var + " daytime mean.") index = 0 for i in self.posDay: gdal_dsm = gdal.Open(solweigDir + "/" + self.l[i]) @@ -278,9 +262,7 @@ def processAlgorithm(self, parameters, context, feedback): # Nighttime mean if statType == 2: - feedback.setProgressText( - "Calculating " + self.var + " nighttime mean." - ) + feedback.setProgressText("Calculating " + self.var + " nighttime mean.") index = 0 for i in self.posNight: gdal_dsm = gdal.Open(solweigDir + "/" + self.l[i]) @@ -407,9 +389,7 @@ def processAlgorithm(self, parameters, context, feedback): # Tmrt threshold above if thresType == 1: feedback.setProgressText( - "Calculating Tmrt percent time above " - + str(thresNum) - + " degC." + "Calculating Tmrt percent time above " + str(thresNum) + " degC." ) outputTMRT = self.parameterAsOutputLayer( parameters, self.TMRT_STAT_OUT, context @@ -437,9 +417,7 @@ def processAlgorithm(self, parameters, context, feedback): # Tmrt threshold below if thresType == 2: feedback.setProgressText( - "Calculating Tmrt percent time below " - + str(thresNum) - + " degC." + "Calculating Tmrt percent time below " + str(thresNum) + " degC." ) outputTMRT = self.parameterAsOutputLayer( parameters, self.TMRT_STAT_OUT, context diff --git a/postprocessor/spatialtc_algorithm.py b/postprocessor/spatialtc_algorithm.py index 0508da6..b8cf0c5 100644 --- a/postprocessor/spatialtc_algorithm.py +++ b/postprocessor/spatialtc_algorithm.py @@ -47,9 +47,7 @@ def load_grid(filepath, feedback): feedback.setProgressText("Successfully loaded " + filepath) except: raise QgsProcessingException( - "Error: Could not load " - + filepath - + ". File does not exist. Check path!" + "Error: Could not load " + filepath + ". File does not exist. Check path!" ) # Return gdal raster layer as numpy array, number of rows and columns in raster @@ -85,9 +83,7 @@ def get_latlon(raster, gdal_raster): heightx = gdal_raster.RasterYSize geotransform = gdal_raster.GetGeoTransform() minx = geotransform[0] - miny = ( - geotransform[3] + widthx * geotransform[4] + heightx * geotransform[5] - ) + miny = geotransform[3] + widthx * geotransform[4] + heightx * geotransform[5] lonlat = transform.TransformPoint(minx, miny) gdalver = float(gdal.__version__[0]) if gdalver == 3.0: @@ -184,9 +180,7 @@ def initAlgorithm(self, config): minValue=0, maxValue=120, ) - age.setFlags( - age.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) + age.setFlags(age.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) self.addParameter(age) act = QgsProcessingParameterNumber( self.ACTIVITY, @@ -197,9 +191,7 @@ def initAlgorithm(self, config): minValue=0, maxValue=1000, ) - act.setFlags( - act.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) + act.setFlags(act.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) self.addParameter(act) clo = QgsProcessingParameterNumber( self.CLO, @@ -210,9 +202,7 @@ def initAlgorithm(self, config): minValue=0, maxValue=10, ) - clo.setFlags( - clo.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) + clo.setFlags(clo.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) self.addParameter(clo) wei = QgsProcessingParameterNumber( self.WEIGHT, @@ -223,9 +213,7 @@ def initAlgorithm(self, config): minValue=0, maxValue=500, ) - wei.setFlags( - wei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) + wei.setFlags(wei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) self.addParameter(wei) hei = QgsProcessingParameterNumber( self.HEIGHT, @@ -236,9 +224,7 @@ def initAlgorithm(self, config): minValue=0, maxValue=250, ) - hei.setFlags( - hei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) + hei.setFlags(hei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) self.addParameter(hei) sex = QgsProcessingParameterEnum( self.SEX, @@ -247,9 +233,7 @@ def initAlgorithm(self, config): optional=True, defaultValue=0, ) - sex.setFlags( - sex.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) + sex.setFlags(sex.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) self.addParameter(sex) # COMFA or COMFA-kid @@ -299,9 +283,7 @@ def processAlgorithm(self, parameters, context, feedback): comfa_kid = self.parameterAsBool(parameters, self.COMFA, context) # metdata = self.parameterAsString(parameters, self.METDATA, context) - outputRaster = self.parameterAsOutputLayer( - parameters, self.TC_OUT, context - ) + outputRaster = self.parameterAsOutputLayer(parameters, self.TC_OUT, context) mbody = None ht = None @@ -314,9 +296,7 @@ def processAlgorithm(self, parameters, context, feedback): # Get SOLWEIG output folder path from Tmrt raster path provider = tmrt.dataProvider() filepath_tmrt = str(provider.dataSourceUri()) # Path for Tmrt raster - solweig_path = os.path.dirname( - os.path.abspath(filepath_tmrt) - ) # issue #702 + solweig_path = os.path.dirname(os.path.abspath(filepath_tmrt)) # issue #702 # solweig_path = filepath_tmrt.split('Tmrt')[0] # Path to SOLWEIG output folder, i.e. where Tmrt raster is located _, solweigfile = os.path.split(filepath_tmrt) # solweig_path = os.path.dirname(filepath_tmrt) # issue 31 @@ -374,9 +354,7 @@ def processAlgorithm(self, parameters, context, feedback): yyyyTmrt = int( solweigfile.split("_")[-3] ) # int(filepath_tmrt[-18:-14]) #issue 571 - doyTmrt = int( - solweigfile.split("_")[-2] - ) # int(filepath_tmrt[-13:-10]) + doyTmrt = int(solweigfile.split("_")[-2]) # int(filepath_tmrt[-13:-10]) hoursTmrt = int(filepath_tmrt[-9:-7]) minuTmrt = int(filepath_tmrt[-7:-5]) @@ -411,9 +389,7 @@ def processAlgorithm(self, parameters, context, feedback): ) ) - feedback.setProgressText( - "Estimating " + thermal_index + " on " + current_time - ) + feedback.setProgressText("Estimating " + thermal_index + " on " + current_time) feedback.setProgressText( "Location: " + str(np.around(lat, decimals=2)) @@ -428,9 +404,7 @@ def processAlgorithm(self, parameters, context, feedback): + "\N{DEGREE SIGN}C" ) feedback.setProgressText( - "Relative Humidity derived from meteorological data is: " - + str(RH) - + "%" + "Relative Humidity derived from meteorological data is: " + str(RH) + "%" ) feedback.setProgressText( "Incoming shortwave radiation derived from meteorological data is: " @@ -444,19 +418,11 @@ def processAlgorithm(self, parameters, context, feedback): filepath = filepath_tmrt.split("Tmrt") # filepath = os.path.dirname(filepath_tmrt) # issue 31 filepath_tmrt.split('Tmrt') # Load Kup, Kdown, Lup, Ldown grids - Kup, rows, cols = load_grid( - filepath[0] + "Kup" + filepath[1], feedback - ) - Kdown, _, __ = load_grid( - filepath[0] + "Kdown" + filepath[1], feedback - ) - Kdiff, _, __ = load_grid( - filepath[0] + "Kdiff" + filepath[1], feedback - ) + Kup, rows, cols = load_grid(filepath[0] + "Kup" + filepath[1], feedback) + Kdown, _, __ = load_grid(filepath[0] + "Kdown" + filepath[1], feedback) + Kdiff, _, __ = load_grid(filepath[0] + "Kdiff" + filepath[1], feedback) Lup, _, __ = load_grid(filepath[0] + "Lup" + filepath[1], feedback) - Ldown, _, __ = load_grid( - filepath[0] + "Ldown" + filepath[1], feedback - ) + Ldown, _, __ = load_grid(filepath[0] + "Ldown" + filepath[1], feedback) settingsSolweig = np.loadtxt( filepath[0] + "/treeplantersettings.txt", skiprows=1, @@ -491,12 +457,8 @@ def processAlgorithm(self, parameters, context, feedback): mbody = self.parameterAsDouble( parameters, self.WEIGHT, context ) # Body weight in kg - clo = self.parameterAsDouble( - parameters, self.CLO, context - ) # Clothing in clo - age = self.parameterAsDouble( - parameters, self.AGE, context - ) # Age in years + clo = self.parameterAsDouble(parameters, self.CLO, context) # Clothing in clo + age = self.parameterAsDouble(parameters, self.AGE, context) # Age in years activity = self.parameterAsDouble( parameters, self.ACTIVITY, context ) # Activity in watt @@ -505,13 +467,10 @@ def processAlgorithm(self, parameters, context, feedback): ) # Sex, #TODO CHECK SO SAME FOR PET AND COMFA if tcType == 0: - feedback.setProgressText( - "Calculating PET for all ground level pixels" - ) + feedback.setProgressText("Calculating PET for all ground level pixels") # Other PET variables ht = ( - self.parameterAsDouble(parameters, self.HEIGHT, context) - / 100.0 + self.parameterAsDouble(parameters, self.HEIGHT, context) / 100.0 ) # Body height in meters pet.mbody = mbody @@ -524,19 +483,13 @@ def processAlgorithm(self, parameters, context, feedback): # Wind speed # WsPET = (10. / sensorheight) ** 0.2 * wsGrid - result = pet.calculate_PET_grid( - Ta, RH, tmrtGrid, wsGrid, pet, feedback - ) + result = pet.calculate_PET_grid(Ta, RH, tmrtGrid, wsGrid, pet, feedback) elif tcType == 1: - feedback.setProgressText( - "Calculating UTCI for all ground level pixels" - ) + feedback.setProgressText("Calculating UTCI for all ground level pixels") # Recalculating wind speed based on power law WsUTCI = (10.0 / sensorheight) ** 0.2 * wsGrid - result = utci.utci_calculator_grid( - Ta, RH, tmrtGrid, WsUTCI, feedback - ) + result = utci.utci_calculator_grid(Ta, RH, tmrtGrid, WsUTCI, feedback) elif tcType == 2: # If True = COMFA-kid (Cheng & Brown, 2020), if False = regular COMFA @@ -595,20 +548,18 @@ def processAlgorithm(self, parameters, context, feedback): TREMIT = np.zeros((Rabs.shape[0], Rabs.shape[1])) for y in np.arange(Rabs.shape[0]): for x in np.arange(Rabs.shape[1]): - MET[y, x], CONV[y, x], EVAP[y, x], TREMIT[y, x] = ( - COMFA_BUDGET( - Mact, - Ta, - RH, - WsCOMFA[y, x], - va, - rco, - rcvo, - mbody, - ht, - age, - comfa_kid, - ) + MET[y, x], CONV[y, x], EVAP[y, x], TREMIT[y, x] = COMFA_BUDGET( + Mact, + Ta, + RH, + WsCOMFA[y, x], + va, + rco, + rcvo, + mbody, + ht, + age, + comfa_kid, ) result = MET + Rabs - CONV - EVAP - TREMIT diff --git a/postprocessor/suewsanalyzer_algorithm.py b/postprocessor/suewsanalyzer_algorithm.py index be98a70..8e52bb2 100644 --- a/postprocessor/suewsanalyzer_algorithm.py +++ b/postprocessor/suewsanalyzer_algorithm.py @@ -188,25 +188,17 @@ def processAlgorithm(self, parameters, context, feedback): # InputParameters suewsNL = self.parameterAsString(parameters, self.SUEWS_NL, context) variaIn = self.parameterAsString(parameters, self.VARIA_IN, context) - startday = self.parameterAsString( - parameters, self.DATEINISTART, context - ) + startday = self.parameterAsString(parameters, self.DATEINISTART, context) endday = self.parameterAsString(parameters, self.DATEINIEND, context) inputPolygonlayer = self.parameterAsVectorLayer( parameters, self.INPUT_POLYGONLAYER, context ) idField = self.parameterAsFields(parameters, self.ID_FIELD, context) irreg = self.parameterAsBool(parameters, self.IRREGULAR, context) - statTypeStr = self.parameterAsString( - parameters, self.STAT_TYPE, context - ) - dayTypeStr = self.parameterAsString( - parameters, self.TIME_OF_DAY, context - ) + statTypeStr = self.parameterAsString(parameters, self.STAT_TYPE, context) + dayTypeStr = self.parameterAsString(parameters, self.TIME_OF_DAY, context) pixelsize = self.parameterAsDouble(parameters, self.PIXELSIZE, context) - addAttributes = self.parameterAsBool( - parameters, self.ADD_ATTRIBUTES, context - ) + addAttributes = self.parameterAsBool(parameters, self.ADD_ATTRIBUTES, context) outputStat = self.parameterAsOutputLayer( parameters, self.SUEWS_GRID_OUT, context ) @@ -235,16 +227,12 @@ def processAlgorithm(self, parameters, context, feedback): f"Selected year '{self.YYYY}' is not present in the data.\n Availible years are {str(years)}" ) - self.id = int( - variaIn - ) # self.dlg.comboBox_SpatialVariable.currentIndex() - 1 + self.id = int(variaIn) # self.dlg.comboBox_SpatialVariable.currentIndex() - 1 poly_field = idField if startday >= endday: - raise QgsProcessingException( - "Start date is greater or equal than end date" - ) + raise QgsProcessingException("Start date is greater or equal than end date") # load, cut data and calculate statistics statvectemp = [0] @@ -261,9 +249,7 @@ def processAlgorithm(self, parameters, context, feedback): else: polygonpath = path - grid_list = [ - feature[poly_field[0]] for feature in vlayer.getFeatures() - ] + grid_list = [feature[poly_field[0]] for feature in vlayer.getFeatures()] for grid in grid_list: @@ -305,9 +291,7 @@ def processAlgorithm(self, parameters, context, feedback): statresult = np.nanmedian(vardata) suffix = "_median" if statTypeStr == "4": - statresult = np.nanpercentile(vardata, 75) - np.percentile( - vardata, 25 - ) + statresult = np.nanpercentile(vardata, 75) - np.percentile(vardata, 25) suffix = "_IQR" statvectemp = np.vstack((statvectemp, statresult)) idvec = np.vstack((idvec, int(grid))) @@ -341,14 +325,10 @@ def processAlgorithm(self, parameters, context, feedback): resy = np.abs(geom[0][0][0][1] - geom[0][0][2][1]) # y if not resx == resy: - raise QgsProcessingException( - "Polygons not squared in current CRS" - ) + raise QgsProcessingException("Polygons not squared in current CRS") return - if os.path.isfile( - self.plugin_dir + "/tempgrid.tif" - ): # response to issue 103 + if os.path.isfile(self.plugin_dir + "/tempgrid.tif"): # response to issue 103 try: shutil.rmtree(self.plugin_dir + "/tempgrid.tif") except OSError: @@ -437,9 +417,7 @@ def addattributes(self, vlayer, matdata, header): caps = vlayer.dataProvider().capabilities() if caps & QgsVectorDataProvider.Capability.AddAttributes: - vlayer.dataProvider().addAttributes( - [QgsField(header, QVariant.Double)] - ) + vlayer.dataProvider().addAttributes([QgsField(header, QVariant.Double)]) attr_dict = {} for y in range(0, matdata.shape[0]): attr_dict.clear() diff --git a/postprocessor/targetanalyzer_algorithm.py b/postprocessor/targetanalyzer_algorithm.py index 53164c1..27f6e17 100644 --- a/postprocessor/targetanalyzer_algorithm.py +++ b/postprocessor/targetanalyzer_algorithm.py @@ -91,9 +91,7 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterDateTime( self.SINGLE_DAY, - self.tr( - "Month and day when single night begins (year is irrelevant)" - ), + self.tr("Month and day when single night begins (year is irrelevant)"), QgsProcessingParameterDateTime.Type.Date, ) ) @@ -180,46 +178,30 @@ def initAlgorithm(self, config): def processAlgorithm(self, parameters, context, feedback): # InputParameters - targetIn = self.parameterAsString( - parameters, self.INPUT_FOLDER, context - ) + targetIn = self.parameterAsString(parameters, self.INPUT_FOLDER, context) # uwgOut = self.parameterAsString(parameters, self.OUTPUT_FOLDER, context) # variaIn = self.parameterAsString(parameters, self.VARIA_IN, context) # startday = self.parameterAsString(parameters, self.DATEINISTART, context) - singleNight = self.parameterAsBool( - parameters, self.SINGLE_DAY_BOOL, context - ) + singleNight = self.parameterAsBool(parameters, self.SINGLE_DAY_BOOL, context) # endday = self.parameterAsString(parameters, self.DATEINIEND, context) inputPolygonlayer = self.parameterAsVectorLayer( parameters, self.INPUT_POLYGONLAYER, context ) idField = self.parameterAsFields(parameters, self.ID_FIELD, context) irreg = self.parameterAsBool(parameters, self.IRREGULAR, context) - statTypeStr = self.parameterAsString( - parameters, self.STAT_TYPE, context - ) + statTypeStr = self.parameterAsString(parameters, self.STAT_TYPE, context) # dayTypeStr = self.parameterAsString(parameters, self.TIME_OF_DAY, context) pixelsize = self.parameterAsDouble(parameters, self.PIXELSIZE, context) - addAttributes = self.parameterAsBool( - parameters, self.ADD_ATTRIBUTES, context - ) - outputStat = self.parameterAsOutputLayer( - parameters, self.UWG_GRID_OUT, context - ) - dayTypeStr = self.parameterAsString( - parameters, self.TIME_OF_DAY, context - ) + addAttributes = self.parameterAsBool(parameters, self.ADD_ATTRIBUTES, context) + outputStat = self.parameterAsOutputLayer(parameters, self.UWG_GRID_OUT, context) + dayTypeStr = self.parameterAsString(parameters, self.TIME_OF_DAY, context) feedback.setProgressText("Initializing...") # statType = int(statTypeStr) - feedback.setProgressText( - "Model input directory: " + targetIn + "/input" - ) - feedback.setProgressText( - "Model output directory: " + targetIn + "/output" - ) + feedback.setProgressText("Model input directory: " + targetIn + "/input") + feedback.setProgressText("Model output directory: " + targetIn + "/output") cfM = read_config(targetIn + "/config.ini") @@ -245,9 +227,7 @@ def processAlgorithm(self, parameters, context, feedback): # start = datetime.date(int(yyyy), int(mm), int(dd)) # end = start + datetime.timedelta(days=int(nDays)) - start = datetime.datetime.strptime( - cfM["date1"], "%Y,%m,%d,%H" - ).strftime( + start = datetime.datetime.strptime(cfM["date1"], "%Y,%m,%d,%H").strftime( "%Y-%m-%d" ) # string end = datetime.datetime.strptime(cfM["date2"], "%Y,%m,%d,%H").strftime( @@ -261,9 +241,7 @@ def processAlgorithm(self, parameters, context, feedback): ) if singleNight: - singledate = self.parameterAsString( - parameters, self.SINGLE_DAY, context - ) + singledate = self.parameterAsString(parameters, self.SINGLE_DAY, context) startDate = datetime.datetime.strptime(singledate, "%Y-%m-%d") mm = startDate.month dd = startDate.day @@ -334,12 +312,7 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText("Processing grid: " + str(gid)) datawhole = np.genfromtxt( - targetIn - + "/output/" - + runName - + "_" - + gid - + "_UMEP_TARGET.txt", + targetIn + "/output/" + runName + "_" + gid + "_UMEP_TARGET.txt", skip_header=1, ) @@ -421,13 +394,9 @@ def processAlgorithm(self, parameters, context, feedback): resy = np.abs(geom[0][0][0][1] - geom[0][0][2][1]) # y if not resx == resy: - raise QgsProcessingException( - "Polygons not squared in current CRS" - ) + raise QgsProcessingException("Polygons not squared in current CRS") - if os.path.isfile( - self.plugin_dir + "/tempgrid.tif" - ): # response to issue 103 + if os.path.isfile(self.plugin_dir + "/tempgrid.tif"): # response to issue 103 try: shutil.rmtree(self.plugin_dir + "/tempgrid.tif") except OSError: @@ -484,9 +453,7 @@ def rasterize( # Create the target raster layer cols = int((xmax - xmin) / resolution) rows = int((ymax - ymin) / resolution) # issue 164 - trgt = gdal.GetDriverByName("GTiff").Create( - dst, cols, rows, 1, GDT_Float32 - ) + trgt = gdal.GetDriverByName("GTiff").Create(dst, cols, rows, 1, GDT_Float32) trgt.SetGeoTransform((xmin, resolution, 0, ymax, 0, -resolution)) # Add crs @@ -516,9 +483,7 @@ def addattributes(self, vlayer, matdata, header): caps = vlayer.dataProvider().capabilities() if caps & QgsVectorDataProvider.Capability.AddAttributes: - vlayer.dataProvider().addAttributes( - [QgsField(header, QVariant.Double)] - ) + vlayer.dataProvider().addAttributes([QgsField(header, QVariant.Double)]) attr_dict = {} for y in range(0, matdata.shape[0]): attr_dict.clear() diff --git a/postprocessor/treeplanter_algorithm.py b/postprocessor/treeplanter_algorithm.py index 616e00c..6877b7d 100644 --- a/postprocessor/treeplanter_algorithm.py +++ b/postprocessor/treeplanter_algorithm.py @@ -286,9 +286,7 @@ def initAlgorithm(self, config): ) self.addParameter( - QgsProcessingParameterFolderDestination( - self.OUTPUT_DIR, "Output folder" - ) + QgsProcessingParameterFolderDestination(self.OUTPUT_DIR, "Output folder") ) # Advanced parameters @@ -300,8 +298,7 @@ def initAlgorithm(self, config): minValue=1, ) iterations.setFlags( - iterations.flags() - | QgsProcessingParameterDefinition.Flag.FlagAdvanced + iterations.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced ) self.addParameter(iterations) @@ -313,21 +310,17 @@ def initAlgorithm(self, config): defaultValue=True, ) includeOutside.setFlags( - includeOutside.flags() - | QgsProcessingParameterDefinition.Flag.FlagAdvanced + includeOutside.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced ) self.addParameter(includeOutside) randomStarting = QgsProcessingParameterBoolean( self.RANDOM_STARTING, - self.tr( - "Use random starting positions in the hill climbing algorithm" - ), + self.tr("Use random starting positions in the hill climbing algorithm"), defaultValue=False, ) randomStarting.setFlags( - randomStarting.flags() - | QgsProcessingParameterDefinition.Flag.FlagAdvanced + randomStarting.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced ) self.addParameter(randomStarting) @@ -337,8 +330,7 @@ def initAlgorithm(self, config): defaultValue=False, ) greedyAlgorithm.setFlags( - greedyAlgorithm.flags() - | QgsProcessingParameterDefinition.Flag.FlagAdvanced + greedyAlgorithm.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced ) self.addParameter(greedyAlgorithm) @@ -353,18 +345,14 @@ def processAlgorithm(self, parameters, context, feedback): # START_HOUR = 'START_HOUR' # END_HOUR = 'END_HOUR' - infolder = self.parameterAsString( - parameters, self.SOLWEIG_DIR, context - ) + infolder = self.parameterAsString(parameters, self.SOLWEIG_DIR, context) ttype = self.parameterAsString(parameters, self.TTYPE, context) height = self.parameterAsDouble(parameters, self.HEIGHT, context) dia = self.parameterAsDouble(parameters, self.DIA, context) trunk = self.parameterAsDouble(parameters, self.TRUNK, context) transVeg = self.parameterAsDouble(parameters, self.TRANS_VEG, context) nTree = self.parameterAsInt(parameters, self.NTREE, context) - transVeg = ( - self.parameterAsDouble(parameters, self.TRANS_VEG, context) / 100 - ) + transVeg = self.parameterAsDouble(parameters, self.TRANS_VEG, context) / 100 # INPUT_MET = self.parameterAsString(parameters, self.INPUT_MET, context) INPUT_MET = infolder + "/metforcing.txt" ITERATIONS = self.parameterAsInt(parameters, self.ITERATIONS, context) @@ -374,18 +362,14 @@ def processAlgorithm(self, parameters, context, feedback): outside_selected = self.parameterAsBoolean( parameters, self.INCLUDE_OUTSIDE, context ) - greedy = self.parameterAsBoolean( - parameters, self.GREEDY_ALGORITHM, context - ) + greedy = self.parameterAsBoolean(parameters, self.GREEDY_ALGORITHM, context) starting_algorithm = self.parameterAsBoolean( parameters, self.RANDOM_STARTING, context ) # inputPolygonlayer = parameters[self.INPUT_POLYGONLAYER] inputPolygonlayer = ( - self.parameterAsVectorLayer( - parameters, self.INPUT_POLYGONLAYER, context - ) + self.parameterAsVectorLayer(parameters, self.INPUT_POLYGONLAYER, context) .dataProvider() .dataSourceUri() ) @@ -395,20 +379,14 @@ def processAlgorithm(self, parameters, context, feedback): # outputTMRT = self.parameterAsOutputLayer(parameters, self.OUTPUT_TMRT, context) # outputOCCURRENCE = self.parameterAsOutputLayer(parameters, self.OUTPUT_OCCURRENCE, context) - outputDir = self.parameterAsString( - parameters, self.OUTPUT_DIR, context - ) + outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): os.mkdir(outputDir) - outputCDSM = self.parameterAsBool( - parameters, self.OUTPUT_CDSM, context - ) - outputPoint = self.parameterAsBool( - parameters, self.OUTPUT_POINTFILE, context - ) + outputCDSM = self.parameterAsBool(parameters, self.OUTPUT_CDSM, context) + outputPoint = self.parameterAsBool(parameters, self.OUTPUT_POINTFILE, context) outputOccurrence = self.parameterAsBool( parameters, self.OUTPUT_OCCURRENCE, context ) @@ -469,12 +447,8 @@ def processAlgorithm(self, parameters, context, feedback): ) if not outside_selected: - feedback.setProgressText( - "Tree shade ineffective outside planting area..." - ) - tree_input.buildings = ( - tree_input.buildings * tree_input.selected_area - ) + feedback.setProgressText("Tree shade ineffective outside planting area...") + tree_input.buildings = tree_input.buildings * tree_input.selected_area for i in np.arange(tree_input.tmrt_ts.shape[2]): tree_input.tmrt_ts[:, :, i] = ( tree_input.tmrt_ts[:, :, i] * tree_input.selected_area @@ -515,9 +489,7 @@ def processAlgorithm(self, parameters, context, feedback): cdsm_ = np.zeros((tree_input.rows, tree_input.cols)) # Empty cdsm tdsm_ = np.zeros((tree_input.rows, tree_input.cols)) # Empty tdsm - dsm_empty = np.ones( - (tree_input.rows, tree_input.cols) - ) # Empty dsm raster + dsm_empty = np.ones((tree_input.rows, tree_input.cols)) # Empty dsm raster buildings_empty = np.ones( (tree_input.rows, tree_input.cols) ) # Empty building raster @@ -594,9 +566,7 @@ def processAlgorithm(self, parameters, context, feedback): i_c += 1 ## Regional groups for tree shadows - shadow_rg = Regional_groups( - r_range, treesh_sum_sh, treesh_ts2, tmrt_1d - ) + shadow_rg = Regional_groups(r_range, treesh_sum_sh, treesh_ts2, tmrt_1d) # Create rasters for new tree; shadows and Tmrt treerasters = Treerasters( @@ -637,14 +607,11 @@ def processAlgorithm(self, parameters, context, feedback): ) if nTree == 1: - t_y, t_x = np.where( - treerasters.d_tmrt == np.max(treerasters.d_tmrt) - ) + t_y, t_x = np.where(treerasters.d_tmrt == np.max(treerasters.d_tmrt)) else: possible_locations = np.sum(treerasters.d_tmrt > 0) feedback.setProgressText( - str(possible_locations) - + " possible locations for trees..." + str(possible_locations) + " possible locations for trees..." ) # Running tree planter t_y, t_x, tmrt_max, t_y_all, t_x_all = ( @@ -666,9 +633,7 @@ def processAlgorithm(self, parameters, context, feedback): # Create occurrence map t_y_all += cropped_rasters.clip_rows[0] t_x_all += cropped_rasters.clip_rows[0] - occurrence_map = np.zeros( - (tree_input.rows, tree_input.cols) - ) + occurrence_map = np.zeros((tree_input.rows, tree_input.cols)) for row in np.arange(t_y_all.shape[0]): for col in np.arange(t_y_all.shape[1]): occurrence_map[ @@ -678,9 +643,7 @@ def processAlgorithm(self, parameters, context, feedback): occurrence_map /= ITERATIONS # Save occurrence map as raster - saveraster( - tree_input.dataSet, outputOccurrence, occurrence_map - ) + saveraster(tree_input.dataSet, outputOccurrence, occurrence_map) # Optimal locations for CDSM and point outputs t_y = t_y + cropped_rasters.clip_rows[0] diff --git a/postprocessor/urock_analyser_algorithm.py b/postprocessor/urock_analyser_algorithm.py index bc7e5ca..c3d564a 100644 --- a/postprocessor/urock_analyser_algorithm.py +++ b/postprocessor/urock_analyser_algorithm.py @@ -112,9 +112,7 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterBoolean( self.IS_STREAM, - self.tr( - "Plot streams instead of arrows (works only for cubic voxels" - ), + self.tr("Plot streams instead of arrows (works only for cubic voxels"), defaultValue=False, ) ) @@ -142,9 +140,7 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterField( self.ID_FIELD_POLYGONS, - self.tr( - "Polygons ID field (used if mutiple polygons is present)" - ), + self.tr("Polygons ID field (used if mutiple polygons is present)"), None, self.INPUT_POLYGONS, QgsProcessingParameterField.DataType.Numeric, @@ -198,9 +194,7 @@ def processAlgorithm(self, parameters, context, feedback): # Get the default value of the Java environment path if already exists javaDirDefault = getJavaDir(plugin_directory) - if ( - not javaDirDefault - ): # Raise an error if could not find a Java installation + if not javaDirDefault: # Raise an error if could not find a Java installation raise QgsProcessingException("No Java installation found") elif ("Program Files (x86)" in javaDirDefault) and ( struct.calcsize("P") * 8 != 32 @@ -213,9 +207,7 @@ def processAlgorithm(self, parameters, context, feedback): ) else: # Set a Java dir if not exist and save it into a file in the plugin repository setJavaDir(javaDirDefault) - saveJavaDir( - javaPath=javaDirDefault, pluginDirectory=plugin_directory - ) + saveJavaDir(javaPath=javaDirDefault, pluginDirectory=plugin_directory) # Defines java environmenet variable javaEnvVar = javaDirDefault @@ -226,12 +218,8 @@ def processAlgorithm(self, parameters, context, feedback): ) # Get line layer, id field name and then file directory and crs - inputLines = self.parameterAsVectorLayer( - parameters, self.INPUT_LINES, context - ) - idLines = self.parameterAsString( - parameters, self.ID_FIELD_LINES, context - ) + inputLines = self.parameterAsVectorLayer(parameters, self.INPUT_LINES, context) + idLines = self.parameterAsString(parameters, self.ID_FIELD_LINES, context) if inputLines: # Test the number of vertices (points) per line features = inputLines.getFeatures() @@ -271,9 +259,7 @@ def processAlgorithm(self, parameters, context, feedback): inputPolygons = self.parameterAsVectorLayer( parameters, self.INPUT_POLYGONS, context ) - idPolygons = self.parameterAsString( - parameters, self.ID_FIELD_POLYGONS, context - ) + idPolygons = self.parameterAsString(parameters, self.ID_FIELD_POLYGONS, context) if inputPolygons: polygons_file = str(inputPolygons.dataProvider().dataSourceUri()) if polygons_file.count("|") > 0: diff --git a/postprocessor/uwganalyzer_algorithm.py b/postprocessor/uwganalyzer_algorithm.py index 30a45e0..7d7e559 100644 --- a/postprocessor/uwganalyzer_algorithm.py +++ b/postprocessor/uwganalyzer_algorithm.py @@ -92,9 +92,7 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterDateTime( self.SINGLE_DAY, - self.tr( - "Month and day when single night begins (year is irrelevant)" - ), + self.tr("Month and day when single night begins (year is irrelevant)"), QgsProcessingParameterDateTime.Type.Date, ) ) @@ -169,31 +167,21 @@ def processAlgorithm(self, parameters, context, feedback): # InputParameters uwgIn = self.parameterAsString(parameters, self.INPUT_FOLDER, context) - uwgOut = self.parameterAsString( - parameters, self.OUTPUT_FOLDER, context - ) + uwgOut = self.parameterAsString(parameters, self.OUTPUT_FOLDER, context) # variaIn = self.parameterAsString(parameters, self.VARIA_IN, context) # startday = self.parameterAsString(parameters, self.DATEINISTART, context) - singleNight = self.parameterAsBool( - parameters, self.SINGLE_DAY_BOOL, context - ) + singleNight = self.parameterAsBool(parameters, self.SINGLE_DAY_BOOL, context) # endday = self.parameterAsString(parameters, self.DATEINIEND, context) inputPolygonlayer = self.parameterAsVectorLayer( parameters, self.INPUT_POLYGONLAYER, context ) idField = self.parameterAsFields(parameters, self.ID_FIELD, context) irreg = self.parameterAsBool(parameters, self.IRREGULAR, context) - statTypeStr = self.parameterAsString( - parameters, self.STAT_TYPE, context - ) + statTypeStr = self.parameterAsString(parameters, self.STAT_TYPE, context) # dayTypeStr = self.parameterAsString(parameters, self.TIME_OF_DAY, context) pixelsize = self.parameterAsDouble(parameters, self.PIXELSIZE, context) - addAttributes = self.parameterAsBool( - parameters, self.ADD_ATTRIBUTES, context - ) - outputStat = self.parameterAsOutputLayer( - parameters, self.UWG_GRID_OUT, context - ) + addAttributes = self.parameterAsBool(parameters, self.ADD_ATTRIBUTES, context) + outputStat = self.parameterAsOutputLayer(parameters, self.UWG_GRID_OUT, context) feedback.setProgressText("Initializing...") @@ -229,9 +217,7 @@ def processAlgorithm(self, parameters, context, feedback): ) if singleNight: - singledate = self.parameterAsString( - parameters, self.SINGLE_DAY, context - ) + singledate = self.parameterAsString(parameters, self.SINGLE_DAY, context) startDate = datetime.datetime.strptime(singledate, "%Y-%m-%d") mm = startDate.month dd = startDate.day @@ -386,13 +372,9 @@ def processAlgorithm(self, parameters, context, feedback): resy = np.abs(geom[0][0][0][1] - geom[0][0][2][1]) # y if not resx == resy: - raise QgsProcessingException( - "Polygons not squared in current CRS" - ) + raise QgsProcessingException("Polygons not squared in current CRS") - if os.path.isfile( - self.plugin_dir + "/tempgrid.tif" - ): # response to issue 103 + if os.path.isfile(self.plugin_dir + "/tempgrid.tif"): # response to issue 103 try: shutil.rmtree(self.plugin_dir + "/tempgrid.tif") except OSError: @@ -451,9 +433,7 @@ def rasterize( cols = int((xmax - xmin) / resolution) # rows = int((ymax - ymin)/resolution) + 1 rows = int((ymax - ymin) / resolution) # issue 164 - trgt = gdal.GetDriverByName("GTiff").Create( - dst, cols, rows, 1, GDT_Float32 - ) + trgt = gdal.GetDriverByName("GTiff").Create(dst, cols, rows, 1, GDT_Float32) trgt.SetGeoTransform((xmin, resolution, 0, ymax, 0, -resolution)) # Add crs @@ -483,9 +463,7 @@ def addattributes(self, vlayer, matdata, header): caps = vlayer.dataProvider().capabilities() if caps & QgsVectorDataProvider.Capability.AddAttributes: - vlayer.dataProvider().addAttributes( - [QgsField(header, QVariant.Double)] - ) + vlayer.dataProvider().addAttributes([QgsField(header, QVariant.Double)]) attr_dict = {} for y in range(0, matdata.shape[0]): attr_dict.clear() diff --git a/preprocessor/copernicusera5_algorithm.py b/preprocessor/copernicusera5_algorithm.py index 09bbffc..7bcb5e5 100644 --- a/preprocessor/copernicusera5_algorithm.py +++ b/preprocessor/copernicusera5_algorithm.py @@ -85,9 +85,7 @@ class ProcessingCopernicusERA5Algorithm(QgsProcessingAlgorithm): def initAlgorithm(self, config): self.addParameter( - QgsProcessingParameterPoint( - self.INPUT_POINT, self.tr("Point of interest") - ) + QgsProcessingParameterPoint(self.INPUT_POINT, self.tr("Point of interest")) ) self.addParameter( QgsProcessingParameterCrs( @@ -105,9 +103,7 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterNumber( self.DIAG_HEIGHT, - self.tr( - "Height above ground level to diagnose forcing variables (m)" - ), + self.tr("Height above ground level to diagnose forcing variables (m)"), QgsProcessingParameterNumber.Type.Double, QVariant(100), minValue=2, @@ -132,20 +128,12 @@ def processAlgorithm(self, parameters, context, feedback): ) # InputParameters - inputPoint = self.parameterAsPoint( - parameters, self.INPUT_POINT, context - ) + inputPoint = self.parameterAsPoint(parameters, self.INPUT_POINT, context) inputCRS = self.parameterAsCrs(parameters, self.CRS, context) - startDate = self.parameterAsString( - parameters, self.DATEINISTART, context - ) + startDate = self.parameterAsString(parameters, self.DATEINISTART, context) endDate = self.parameterAsString(parameters, self.DATEINIEND, context) - outputDir = self.parameterAsString( - parameters, self.OUTPUT_DIR, context - ) - diagHeight = self.parameterAsDouble( - parameters, self.DIAG_HEIGHT, context - ) + outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) + diagHeight = self.parameterAsDouble(parameters, self.DIAG_HEIGHT, context) if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): @@ -176,9 +164,7 @@ def processAlgorithm(self, parameters, context, feedback): transform = osr.CoordinateTransformation(old_cs, new_cs) - latlon = ogr.CreateGeometryFromWkt( - "POINT (" + str(x) + " " + str(y) + ")" - ) + latlon = ogr.CreateGeometryFromWkt("POINT (" + str(x) + " " + str(y) + ")") latlon.Transform(transform) gdalver = float(gdal.__version__[0]) @@ -199,9 +185,7 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText("Output folder = " + str(outputDir)) if startDate >= endDate: - raise QgsProcessingException( - "Start date is greater or equal than end date" - ) + raise QgsProcessingException("Start date is greater or equal than end date") logger_sp = logging.getLogger("SuPy") logger_sp.disabled = True diff --git a/preprocessor/dsm_generator_algorithm.py b/preprocessor/dsm_generator_algorithm.py index 64abf03..35c3125 100644 --- a/preprocessor/dsm_generator_algorithm.py +++ b/preprocessor/dsm_generator_algorithm.py @@ -141,9 +141,7 @@ def initAlgorithm(self, config): ) ) - self.addParameter( - QgsProcessingParameterExtent(self.EXTENT, self.tr("Extent")) - ) + self.addParameter(QgsProcessingParameterExtent(self.EXTENT, self.tr("Extent"))) self.addParameter( QgsProcessingParameterDistance( @@ -174,15 +172,11 @@ def initAlgorithm(self, config): def processAlgorithm(self, parameters, context, feedback): # Input data - demlayer = self.parameterAsRasterLayer( - parameters, self.INPUT_DEM, context - ) + demlayer = self.parameterAsRasterLayer(parameters, self.INPUT_DEM, context) shapelayer = self.parameterAsVectorLayer( parameters, self.INPUT_POLYGONLAYER, context ) - heightField = self.parameterAsFields( - parameters, self.INPUT_FIELD, context - ) + heightField = self.parameterAsFields(parameters, self.INPUT_FIELD, context) # OSM switches useOsm = self.parameterAsBool(parameters, self.USE_OSM, context) @@ -199,9 +193,7 @@ def processAlgorithm(self, parameters, context, feedback): ) # Output data - outputDSM = self.parameterAsOutputLayer( - parameters, self.OUTPUT_DSM, context - ) + outputDSM = self.parameterAsOutputLayer(parameters, self.OUTPUT_DSM, context) outputShape = self.parameterAsOutputLayer( parameters, self.OUTPUT_POLYGON, context ) @@ -353,8 +345,7 @@ def processAlgorithm(self, parameters, context, feedback): root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) temp_dir = ( - os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - + "/temp/" + os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "/temp/" ) if useOsm: @@ -450,9 +441,7 @@ def processAlgorithm(self, parameters, context, feedback): osmFile.write(osmXml) feedback.setProgressText("Downloading OSM data from " + urlStr) if os.fstat(osmFile.fileno()).st_size < 1: - raise QgsProcessingException( - "Error! No OSM data available." - ) + raise QgsProcessingException("Error! No OSM data available.") osmFile.close() @@ -491,9 +480,7 @@ def processAlgorithm(self, parameters, context, feedback): # Renames attribute fields def renameField(srcLayer, oldFieldName, newFieldName): - ds = gdal.OpenEx( - srcLayer.source(), gdal.OF_VECTOR | gdal.OF_UPDATE - ) + ds = gdal.OpenEx(srcLayer.source(), gdal.OF_VECTOR | gdal.OF_UPDATE) ds.ExecuteSQL( "ALTER TABLE {} RENAME COLUMN {} TO {}".format( srcLayer.name(), oldFieldName, newFieldName @@ -521,9 +508,7 @@ def renameField(srcLayer, oldFieldName, newFieldName): counterNone = 0 counter = 0 for feature in vlayer.getFeatures(): - if feature[ - "height" - ]: # Tries first with actual building height data + if feature["height"]: # Tries first with actual building height data try: feature.setAttribute( feature.fieldNameIndex("bld_height"), @@ -545,13 +530,8 @@ def renameField(srcLayer, oldFieldName, newFieldName): "bld_levels" ]: # If no height or building height then make building height from stories try: - temp = ( - float(str(feature["bld_levels"])) - * buildingLevelHeight - ) - feature.setAttribute( - feature.fieldNameIndex("bld_height"), temp - ) + temp = float(str(feature["bld_levels"])) * buildingLevelHeight + feature.setAttribute(feature.fieldNameIndex("bld_height"), temp) except: counterNone += 1 else: @@ -594,9 +574,7 @@ def renameField(srcLayer, oldFieldName, newFieldName): for f in vlayer.getFeatures(): scope.setFeature(f) exp = QgsExpression("stats_mean + " + flname) - f.setAttribute( - f.fieldNameIndex("height_asl"), exp.evaluate(context) - ) + f.setAttribute(f.fieldNameIndex("height_asl"), exp.evaluate(context)) vlayer.updateFeature(f) vlayer.commitChanges() @@ -617,9 +595,9 @@ def renameField(srcLayer, oldFieldName, newFieldName): str(sortPoly), str(osmPolygonPath), options=sort_options ) else: - query = safe( - 'SELECT * FROM "{table}" ORDER BY height_asl ASC' - ).format(table=safe(str(polygon_ln))) + query = safe('SELECT * FROM "{table}" ORDER BY height_asl ASC').format( + table=safe(str(polygon_ln)) + ) sort_options = gdal.VectorTranslateOptions( options=["-select", "height_asl", "-sql", safe(query)] @@ -724,9 +702,7 @@ def renameField(srcLayer, oldFieldName, newFieldName): sqUnit = 1 elif dem_unit in possible_units_feet: sqUnit = 10.76 - if ( - int(geom.area()) > 50000 * sqUnit - ): # Deleting large polygons + if int(geom.area()) > 50000 * sqUnit: # Deleting large polygons vlayer.deleteFeature(f.id()) vlayer.updateFeature(f) diff --git a/preprocessor/imagemorphparms_algorithm.py b/preprocessor/imagemorphparms_algorithm.py index ab87ada..2040a93 100644 --- a/preprocessor/imagemorphparms_algorithm.py +++ b/preprocessor/imagemorphparms_algorithm.py @@ -148,9 +148,7 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterBoolean( self.USE_DSMBUILD, - self.tr( - "Raster DSM (only 3D building or vegetation objects) exist" - ), + self.tr("Raster DSM (only 3D building or vegetation objects) exist"), defaultValue=False, ) ) @@ -184,9 +182,7 @@ def initAlgorithm(self, config): ) ) self.addParameter( - QgsProcessingParameterString( - self.FILE_PREFIX, self.tr("File prefix") - ) + QgsProcessingParameterString(self.FILE_PREFIX, self.tr("File prefix")) ) self.addParameter( QgsProcessingParameterBoolean( @@ -215,9 +211,7 @@ def initAlgorithm(self, config): defaultValue=False, optional=True, ) - ss.setFlags( - ss.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) + ss.setFlags(ss.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) self.addParameter(ss) sscdsm = QgsProcessingParameterRasterLayer( self.INPUT_CDSM, self.tr("Raster vegetation DSM (CDSM)"), "", True @@ -238,31 +232,17 @@ def processAlgorithm(self, parameters, context, feedback): parameters, self.INPUT_POLYGONLAYER, context ) idField = self.parameterAsFields(parameters, self.ID_FIELD, context) - searchMethod = self.parameterAsString( - parameters, self.SERACH_METHOD, context - ) - inputDistance = self.parameterAsDouble( - parameters, self.INPUT_DISTANCE, context - ) - inputInterval = self.parameterAsDouble( - parameters, self.INPUT_INTERVAL, context - ) - useDsmBuild = self.parameterAsBool( - parameters, self.USE_DSMBUILD, context - ) + searchMethod = self.parameterAsString(parameters, self.SERACH_METHOD, context) + inputDistance = self.parameterAsDouble(parameters, self.INPUT_DISTANCE, context) + inputInterval = self.parameterAsDouble(parameters, self.INPUT_INTERVAL, context) + useDsmBuild = self.parameterAsBool(parameters, self.USE_DSMBUILD, context) dsmlayer = None demlayer = None ro = self.parameterAsString(parameters, self.ROUGH, context) - filePrefix = self.parameterAsString( - parameters, self.FILE_PREFIX, context - ) + filePrefix = self.parameterAsString(parameters, self.FILE_PREFIX, context) attrTable = self.parameterAsBool(parameters, self.ATTR_TABLE, context) - ignoreNodata = self.parameterAsBool( - parameters, self.IGNORE_NODATA, context - ) - outputDir = self.parameterAsString( - parameters, self.OUTPUT_DIR, context - ) + ignoreNodata = self.parameterAsBool(parameters, self.IGNORE_NODATA, context) + outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) calcSS = self.parameterAsBool(parameters, self.CALC_SS, context) if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": @@ -274,7 +254,9 @@ def processAlgorithm(self, parameters, context, feedback): pre = filePrefix headerAniso = " Wd pai fai zH zHmax zHstd zd z0 noOfPixels" numformat = "%3d %4.3f %4.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.0f" - headerIso = " id pai fai zH zHmax zHstd zd z0 wai " # moved inside loop + headerIso = ( + " id pai fai zH zHmax zHstd zd z0 wai " # moved inside loop + ) numformat2 = "%3d %4.3f %4.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f" # adding parameters for SUEWS/SS @@ -347,10 +329,7 @@ def processAlgorithm(self, parameters, context, feedback): prov.crs(), "ESRI shapefile", ) - if ( - writer.hasError() - != QgsVectorFileWriter.WriterError.NoError - ): + if writer.hasError() != QgsVectorFileWriter.WriterError.NoError: raise QgsProcessingException( "Error when creating shapefile: ", str(writer.hasError()), @@ -515,9 +494,7 @@ def processAlgorithm(self, parameters, context, feedback): options=clip_spec, ) bigraster = None - dataseti = gdal.Open( - self.plugin_dir + "/data/clipcdsm.tif" - ) + dataseti = gdal.Open(self.plugin_dir + "/data/clipcdsm.tif") ndCDSM = dataseti.GetRasterBand(1).GetNoDataValue() cdsm_array = dataseti.ReadAsArray().astype(float) @@ -529,14 +506,10 @@ def processAlgorithm(self, parameters, context, feedback): "NoData in DSM layer not set. Tick off 'Ignore NoData pixels' to make use of this tool or assign NoData value to your raster data." ) else: - feedback.setProgressText( - "NoData-value in DSM-layer: " + str(nd) - ) + feedback.setProgressText("NoData-value in DSM-layer: " + str(nd)) nodata_test = dsm_array == nd if ignoreNodata: - if np.sum(dsm_array) == ( - dsm_array.shape[0] * dsm_array.shape[1] * nd - ): + if np.sum(dsm_array) == (dsm_array.shape[0] * dsm_array.shape[1] * nd): feedback.setProgressText( "Grid " + str(f.attributes()[idx]) @@ -545,9 +518,7 @@ def processAlgorithm(self, parameters, context, feedback): cal = 0 else: feedback.setProgressText( - "Grid " - + str(f.attributes()[idx]) - + " being calculated." + "Grid " + str(f.attributes()[idx]) + " being calculated." ) cal = 1 else: @@ -561,9 +532,7 @@ def processAlgorithm(self, parameters, context, feedback): else: cal = 1 feedback.setProgressText( - "Grid " - + str(f.attributes()[idx]) - + " being calculated." + "Grid " + str(f.attributes()[idx]) + " being calculated." ) if cal == 1: @@ -755,9 +724,7 @@ def addattr(self, vlayer, matdata, header, pre, feedback, idx): wo = np.where(f.attributes()[idx] == matdata[:, 0]) if wo[0] >= 0: for x in range(1, matdata.shape[1]): - attr_dict[current_index_length + x - 1] = float( - matdata[wo[0], x] - ) + attr_dict[current_index_length + x - 1] = float(matdata[wo[0], x]) vlayer.dataProvider().changeAttributeValues({id: attr_dict}) def name(self): diff --git a/preprocessor/imagemorphparmspoint_algorithm.py b/preprocessor/imagemorphparmspoint_algorithm.py index 123ae3c..dbb598e 100644 --- a/preprocessor/imagemorphparmspoint_algorithm.py +++ b/preprocessor/imagemorphparmspoint_algorithm.py @@ -133,9 +133,7 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterBoolean( self.USE_DSMBUILD, - self.tr( - "Raster DSM (only 3D building or vegetation objects) exist" - ), + self.tr("Raster DSM (only 3D building or vegetation objects) exist"), defaultValue=False, ) ) @@ -171,9 +169,7 @@ def initAlgorithm(self, config): ) ) self.addParameter( - QgsProcessingParameterString( - self.FILE_PREFIX, self.tr("File prefix") - ) + QgsProcessingParameterString(self.FILE_PREFIX, self.tr("File prefix")) ) # self.addParameter(QgsProcessingParameterBoolean(self.SAVE_POINT, # self.tr("Save point of interest as new vector layer"), defaultValue=False)) @@ -205,32 +201,20 @@ def initAlgorithm(self, config): def processAlgorithm(self, parameters, context, feedback): # InputParameters - usePointlayer = self.parameterAsBool( - parameters, self.USE_POINTLAYER, context - ) + usePointlayer = self.parameterAsBool(parameters, self.USE_POINTLAYER, context) inputPoint = None inputPointLayer = self.parameterAsVectorLayer( parameters, self.INPUT_POINTLAYER, context ) - inputDistance = self.parameterAsDouble( - parameters, self.INPUT_DISTANCE, context - ) - inputInterval = self.parameterAsDouble( - parameters, self.INPUT_INTERVAL, context - ) - useDsmBuild = self.parameterAsBool( - parameters, self.USE_DSMBUILD, context - ) + inputDistance = self.parameterAsDouble(parameters, self.INPUT_DISTANCE, context) + inputInterval = self.parameterAsDouble(parameters, self.INPUT_INTERVAL, context) + useDsmBuild = self.parameterAsBool(parameters, self.USE_DSMBUILD, context) dsmlayer = None demlayer = None # dsm_build = None ro = self.parameterAsString(parameters, self.ROUGH, context) - filePrefix = self.parameterAsString( - parameters, self.FILE_PREFIX, context - ) - outputDir = self.parameterAsString( - parameters, self.OUTPUT_DIR, context - ) + filePrefix = self.parameterAsString(parameters, self.FILE_PREFIX, context) + outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) outputPolygon = self.parameterAsOutputLayer( parameters, self.OUTPUT_POLYGON, context ) @@ -245,9 +229,7 @@ def processAlgorithm(self, parameters, context, feedback): # if lcgrid is not None: if inputPointLayer is None: feedback.setProgressText("Point location obtained manually") - inputPoint = self.parameterAsPoint( - parameters, self.INPUT_POINT, context - ) + inputPoint = self.parameterAsPoint(parameters, self.INPUT_POINT, context) x = float(inputPoint[0]) y = float(inputPoint[1]) else: @@ -304,12 +286,8 @@ def processAlgorithm(self, parameters, context, feedback): dem = np.zeros((sizex, sizey)) else: # Both building ground heights - dsmlayer = self.parameterAsRasterLayer( - parameters, self.INPUT_DSM, context - ) - demlayer = self.parameterAsRasterLayer( - parameters, self.INPUT_DEM, context - ) + dsmlayer = self.parameterAsRasterLayer(parameters, self.INPUT_DSM, context) + demlayer = self.parameterAsRasterLayer(parameters, self.INPUT_DEM, context) if dsmlayer is None: raise QgsProcessingException( @@ -340,9 +318,7 @@ def processAlgorithm(self, parameters, context, feedback): dataset2 = gdal.Open(self.plugin_dir + "/data/clipdem.tif") dem = dataset2.ReadAsArray().astype(float) - if not (dsm.shape[0] == dem.shape[0]) & ( - dsm.shape[1] == dem.shape[1] - ): + if not (dsm.shape[0] == dem.shape[0]) & (dsm.shape[1] == dem.shape[1]): raise QgsProcessingException( "All grids must be of same extent and resolution" ) @@ -384,9 +360,7 @@ def processAlgorithm(self, parameters, context, feedback): zMax = immorphresult["zHmax"] zSdev = immorphresult["zH_sd"] - zd, z0 = rg.RoughnessCalcMany( - Roughnessmethod, zH, fai, pai, zMax, zSdev - ) + zd, z0 = rg.RoughnessCalcMany(Roughnessmethod, zH, fai, pai, zMax, zSdev) # save to file pre = filePrefix @@ -486,8 +460,7 @@ def processAlgorithm(self, parameters, context, feedback): def create_point_layer(self, outputPoint, x, y, crs): uri = ( - "Point?field=id:integer&field=x:double&field=y:double&index=yes&crs=" - + crs + "Point?field=id:integer&field=x:double&field=y:double&index=yes&crs=" + crs ) self.poiLayer = QgsVectorLayer(uri, "Point of Interest", "memory") self.provider = self.poiLayer.dataProvider() diff --git a/preprocessor/landcoverfraction_algorithm.py b/preprocessor/landcoverfraction_algorithm.py index 9c63ca5..7f15693 100644 --- a/preprocessor/landcoverfraction_algorithm.py +++ b/preprocessor/landcoverfraction_algorithm.py @@ -81,9 +81,7 @@ def initAlgorithm(self, config): self.search = ( ( - self.tr( - "Search throughout the grid extent (search distance not used)" - ), + self.tr("Search throughout the grid extent (search distance not used)"), "0", ), (self.tr("Search from grid centroid"), "1"), @@ -136,24 +134,18 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterRasterLayer( self.INPUT_LCGRID, - self.tr( - "UMEP formatted land cover grid (see help for more info)" - ), + self.tr("UMEP formatted land cover grid (see help for more info)"), None, False, ) ) self.addParameter( - QgsProcessingParameterString( - self.FILE_PREFIX, self.tr("File prefix") - ) + QgsProcessingParameterString(self.FILE_PREFIX, self.tr("File prefix")) ) self.addParameter( QgsProcessingParameterBoolean( self.TARGET_LC, - self.tr( - "Calculate fractions for TARGET (9 classes instead of 7)" - ), + self.tr("Calculate fractions for TARGET (9 classes instead of 7)"), defaultValue=False, ) ) @@ -188,27 +180,15 @@ def processAlgorithm(self, parameters, context, feedback): parameters, self.INPUT_POLYGONLAYER, context ) idField = self.parameterAsFields(parameters, self.ID_FIELD, context) - searchMethod = self.parameterAsString( - parameters, self.SERACH_METHOD, context - ) - inputDistance = self.parameterAsDouble( - parameters, self.INPUT_DISTANCE, context - ) - inputInterval = self.parameterAsDouble( - parameters, self.INPUT_INTERVAL, context - ) + searchMethod = self.parameterAsString(parameters, self.SERACH_METHOD, context) + inputDistance = self.parameterAsDouble(parameters, self.INPUT_DISTANCE, context) + inputInterval = self.parameterAsDouble(parameters, self.INPUT_INTERVAL, context) dsmlayer = None useTarget = self.parameterAsBool(parameters, self.TARGET_LC, context) - filePrefix = self.parameterAsString( - parameters, self.FILE_PREFIX, context - ) + filePrefix = self.parameterAsString(parameters, self.FILE_PREFIX, context) attrTable = self.parameterAsBool(parameters, self.ATTR_TABLE, context) - ignoreNodata = self.parameterAsBool( - parameters, self.IGNORE_NODATA, context - ) - outputDir = self.parameterAsString( - parameters, self.OUTPUT_DIR, context - ) + ignoreNodata = self.parameterAsBool(parameters, self.IGNORE_NODATA, context) + outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): @@ -225,18 +205,18 @@ def processAlgorithm(self, parameters, context, feedback): if useTarget: iter = 9 header = "Wd Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water IrrGrass Concrete" - numformat = ( - "%3d %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f" - ) + numformat = "%3d %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f" header2 = "ID Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water IrrGrass Concrete" - numformat2 = ( - "%3d %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f" - ) + numformat2 = "%3d %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f" else: iter = 7 - header = "Wd Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water" + header = ( + "Wd Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water" + ) numformat = "%3d %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f" - header2 = "ID Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water" + header2 = ( + "ID Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water" + ) numformat2 = "%3d %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f" arrmat = np.empty((1, int(iter + 1))) @@ -261,9 +241,7 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText("idx: " + str(idx)) - dsmlayer = self.parameterAsRasterLayer( - parameters, self.INPUT_LCGRID, context - ) + dsmlayer = self.parameterAsRasterLayer(parameters, self.INPUT_LCGRID, context) if dsmlayer is None: raise QgsProcessingException( "No valid building land cover raster layer is selected" @@ -300,10 +278,7 @@ def processAlgorithm(self, parameters, context, feedback): prov.crs(), "ESRI shapefile", ) - if ( - writer.hasError() - != QgsVectorFileWriter.WriterError.NoError - ): + if writer.hasError() != QgsVectorFileWriter.WriterError.NoError: raise QgsProcessingException( "Error when creating shapefile: ", str(writer.hasError()), @@ -367,9 +342,7 @@ def processAlgorithm(self, parameters, context, feedback): else: lcgrid[lcgrid == nd] = 0 feedback.setProgressText( - "Grid " - + str(f.attributes()[idx]) - + " being calculated." + "Grid " + str(f.attributes()[idx]) + " being calculated." ) cal = 1 else: @@ -383,9 +356,7 @@ def processAlgorithm(self, parameters, context, feedback): else: cal = 1 feedback.setProgressText( - "Grid " - + str(f.attributes()[idx]) - + " being calculated." + "Grid " + str(f.attributes()[idx]) + " being calculated." ) if cal == 1: @@ -488,9 +459,7 @@ def addattr(self, vlayer, matdata, header, pre, feedback, idx): wo = np.where(f.attributes()[idx] == matdata[:, 0]) if wo[0] >= 0: for x in range(1, matdata.shape[1]): - attr_dict[current_index_length + x - 1] = float( - matdata[wo[0], x] - ) + attr_dict[current_index_length + x - 1] = float(matdata[wo[0], x]) vlayer.dataProvider().changeAttributeValues({id: attr_dict}) def resultcheck(self, landcoverresult): diff --git a/preprocessor/landcoverfractionpoint_algorithm.py b/preprocessor/landcoverfractionpoint_algorithm.py index 5de413b..0f6c2ca 100644 --- a/preprocessor/landcoverfractionpoint_algorithm.py +++ b/preprocessor/landcoverfractionpoint_algorithm.py @@ -116,24 +116,18 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterRasterLayer( self.INPUT_LCGRID, - self.tr( - "UMEP formatted land cover grid (see help for more info)" - ), + self.tr("UMEP formatted land cover grid (see help for more info)"), None, False, ) ) self.addParameter( - QgsProcessingParameterString( - self.FILE_PREFIX, self.tr("File prefix") - ) + QgsProcessingParameterString(self.FILE_PREFIX, self.tr("File prefix")) ) self.addParameter( QgsProcessingParameterBoolean( self.TARGET_LC, - self.tr( - "Calculate fractions for TARGET (9 classes instead of 7)" - ), + self.tr("Calculate fractions for TARGET (9 classes instead of 7)"), defaultValue=False, ) ) @@ -169,22 +163,12 @@ def processAlgorithm(self, parameters, context, feedback): inputPointLayer = self.parameterAsVectorLayer( parameters, self.INPUT_POINTLAYER, context ) - inputDistance = self.parameterAsDouble( - parameters, self.INPUT_DISTANCE, context - ) - inputInterval = self.parameterAsDouble( - parameters, self.INPUT_INTERVAL, context - ) - lclayer = self.parameterAsRasterLayer( - parameters, self.INPUT_LCGRID, context - ) - filePrefix = self.parameterAsString( - parameters, self.FILE_PREFIX, context - ) + inputDistance = self.parameterAsDouble(parameters, self.INPUT_DISTANCE, context) + inputInterval = self.parameterAsDouble(parameters, self.INPUT_INTERVAL, context) + lclayer = self.parameterAsRasterLayer(parameters, self.INPUT_LCGRID, context) + filePrefix = self.parameterAsString(parameters, self.FILE_PREFIX, context) useTarget = self.parameterAsBool(parameters, self.TARGET_LC, context) - outputDir = self.parameterAsString( - parameters, self.OUTPUT_DIR, context - ) + outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) outputPolygon = self.parameterAsOutputLayer( parameters, self.OUTPUT_POLYGON, context ) @@ -197,9 +181,7 @@ def processAlgorithm(self, parameters, context, feedback): # Get POI if inputPointLayer is None: feedback.setProgressText("Point location obtained manually") - inputPoint = self.parameterAsPoint( - parameters, self.INPUT_POINT, context - ) + inputPoint = self.parameterAsPoint(parameters, self.INPUT_POINT, context) x = float(inputPoint[0]) y = float(inputPoint[1]) else: @@ -238,9 +220,7 @@ def processAlgorithm(self, parameters, context, feedback): filePath_lcgrid = str(provider.dataSourceUri()) bigraster = gdal.Open(filePath_lcgrid) bbox = (x - r, y + r, x + r, y - r) - gdal.Translate( - self.plugin_dir + "/data/clipdsm.tif", bigraster, projWin=bbox - ) + gdal.Translate(self.plugin_dir + "/data/clipdsm.tif", bigraster, projWin=bbox) bigraster = None dataset = gdal.Open(self.plugin_dir + "/data/clipdsm.tif") lcgrid = dataset.ReadAsArray().astype(float) @@ -263,19 +243,17 @@ def processAlgorithm(self, parameters, context, feedback): + " m) from point of interest. Extend your raster(s) or more point." ) else: - landcoverresult = land.landcover_v2( - lcgrid, 1, degree, feedback, 1, iter - ) + landcoverresult = land.landcover_v2(lcgrid, 1, degree, feedback, 1, iter) ## save to file ## # anisotropic if useTarget: header = "Wd Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water IrrGrass Concrete" - numformat = ( - "%3d %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f" - ) + numformat = "%3d %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f" else: - header = "Wd Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water" + header = ( + "Wd Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water" + ) numformat = "%3d %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f" arr = np.concatenate( @@ -295,7 +273,9 @@ def processAlgorithm(self, parameters, context, feedback): header = "Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water IrrGrass Concrete" numformat = "%5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f" else: - header = "Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water" + header = ( + "Paved Buildings EvergreenTrees DecidiousTrees Grass Baresoil Water" + ) numformat = "%5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f" arr2 = np.array(landcoverresult["lc_frac_all"]) @@ -328,8 +308,7 @@ def processAlgorithm(self, parameters, context, feedback): def create_point_layer(self, outputPoint, x, y, crs): uri = ( - "Point?field=id:integer&field=x:double&field=y:double&index=yes&crs=" - + crs + "Point?field=id:integer&field=x:double&field=y:double&index=yes&crs=" + crs ) self.poiLayer = QgsVectorLayer(uri, "Point of Interest", "memory") self.provider = self.poiLayer.dataProvider() diff --git a/preprocessor/skyviewfactor_algorithm.py b/preprocessor/skyviewfactor_algorithm.py index a4aad1b..74021be 100644 --- a/preprocessor/skyviewfactor_algorithm.py +++ b/preprocessor/skyviewfactor_algorithm.py @@ -155,8 +155,7 @@ def initAlgorithm(self, config): defaultValue=False, ) wall_scheme.setFlags( - wall_scheme.flags() - | QgsProcessingParameterDefinition.Flag.FlagAdvanced + wall_scheme.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced ) self.addParameter(wall_scheme) @@ -166,16 +165,13 @@ def initAlgorithm(self, config): defaultValue=True, ) wall_kmeans.setFlags( - wall_kmeans.flags() - | QgsProcessingParameterDefinition.Flag.FlagAdvanced + wall_kmeans.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced ) self.addParameter(wall_kmeans) wall_clusters = QgsProcessingParameterNumber( self.CLUSTERS, - self.tr( - "Number of clusters used in K-Means (number of elevations)" - ), + self.tr("Number of clusters used in K-Means (number of elevations)"), QgsProcessingParameterNumber.Type.Integer, QVariant(5), True, @@ -183,8 +179,7 @@ def initAlgorithm(self, config): maxValue=100, ) wall_clusters.setFlags( - wall_clusters.flags() - | QgsProcessingParameterDefinition.Flag.FlagAdvanced + wall_clusters.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced ) self.addParameter(wall_clusters) @@ -197,8 +192,7 @@ def initAlgorithm(self, config): True, ) wall_dem.setFlags( - wall_dem.flags() - | QgsProcessingParameterDefinition.Flag.FlagAdvanced + wall_dem.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced ) self.addParameter(wall_dem) @@ -214,8 +208,7 @@ def initAlgorithm(self, config): maxValue=10, ) wall_svfheight.setFlags( - wall_svfheight.flags() - | QgsProcessingParameterDefinition.Flag.FlagAdvanced + wall_svfheight.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced ) self.addParameter(wall_svfheight) @@ -236,37 +229,21 @@ def initAlgorithm(self, config): def processAlgorithm(self, parameters, context, feedback): # InputParameters - outputDir = self.parameterAsString( - parameters, self.OUTPUT_DIR, context - ) - outputFile = self.parameterAsOutputLayer( - parameters, self.OUTPUT_FILE, context - ) - dsmlayer = self.parameterAsRasterLayer( - parameters, self.INPUT_DSM, context - ) + outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) + outputFile = self.parameterAsOutputLayer(parameters, self.OUTPUT_FILE, context) + dsmlayer = self.parameterAsRasterLayer(parameters, self.INPUT_DSM, context) # useVegdem = self.parameterAsBool(parameters, self.USE_VEG, context) transVeg = self.parameterAsDouble(parameters, self.TRANS_VEG, context) - vegdsm = self.parameterAsRasterLayer( - parameters, self.INPUT_CDSM, context - ) - vegdsm2 = self.parameterAsRasterLayer( - parameters, self.INPUT_TDSM, context - ) + vegdsm = self.parameterAsRasterLayer(parameters, self.INPUT_CDSM, context) + vegdsm2 = self.parameterAsRasterLayer(parameters, self.INPUT_TDSM, context) # tdsmExists = self.parameterAsBool(parameters, self.TSDM_EXIST, context) - trunkr = self.parameterAsDouble( - parameters, self.INPUT_THEIGHT, context - ) + trunkr = self.parameterAsDouble(parameters, self.INPUT_THEIGHT, context) aniso = self.parameterAsBool(parameters, self.ANISO, context) use_gpu = self.parameterAsBool(parameters, self.USE_GPU, context) # Wall parameterization settings - demlayer = self.parameterAsRasterLayer( - parameters, self.INPUT_DEM, context - ) - svf_height = self.parameterAsDouble( - parameters, self.INPUT_SVFHEIGHT, context - ) + demlayer = self.parameterAsRasterLayer(parameters, self.INPUT_DEM, context) + svf_height = self.parameterAsDouble(parameters, self.INPUT_SVFHEIGHT, context) kmeans = self.parameterAsBool( parameters, self.KMEANS, context @@ -274,9 +251,7 @@ def processAlgorithm(self, parameters, context, feedback): clusters = ( self.parameterAsInt(parameters, self.CLUSTERS, context) + 1 ) # + 1 because ground areas will be one cluster when dsm - dem - wallScheme = self.parameterAsBool( - parameters, self.WALL_SCHEME, context - ) + wallScheme = self.parameterAsBool(parameters, self.WALL_SCHEME, context) feedback.setProgressText("Initiating algorithm") @@ -383,7 +358,7 @@ def processAlgorithm(self, parameters, context, feedback): if aniso == 1: feedback.setProgressText("Calculating SVF using 153 iterations") - with torch.no_grad(): + with torch.no_grad(): ret = svf.svfForProcessing153( dsm, @@ -400,7 +375,7 @@ def processAlgorithm(self, parameters, context, feedback): else: feedback.setProgressText("Calculating SVF using 655 iterations") - with torch.no_grad(): + with torch.no_grad(): ret = svf.svfForProcessing655( dsm, vegdsm, vegdsm2, scale, usevegdem, feedback, device=device @@ -425,9 +400,7 @@ def processAlgorithm(self, parameters, context, feedback): svftotal = svfbu - (1 - svfveg) * (1 - trans) # Lägg till loop för att lägga till i tabellen svf_array = torch.zeros((voxelTable.shape[0]), device=device) - svf_height_array = torch.zeros( - (voxelTable.shape[0]), device=device - ) + svf_height_array = torch.zeros((voxelTable.shape[0]), device=device) svfbu_array = torch.zeros((voxelTable.shape[0]), device=device) svfveg_array = torch.zeros((voxelTable.shape[0]), device=device) svfaveg_array = torch.zeros((voxelTable.shape[0]), device=device) @@ -448,7 +421,7 @@ def processAlgorithm(self, parameters, context, feedback): svf_height_array[temp_y] = svf_height if kmeans: - with torch.no_grad(): + with torch.no_grad(): voxelTable, cluster_heights = svfv.svf_kmeans( dsm, @@ -483,8 +456,8 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText( "Calculating SVF for wall surface temperature parameterization" ) - - with torch.no_grad(): + + with torch.no_grad(): voxelTable = svfv.svf_for_voxels( dsm, @@ -673,9 +646,7 @@ def processAlgorithm(self, parameters, context, feedback): trans = transVeg / 100.0 svftotal = svfbu - (1 - svfveg) * (1 - trans) - misc.saveraster( - gdal_dsm, filename, svftotal.cpu().detach().numpy() - ) + misc.saveraster(gdal_dsm, filename, svftotal.cpu().detach().numpy()) # Save shadow images for SOLWEIG 2019a if aniso == 1: @@ -706,9 +677,7 @@ def processAlgorithm(self, parameters, context, feedback): voxelTable=voxelTable.cpu().detach().numpy(), ) - feedback.setProgressText( - "Sky View Factor: SVF grid(s) successfully generated" - ) + feedback.setProgressText("Sky View Factor: SVF grid(s) successfully generated") return {self.OUTPUT_DIR: outputDir, self.OUTPUT_FILE: outputFile} diff --git a/preprocessor/targetprepare_algorithm.py b/preprocessor/targetprepare_algorithm.py index c410318..75ab045 100644 --- a/preprocessor/targetprepare_algorithm.py +++ b/preprocessor/targetprepare_algorithm.py @@ -108,18 +108,14 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterBoolean( self.UMEP_LC, - self.tr( - "Use standard UMEP land cover grid (fractions below is used)" - ), + self.tr("Use standard UMEP land cover grid (fractions below is used)"), defaultValue=True, ) ) self.addParameter( QgsProcessingParameterNumber( self.FRAC_IRR, - self.tr( - "Fraction Irrigated grass taken from Grass land cover class" - ), + self.tr("Fraction Irrigated grass taken from Grass land cover class"), QgsProcessingParameterNumber.Type.Double, QVariant(0.20), False, @@ -155,17 +151,13 @@ def processAlgorithm(self, parameters, context, feedback): parameters, self.INPUT_POLYGONLAYER, context ) idField = self.parameterAsFields(parameters, self.ID_FIELD, context) - morphFile = self.parameterAsString( - parameters, self.INPUT_MORPH, context - ) + morphFile = self.parameterAsString(parameters, self.INPUT_MORPH, context) lcFile = self.parameterAsString(parameters, self.INPUT_LC, context) umepLC = self.parameterAsBool(parameters, self.UMEP_LC, context) fracIrr = self.parameterAsDouble(parameters, self.FRAC_IRR, context) fracConc = self.parameterAsDouble(parameters, self.FRAC_CONC, context) siteName = self.parameterAsString(parameters, self.SITE_NAME, context) - outputDir = self.parameterAsString( - parameters, self.OUTPUT_DIR, context - ) + outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): @@ -197,9 +189,7 @@ def processAlgorithm(self, parameters, context, feedback): grid_crs = osr.SpatialReference() grid_crs.ImportFromWkt(vlayer.crs().toWkt()) grid_unit = grid_crs.GetAttrValue("UNIT") - feedback.setProgressText( - "Length unit of vector layer: " + str(grid_unit) - ) + feedback.setProgressText("Length unit of vector layer: " + str(grid_unit)) possible_units_metre = [ "metre", "Metre", @@ -324,29 +314,14 @@ def processAlgorithm(self, parameters, context, feedback): jsn2.write(jsonout) # creating folders and saving - if not os.path.exists( - outputDir + "/" + siteName + "/" + "input" + "/" + "LC" - ): - os.makedirs( - outputDir + "/" + siteName + "/" + "input" + "/" + "LC" - ) + if not os.path.exists(outputDir + "/" + siteName + "/" + "input" + "/" + "LC"): + os.makedirs(outputDir + "/" + siteName + "/" + "input" + "/" + "LC") - if not os.path.exists( - outputDir + "/" + siteName + "/" + "input" + "/" + "MET" - ): - os.makedirs( - outputDir + "/" + siteName + "/" + "input" + "/" + "MET" - ) + if not os.path.exists(outputDir + "/" + siteName + "/" + "input" + "/" + "MET"): + os.makedirs(outputDir + "/" + siteName + "/" + "input" + "/" + "MET") np.savetxt( - outputDir - + "/" - + siteName - + "/" - + "input" - + "/" - + "LC" - + "/lc_target.txt", + outputDir + "/" + siteName + "/" + "input" + "/" + "LC" + "/lc_target.txt", arrmatsave, fmt=numformat, header=header, diff --git a/preprocessor/treegenerator_algorithm.py b/preprocessor/treegenerator_algorithm.py index 06cfaae..f9c6c3d 100644 --- a/preprocessor/treegenerator_algorithm.py +++ b/preprocessor/treegenerator_algorithm.py @@ -187,27 +187,15 @@ def processAlgorithm(self, parameters, context, feedback): inputPointLayer = self.parameterAsVectorLayer( parameters, self.INPUT_POINTLAYER, context ) - ttype_field = self.parameterAsFields( - parameters, self.TREE_TYPE, context - ) - trunk_field = self.parameterAsFields( - parameters, self.TRUNK_HEIGHT, context - ) - tot_field = self.parameterAsFields( - parameters, self.TOT_HEIGHT, context - ) + ttype_field = self.parameterAsFields(parameters, self.TREE_TYPE, context) + trunk_field = self.parameterAsFields(parameters, self.TRUNK_HEIGHT, context) + tot_field = self.parameterAsFields(parameters, self.TOT_HEIGHT, context) dia_field = self.parameterAsFields(parameters, self.DIA, context) - build = self.parameterAsRasterLayer( - parameters, self.INPUT_BUILD, context - ) + build = self.parameterAsRasterLayer(parameters, self.INPUT_BUILD, context) dsm = self.parameterAsRasterLayer(parameters, self.INPUT_DSM, context) dem = self.parameterAsRasterLayer(parameters, self.INPUT_DEM, context) - cdsm = self.parameterAsRasterLayer( - parameters, self.INPUT_CDSM, context - ) - tdsm = self.parameterAsRasterLayer( - parameters, self.INPUT_TDSM, context - ) + cdsm = self.parameterAsRasterLayer(parameters, self.INPUT_CDSM, context) + tdsm = self.parameterAsRasterLayer(parameters, self.INPUT_TDSM, context) outputCDSM = self.parameterAsOutputLayer( parameters, self.CDSM_GRID_OUT, context ) @@ -337,11 +325,7 @@ def processAlgorithm(self, parameters, context, feedback): width = dataset.RasterXSize height = dataset.RasterYSize minx = geotransform[0] - miny = ( - geotransform[3] - + width * geotransform[4] - + height * geotransform[5] - ) + miny = geotransform[3] + width * geotransform[4] + height * geotransform[5] rows = build_array.shape[0] cols = build_array.shape[1] diff --git a/preprocessor/urock_prepare_algorithm.py b/preprocessor/urock_prepare_algorithm.py index b2fc8de..f43baee 100644 --- a/preprocessor/urock_prepare_algorithm.py +++ b/preprocessor/urock_prepare_algorithm.py @@ -136,9 +136,7 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterFeatureSource( self.INPUT_VEG_POINTS, - self.tr( - "Vegetation point data (trunk location and max height)" - ), + self.tr("Vegetation point data (trunk location and max height)"), [QgsProcessing.SourceType.TypeVectorPoint], optional=True, ) @@ -205,9 +203,7 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterString( self.OUTPUT_VEG_HEIGHT_FIELD, - self.tr( - "Attribute name for vegetation height in output table" - ), + self.tr("Attribute name for vegetation height in output table"), defaultValue="VEG_HEIGHT", optional=True, ) @@ -248,9 +244,7 @@ def processAlgorithm(self, parameters, context, feedback): build_dem = self.parameterAsRasterLayer( parameters, self.INPUT_BUILD_DEM, context ) - veg_dsm = self.parameterAsRasterLayer( - parameters, self.INPUT_VEG_CDSM, context - ) + veg_dsm = self.parameterAsRasterLayer(parameters, self.INPUT_VEG_CDSM, context) # Get output file paths outputBuildFilepath = self.parameterAsOutputLayer( @@ -328,9 +322,9 @@ def processAlgorithm(self, parameters, context, feedback): .split(os.sep)[-1] .split(".")[0] ) - build_dsm_fieldname = re.sub( - "[-0123456789]", "", build_dsm_filename - )[0:11] + build_dsm_fieldname = re.sub("[-0123456789]", "", build_dsm_filename)[ + 0:11 + ] # Create DSM above ground if a DEM is provided if build_dem: @@ -548,12 +542,16 @@ def processAlgorithm(self, parameters, context, feedback): radiusVegField ) ) - heightExpression = "case when {0} is null then 0 else {0}/{1} end".format( - radiusVegField, vegetationAspect + heightExpression = ( + "case when {0} is null then 0 else {0}/{1} end".format( + radiusVegField, vegetationAspect + ) ) elif not radiusVegField and heightVegField: - distanceExpression = "case when {0} is null then 0 else {0}*{1} end".format( - radiusVegField, vegetationAspect + distanceExpression = ( + "case when {0} is null then 0 else {0}*{1} end".format( + radiusVegField, vegetationAspect + ) ) heightExpression = ( "case when {0} is null then 0 else {0} end".format( @@ -584,9 +582,7 @@ def processAlgorithm(self, parameters, context, feedback): "native:buffer", { "INPUT": inputVeglayer, - "DISTANCE": QgsProperty.fromExpression( - distanceExpression - ), + "DISTANCE": QgsProperty.fromExpression(distanceExpression), "SEGMENTS": 5, "END_CAP_STYLE": 0, "JOIN_STYLE": 0, diff --git a/preprocessor/uwgprepare_algorithm.py b/preprocessor/uwgprepare_algorithm.py index 48c7c54..debd61e 100644 --- a/preprocessor/uwgprepare_algorithm.py +++ b/preprocessor/uwgprepare_algorithm.py @@ -154,9 +154,7 @@ def initAlgorithm(self, config): ) self.addParameter( - QgsProcessingParameterString( - self.FILE_PREFIX, self.tr("File code") - ) + QgsProcessingParameterString(self.FILE_PREFIX, self.tr("File code")) ) self.addParameter( @@ -180,20 +178,12 @@ def processAlgorithm(self, parameters, context, feedback): polyBT = self.parameterAsVectorLayer( parameters, self.INPUT_POLYGONLAYERTYPOLOGY, context ) - morphFile = self.parameterAsString( - parameters, self.INPUT_MORPH, context - ) + morphFile = self.parameterAsString(parameters, self.INPUT_MORPH, context) lcFile = self.parameterAsString(parameters, self.INPUT_LC, context) - rurVegCover = self.parameterAsDouble( - parameters, self.INPUT_RURAL, context - ) - climateZone = self.parameterAsString( - parameters, self.CLIMATEZONE, context - ) + rurVegCover = self.parameterAsDouble(parameters, self.INPUT_RURAL, context) + climateZone = self.parameterAsString(parameters, self.CLIMATEZONE, context) prefix = self.parameterAsString(parameters, self.FILE_PREFIX, context) - outputDir = self.parameterAsString( - parameters, self.OUTPUT_DIR, context - ) + outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): @@ -330,15 +320,11 @@ def processAlgorithm(self, parameters, context, feedback): break # Populate dict from UMEP - uwgDict["bldHeight"] = ( - IMP_heights_mean # average building height (m) - ) + uwgDict["bldHeight"] = IMP_heights_mean # average building height (m) uwgDict["bldDensity"] = ( LCF_buildings # urban area building plan density (0-1) ) - uwgDict["verToHor"] = ( - IMP_wai # urban area vertical to horizontal ratio - ) + uwgDict["verToHor"] = IMP_wai # urban area vertical to horizontal ratio uwgDict["grasscover"] = ( LCF_grass # Fraction of the urban ground covered in grass/shrubs only (0-1) ) diff --git a/preprocessor/wall_heightaspect_algorithm.py b/preprocessor/wall_heightaspect_algorithm.py index afc8dcc..97c2810 100644 --- a/preprocessor/wall_heightaspect_algorithm.py +++ b/preprocessor/wall_heightaspect_algorithm.py @@ -51,7 +51,6 @@ import os - class ProcessingWallHeightAscpetAlgorithm(QgsProcessingAlgorithm): INPUT_LIMIT = "INPUT_LIMIT" @@ -117,14 +116,10 @@ def processAlgorithm(self, parameters, context, feedback): ) dsmin = self.parameterAsRasterLayer(parameters, self.INPUT, context) # aspectcalculation = self.parameterAsBool(parameters, self.ASPECT_BOOL, context) - walllimit = self.parameterAsDouble( - parameters, self.INPUT_LIMIT, context - ) + walllimit = self.parameterAsDouble(parameters, self.INPUT_LIMIT, context) use_gpu = self.parameterAsBool(parameters, self.USE_GPU, context) - cmd_folder = Path( - os.path.split(inspect.getfile(inspect.currentframe()))[0] - ) + cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]) feedback.setProgressText(str(cmd_folder)) feedback.setProgressText(str(cmd_folder.parent)) @@ -151,7 +146,12 @@ def processAlgorithm(self, parameters, context, feedback): # outputFileAspect = self.parameterAsOutputLayer(parameters, self.OUTPUT_ASPECT, context) feedback.setProgressText("Calculating wall aspect") dirwalls = wa.filter1Goodwin_as_aspect_v3( - walls, torch.tensor(1, device=device), torch.tensor(dsm, device=device), feedback, torch.tensor(total, device=device), device + walls, + torch.tensor(1, device=device), + torch.tensor(dsm, device=device), + feedback, + torch.tensor(total, device=device), + device, ) saverasternd(gdal_dsm, outputFileAspect, dirwalls.cpu().detach().numpy()) else: diff --git a/processor/sebe_algorithm.py b/processor/sebe_algorithm.py index 0bcb04e..464a1a5 100644 --- a/processor/sebe_algorithm.py +++ b/processor/sebe_algorithm.py @@ -200,9 +200,7 @@ def initAlgorithm(self, config): ) ) self.addParameter( - QgsProcessingParameterFolderDestination( - self.OUTPUT_DIR, "Output folder" - ) + QgsProcessingParameterFolderDestination(self.OUTPUT_DIR, "Output folder") ) self.addParameter( QgsProcessingParameterRasterDestination( @@ -215,28 +213,14 @@ def initAlgorithm(self, config): def processAlgorithm(self, parameters, context, feedback): # InputParameters - outputDir = self.parameterAsString( - parameters, self.OUTPUT_DIR, context - ) - dsmlayer = self.parameterAsRasterLayer( - parameters, self.INPUT_DSM, context - ) + outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) + dsmlayer = self.parameterAsRasterLayer(parameters, self.INPUT_DSM, context) transVeg = self.parameterAsDouble(parameters, self.TRANS_VEG, context) - vegdsm = self.parameterAsRasterLayer( - parameters, self.INPUT_CDSM, context - ) - vegdsm2 = self.parameterAsRasterLayer( - parameters, self.INPUT_TDSM, context - ) - whlayer = self.parameterAsRasterLayer( - parameters, self.INPUT_HEIGHT, context - ) - walayer = self.parameterAsRasterLayer( - parameters, self.INPUT_ASPECT, context - ) - trunkr = self.parameterAsDouble( - parameters, self.INPUT_THEIGHT, context - ) + vegdsm = self.parameterAsRasterLayer(parameters, self.INPUT_CDSM, context) + vegdsm2 = self.parameterAsRasterLayer(parameters, self.INPUT_TDSM, context) + whlayer = self.parameterAsRasterLayer(parameters, self.INPUT_HEIGHT, context) + walayer = self.parameterAsRasterLayer(parameters, self.INPUT_ASPECT, context) + trunkr = self.parameterAsDouble(parameters, self.INPUT_THEIGHT, context) onlyglobal = self.parameterAsBool(parameters, self.ONLYGLOBAL, context) utcpos = self.parameterAsString(parameters, self.UTC, context) albedo = self.parameterAsDouble(parameters, self.ALBEDO, context) @@ -244,21 +228,16 @@ def processAlgorithm(self, parameters, context, feedback): saveskyirr = self.parameterAsBool(parameters, self.SAVESKYIRR, context) use_gpu = self.parameterAsBool(parameters, self.USE_GPU, context) - irrFile = self.parameterAsFileOutput( - parameters, self.IRR_FILE, context - ) - outputRoof = self.parameterAsOutputLayer( - parameters, self.OUTPUT_ROOF, context - ) + irrFile = self.parameterAsFileOutput(parameters, self.IRR_FILE, context) + outputRoof = self.parameterAsOutputLayer(parameters, self.OUTPUT_ROOF, context) if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): os.mkdir(outputDir) - + device = torch.device("cpu") if use_gpu and torch.cuda.is_available(): device = torch.device("cuda") - provider = dsmlayer.dataProvider() filepath_dsm = str(provider.dataSourceUri()) @@ -302,11 +281,7 @@ def processAlgorithm(self, parameters, context, feedback): height = self.gdal_dsm.RasterYSize geotransform = self.gdal_dsm.GetGeoTransform() minx = geotransform[0] - miny = ( - geotransform[3] - + width * geotransform[4] - + height * geotransform[5] - ) + miny = geotransform[3] + width * geotransform[4] + height * geotransform[5] lonlat = transform.TransformPoint(minx, miny) gdalver = float(gdal.__version__[0]) if gdalver == 3.0: @@ -392,9 +367,7 @@ def processAlgorithm(self, parameters, context, feedback): "Error in Wall height raster: All rasters must be of same extent and resolution" ) - wallmaxheight = self.gdal_wh.GetRasterBand(1).GetStatistics( - True, True - )[1] + wallmaxheight = self.gdal_wh.GetRasterBand(1).GetStatistics(True, True)[1] # wall aspectlayer # if walayer is None: @@ -417,9 +390,9 @@ def processAlgorithm(self, parameters, context, feedback): delim = " " try: - self.metdata = torch.from_numpy(np.loadtxt( - inputMet, skiprows=headernum, delimiter=delim - ), device=device) + self.metdata = torch.from_numpy( + np.loadtxt(inputMet, skiprows=headernum, delimiter=delim), device=device + ) except: QgsProcessingException( "Error: Make sure format of meteorological file is correct. You can" @@ -449,9 +422,7 @@ def processAlgorithm(self, parameters, context, feedback): if alt < 0: alt = 3 - feedback.setProgressText( - "Calculating sun positions for each time step" - ) + feedback.setProgressText("Calculating sun positions for each time step") location = {"longitude": lon, "latitude": lat, "altitude": alt} YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax = ( Solweig_2015a_metdata_noload(self.metdata, location, utc) @@ -469,7 +440,7 @@ def processAlgorithm(self, parameters, context, feedback): albedo, location, zen, - device + device, ) if saveskyirr: @@ -480,9 +451,7 @@ def processAlgorithm(self, parameters, context, feedback): metout[:, 3] = radmatD[:, 2] header = "%altitude azimuth radI radD" numformat = "%6.2f %6.2f %6.2f %6.2f" - np.savetxt( - irrFile, metout, fmt=numformat, header=header, comments="" - ) + np.savetxt(irrFile, metout, fmt=numformat, header=header, comments="") building_slope, building_aspect = get_ders(self.dsm, self.scale) @@ -528,16 +497,14 @@ def processAlgorithm(self, parameters, context, feedback): usevegdem, feedback, wallmaxheight, - device + device, ) Energyyearroof = seberesult["Energyyearroof"] Energyyearwall = seberesult["Energyyearwall"] vegdata = seberesult["vegdata"] - feedback.setProgressText( - "SEBE: Model calculation finished. Saving to disk" - ) + feedback.setProgressText("SEBE: Model calculation finished. Saving to disk") if outputRoof: saveraster(self.gdal_dsm, outputRoof, Energyyearroof) diff --git a/processor/shadow_generator_algorithm.py b/processor/shadow_generator_algorithm.py index 8f1d1bb..b8fbc09 100644 --- a/processor/shadow_generator_algorithm.py +++ b/processor/shadow_generator_algorithm.py @@ -182,9 +182,7 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterNumber( self.ITERTIME, - self.tr( - "Time interval between casting of each shadow (minutes)" - ), + self.tr("Time interval between casting of each shadow (minutes)"), QgsProcessingParameterNumber.Type.Integer, QVariant(30), True, @@ -207,9 +205,7 @@ def initAlgorithm(self, config): ) ) self.addParameter( - QgsProcessingParameterFolderDestination( - self.OUTPUT_DIR, "Output folder" - ) + QgsProcessingParameterFolderDestination(self.OUTPUT_DIR, "Output folder") ) self.addParameter( QgsProcessingParameterRasterDestination( @@ -222,37 +218,19 @@ def initAlgorithm(self, config): def processAlgorithm(self, parameters, context, feedback): # InputParameters - outputDir = self.parameterAsString( - parameters, self.OUTPUT_DIR, context - ) - outputFile = self.parameterAsOutputLayer( - parameters, self.OUTPUT_FILE, context - ) - dsmlayer = self.parameterAsRasterLayer( - parameters, self.INPUT_DSM, context - ) + outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) + outputFile = self.parameterAsOutputLayer(parameters, self.OUTPUT_FILE, context) + dsmlayer = self.parameterAsRasterLayer(parameters, self.INPUT_DSM, context) transVeg = self.parameterAsDouble(parameters, self.TRANS_VEG, context) - vegdsm = self.parameterAsRasterLayer( - parameters, self.INPUT_CDSM, context - ) - vegdsm2 = self.parameterAsRasterLayer( - parameters, self.INPUT_TDSM, context - ) - whlayer = self.parameterAsRasterLayer( - parameters, self.INPUT_HEIGHT, context - ) - walayer = self.parameterAsRasterLayer( - parameters, self.INPUT_ASPECT, context - ) - trunkr = self.parameterAsDouble( - parameters, self.INPUT_THEIGHT, context - ) + vegdsm = self.parameterAsRasterLayer(parameters, self.INPUT_CDSM, context) + vegdsm2 = self.parameterAsRasterLayer(parameters, self.INPUT_TDSM, context) + whlayer = self.parameterAsRasterLayer(parameters, self.INPUT_HEIGHT, context) + walayer = self.parameterAsRasterLayer(parameters, self.INPUT_ASPECT, context) + trunkr = self.parameterAsDouble(parameters, self.INPUT_THEIGHT, context) utcpos = self.parameterAsString(parameters, self.UTC, context) dst = self.parameterAsBool(parameters, self.DST, context) myDate = self.parameterAsString(parameters, self.DATEINI, context) - oneShadow = self.parameterAsDouble( - parameters, self.ONE_SHADOW, context - ) + oneShadow = self.parameterAsDouble(parameters, self.ONE_SHADOW, context) myTime = self.parameterAsString(parameters, self.TIMEINI, context) iterShadow = self.parameterAsDouble(parameters, self.ITERTIME, context) diff --git a/processor/solweig_algorithm.py b/processor/solweig_algorithm.py index c196955..2e448e9 100644 --- a/processor/solweig_algorithm.py +++ b/processor/solweig_algorithm.py @@ -196,9 +196,7 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterNumber( self.LEAF_START, - self.tr( - "First day of year with leaves on trees (if deciduous)" - ), + self.tr("First day of year with leaves on trees (if deciduous)"), QgsProcessingParameterNumber.Type.Integer, QVariant(97), False, @@ -210,9 +208,7 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterNumber( self.LEAF_END, - self.tr( - "Last day of year with leaves on trees (if deciduous)" - ), + self.tr("Last day of year with leaves on trees (if deciduous)"), QgsProcessingParameterNumber.Type.Integer, QVariant(300), False, @@ -295,9 +291,7 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterFile( self.INPUT_WALLSCHEME, - self.tr( - "Voxel data for wall temperature parameterization (.npz)" - ), + self.tr("Voxel data for wall temperature parameterization (.npz)"), extension="npz", optional=True, ) @@ -448,8 +442,7 @@ def initAlgorithm(self, config): optional=True, ) woifile.setFlags( - woifile.flags() - | QgsProcessingParameterDefinition.Flag.FlagAdvanced + woifile.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced ) self.addParameter(woifile) woi_field = QgsProcessingParameterField( @@ -461,8 +454,7 @@ def initAlgorithm(self, config): optional=True, ) woi_field.setFlags( - woi_field.flags() - | QgsProcessingParameterDefinition.Flag.FlagAdvanced + woi_field.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced ) self.addParameter(woi_field) @@ -476,8 +468,7 @@ def initAlgorithm(self, config): optional=True, ) poifile.setFlags( - poifile.flags() - | QgsProcessingParameterDefinition.Flag.FlagAdvanced + poifile.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced ) self.addParameter(poifile) poi_field = QgsProcessingParameterField( @@ -489,8 +480,7 @@ def initAlgorithm(self, config): optional=True, ) poi_field.setFlags( - poi_field.flags() - | QgsProcessingParameterDefinition.Flag.FlagAdvanced + poi_field.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced ) self.addParameter(poi_field) @@ -504,9 +494,7 @@ def initAlgorithm(self, config): minValue=0, maxValue=120, ) - age.setFlags( - age.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) + age.setFlags(age.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) self.addParameter(age) act = QgsProcessingParameterNumber( self.ACTIVITY, @@ -517,9 +505,7 @@ def initAlgorithm(self, config): minValue=0, maxValue=1000, ) - act.setFlags( - act.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) + act.setFlags(act.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) self.addParameter(act) clo = QgsProcessingParameterNumber( self.CLO, @@ -530,9 +516,7 @@ def initAlgorithm(self, config): minValue=0, maxValue=10, ) - clo.setFlags( - clo.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) + clo.setFlags(clo.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) self.addParameter(clo) wei = QgsProcessingParameterNumber( self.WEIGHT, @@ -543,9 +527,7 @@ def initAlgorithm(self, config): minValue=0, maxValue=500, ) - wei.setFlags( - wei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) + wei.setFlags(wei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) self.addParameter(wei) hei = QgsProcessingParameterNumber( self.HEIGHT, @@ -556,9 +538,7 @@ def initAlgorithm(self, config): minValue=0, maxValue=250, ) - hei.setFlags( - hei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) + hei.setFlags(hei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) self.addParameter(hei) sex = QgsProcessingParameterEnum( self.SEX, @@ -567,9 +547,7 @@ def initAlgorithm(self, config): optional=True, defaultValue=0, ) - sex.setFlags( - sex.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) + sex.setFlags(sex.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) self.addParameter(sex) shei = QgsProcessingParameterNumber( self.SENSOR_HEIGHT, @@ -580,17 +558,13 @@ def initAlgorithm(self, config): minValue=0, maxValue=250, ) - shei.setFlags( - shei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) + shei.setFlags(shei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) self.addParameter(shei) self.addParameter( QgsProcessingParameterBoolean( self.USE_GPU, - self.tr( - "Use GPU for calculations (if not ticked, CPU will be used)" - ), + self.tr("Use GPU for calculations (if not ticked, CPU will be used)"), defaultValue=False, optional=False, ) @@ -650,9 +624,7 @@ def initAlgorithm(self, config): ) ) self.addParameter( - QgsProcessingParameterFolderDestination( - self.OUTPUT_DIR, "Output folder" - ) + QgsProcessingParameterFolderDestination(self.OUTPUT_DIR, "Output folder") ) self.plugin_dir = os.path.dirname(__file__) @@ -662,50 +634,26 @@ def processAlgorithm(self, parameters, context, feedback): np.seterr(divide="ignore", invalid="ignore") # InputParameters - dsmlayer = self.parameterAsRasterLayer( - parameters, self.INPUT_DSM, context - ) - transVeg = ( - self.parameterAsDouble(parameters, self.TRANS_VEG, context) / 100.0 - ) - firstdayleaf = self.parameterAsInt( - parameters, self.LEAF_START, context - ) + dsmlayer = self.parameterAsRasterLayer(parameters, self.INPUT_DSM, context) + transVeg = self.parameterAsDouble(parameters, self.TRANS_VEG, context) / 100.0 + firstdayleaf = self.parameterAsInt(parameters, self.LEAF_START, context) lastdayleaf = self.parameterAsInt(parameters, self.LEAF_END, context) - conifer_bool = self.parameterAsBool( - parameters, self.CONIFER_TREES, context - ) - vegdsm = self.parameterAsRasterLayer( - parameters, self.INPUT_CDSM, context - ) - vegdsm2 = self.parameterAsRasterLayer( - parameters, self.INPUT_TDSM, context - ) - lcgrid = self.parameterAsRasterLayer( - parameters, self.INPUT_LC, context - ) - useLcBuild = self.parameterAsBool( - parameters, self.USE_LC_BUILD, context - ) + conifer_bool = self.parameterAsBool(parameters, self.CONIFER_TREES, context) + vegdsm = self.parameterAsRasterLayer(parameters, self.INPUT_CDSM, context) + vegdsm2 = self.parameterAsRasterLayer(parameters, self.INPUT_TDSM, context) + lcgrid = self.parameterAsRasterLayer(parameters, self.INPUT_LC, context) + useLcBuild = self.parameterAsBool(parameters, self.USE_LC_BUILD, context) dem = None inputSVF = self.parameterAsString(parameters, self.INPUT_SVF, context) - whlayer = self.parameterAsRasterLayer( - parameters, self.INPUT_HEIGHT, context - ) - walayer = self.parameterAsRasterLayer( - parameters, self.INPUT_ASPECT, context - ) + whlayer = self.parameterAsRasterLayer(parameters, self.INPUT_HEIGHT, context) + walayer = self.parameterAsRasterLayer(parameters, self.INPUT_ASPECT, context) - trunkr = self.parameterAsDouble( - parameters, self.INPUT_THEIGHT, context - ) + trunkr = self.parameterAsDouble(parameters, self.INPUT_THEIGHT, context) onlyglobal = self.parameterAsBool(parameters, self.ONLYGLOBAL, context) utc = self.parameterAsDouble(parameters, self.UTC, context) inputMet = self.parameterAsString(parameters, self.INPUT_MET, context) # usePOI = self.parameterAsBool(parameters, self.POI, context) - poilyr = self.parameterAsVectorLayer( - parameters, self.POI_FILE, context - ) + poilyr = self.parameterAsVectorLayer(parameters, self.POI_FILE, context) poi_field = None mbody = None ht = None @@ -716,22 +664,16 @@ def processAlgorithm(self, parameters, context, feedback): sensorheight = None saveBuild = self.parameterAsBool(parameters, self.SAVE_BUILD, context) demforbuild = 0 - folderPathPerez = self.parameterAsString( - parameters, self.INPUT_ANISO, context - ) + folderPathPerez = self.parameterAsString(parameters, self.INPUT_ANISO, context) folderWallScheme = self.parameterAsString( parameters, self.INPUT_WALLSCHEME, context ) - wallNetCDF = self.parameterAsBool( - parameters, self.WALLTEMP_NETCDF, context - ) + wallNetCDF = self.parameterAsBool(parameters, self.WALLTEMP_NETCDF, context) poisxy = None poiname = None # Wall of interest - woilyr = self.parameterAsVectorLayer( - parameters, self.WOI_FILE, context - ) + woilyr = self.parameterAsVectorLayer(parameters, self.WOI_FILE, context) woi_field = None woisxy = None woiname = None @@ -754,36 +696,23 @@ def processAlgorithm(self, parameters, context, feedback): height = 0.75 Fcyl = 0.2 - albedo_b = self.parameterAsDouble( - parameters, self.ALBEDO_WALLS, context - ) - albedo_g = self.parameterAsDouble( - parameters, self.ALBEDO_GROUND, context - ) + albedo_b = self.parameterAsDouble(parameters, self.ALBEDO_WALLS, context) + albedo_g = self.parameterAsDouble(parameters, self.ALBEDO_GROUND, context) ewall = self.parameterAsDouble(parameters, self.EMIS_WALLS, context) eground = self.parameterAsDouble(parameters, self.EMIS_GROUND, context) elvis = 0 # option removed 20200907 in processing UMEP. Should be removed completely # thermal_effusivity = self.parameterAsDouble(parameters, self.EFFUS_WALL, context) wall_type = str( - 100 - + int(self.parameterAsString(parameters, self.WALL_TYPE, context)) + 100 + int(self.parameterAsString(parameters, self.WALL_TYPE, context)) ) - outputDir = self.parameterAsString( - parameters, self.OUTPUT_DIR, context - ) - outputTmrt = self.parameterAsBool( - parameters, self.OUTPUT_TMRT, context - ) + outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) + outputTmrt = self.parameterAsBool(parameters, self.OUTPUT_TMRT, context) outputSh = self.parameterAsBool(parameters, self.OUTPUT_SH, context) outputKup = self.parameterAsBool(parameters, self.OUTPUT_KUP, context) - outputKdown = self.parameterAsBool( - parameters, self.OUTPUT_KDOWN, context - ) + outputKdown = self.parameterAsBool(parameters, self.OUTPUT_KDOWN, context) outputLup = self.parameterAsBool(parameters, self.OUTPUT_LUP, context) - outputLdown = self.parameterAsBool( - parameters, self.OUTPUT_LDOWN, context - ) + outputLdown = self.parameterAsBool(parameters, self.OUTPUT_LDOWN, context) gpu_bool = self.parameterAsBool(parameters, self.USE_GPU, context) @@ -816,9 +745,7 @@ def processAlgorithm(self, parameters, context, feedback): # Load parameters and config settings for SOLWEIG with open(self.plugin_dir + "/parametersforsolweig.json", "r") as jsn: solweig_parameters = json.load(jsn) - configDict = read_solweig_config( - self.plugin_dir + "/configsolweig.ini" - ) + configDict = read_solweig_config(self.plugin_dir + "/configsolweig.ini") # Add GUI Tmrt settings to SOLWEIG parameter file solweig_parameters["Tmrt_params"]["Value"]["absK"] = absK @@ -832,9 +759,7 @@ def processAlgorithm(self, parameters, context, feedback): "Cobble_stone_2014a" ] = albedo_g solweig_parameters["Albedo"]["Effective"]["Value"]["Walls"] = albedo_b - solweig_parameters["Emissivity"]["Value"][ - "Cobble_stone_2014a" - ] = eground + solweig_parameters["Emissivity"]["Value"]["Cobble_stone_2014a"] = eground solweig_parameters["Emissivity"]["Value"]["Walls"] = ewall # Load dsm layer @@ -879,15 +804,11 @@ def processAlgorithm(self, parameters, context, feedback): vegrows = vegdsm.height() vegcols = vegdsm.width() - solweig_parameters["Tree_settings"]["Value"][ - "Transmissivity" - ] = transVeg + solweig_parameters["Tree_settings"]["Value"]["Transmissivity"] = transVeg solweig_parameters["Tree_settings"]["Value"][ "First_day_leaf" ] = firstdayleaf - solweig_parameters["Tree_settings"]["Value"][ - "Last_day_leaf" - ] = lastdayleaf + solweig_parameters["Tree_settings"]["Value"]["Last_day_leaf"] = lastdayleaf if not (vegrows == rows) & (vegcols == cols): raise QgsProcessingException( @@ -955,9 +876,7 @@ def processAlgorithm(self, parameters, context, feedback): # DEM # if not useLcBuild: demforbuild = 1 - dem = self.parameterAsRasterLayer( - parameters, self.INPUT_DEM, context - ) + dem = self.parameterAsRasterLayer(parameters, self.INPUT_DEM, context) if dem is None: raise QgsProcessingException("Error: No valid DEM selected") @@ -1049,9 +968,7 @@ def processAlgorithm(self, parameters, context, feedback): delim = " " try: - self.metdata = np.loadtxt( - inputMet, skiprows=headernum, delimiter=delim - ) + self.metdata = np.loadtxt(inputMet, skiprows=headernum, delimiter=delim) except: raise QgsProcessingException( "Error: Make sure format of meteorological file is correct. You can" @@ -1096,23 +1013,20 @@ def processAlgorithm(self, parameters, context, feedback): if poilyr is not None: # usePOI: poi_file = str(poilyr.dataProvider().dataSourceUri()) - poi_field = self.parameterAsString( - parameters, self.POI_FIELD, context - ) + poi_field = self.parameterAsString(parameters, self.POI_FIELD, context) # Other PET variables - solweig_parameters["PET_settings"]["Value"]["Age"] = ( - self.parameterAsDouble(parameters, self.AGE, context) + solweig_parameters["PET_settings"]["Value"]["Age"] = self.parameterAsDouble( + parameters, self.AGE, context ) solweig_parameters["PET_settings"]["Value"]["Weight"] = ( self.parameterAsDouble(parameters, self.WEIGHT, context) ) solweig_parameters["PET_settings"]["Value"]["Height"] = ( - self.parameterAsDouble(parameters, self.HEIGHT, context) - / 100.0 + self.parameterAsDouble(parameters, self.HEIGHT, context) / 100.0 ) - solweig_parameters["PET_settings"]["Value"]["clo"] = ( - self.parameterAsDouble(parameters, self.CLO, context) + solweig_parameters["PET_settings"]["Value"]["clo"] = self.parameterAsDouble( + parameters, self.CLO, context ) solweig_parameters["PET_settings"]["Value"]["Activity"] = ( self.parameterAsDouble(parameters, self.WEIGHT, context) @@ -1122,13 +1036,11 @@ def processAlgorithm(self, parameters, context, feedback): else: solweig_parameters["PET_settings"]["Value"]["Sex"] = "Female" - solweig_parameters["Wind_Height"]["Value"]["magl"] = ( - self.parameterAsDouble(parameters, self.SENSOR_HEIGHT, context) + solweig_parameters["Wind_Height"]["Value"]["magl"] = self.parameterAsDouble( + parameters, self.SENSOR_HEIGHT, context ) - feedback.setProgressText( - "Point of interest (POI) vector data identified" - ) + feedback.setProgressText("Point of interest (POI) vector data identified") else: poi_file = "" poi_field = "" @@ -1148,10 +1060,7 @@ def processAlgorithm(self, parameters, context, feedback): if folderWallScheme: unique_landcover = np.unique(lcgrid) unique_landcover = unique_landcover[unique_landcover < 100] - if ( - np.max(unique_landcover) > 7 - or np.min(unique_landcover) < 1 - ): + if np.max(unique_landcover) > 7 or np.min(unique_landcover) < 1: feedback.pushWarning( "The land cover grid includes integer values higher (or lower) than standard UMEP-formatted. " "Land cover grid (should be integer between 1 and 7). If other LC-classes should be included they also need to be included in landcoverclasses_2016a.txt" @@ -1170,16 +1079,12 @@ def processAlgorithm(self, parameters, context, feedback): # Import data for wall temperature parameterization if folderWallScheme: wallScheme = 1 - feedback.setProgressText( - "Wall surface temperature scheme activated" - ) + feedback.setProgressText("Wall surface temperature scheme activated") # Use wall of interest if woilyr is not None: woi_file = str(woilyr.dataProvider().dataSourceUri()) - woi_field = self.parameterAsString( - parameters, self.WOI_FIELD, context - ) + woi_field = self.parameterAsString(parameters, self.WOI_FIELD, context) else: woi_file = "" woi_field = "" diff --git a/processor/solweig_algorithm_old.py b/processor/solweig_algorithm_old.py index cb5047d..f8404c3 100644 --- a/processor/solweig_algorithm_old.py +++ b/processor/solweig_algorithm_old.py @@ -219,9 +219,7 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterNumber( self.LEAF_START, - self.tr( - "First day of year with leaves on trees (if deciduous)" - ), + self.tr("First day of year with leaves on trees (if deciduous)"), QgsProcessingParameterNumber.Type.Integer, QVariant(97), False, @@ -233,9 +231,7 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterNumber( self.LEAF_END, - self.tr( - "Last day of year with leaves on trees (if deciduous)" - ), + self.tr("Last day of year with leaves on trees (if deciduous)"), QgsProcessingParameterNumber.Type.Integer, QVariant(300), False, @@ -318,9 +314,7 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterFile( self.INPUT_WALLSCHEME, - self.tr( - "Voxel data for wall temperature parameterization (.npz)" - ), + self.tr("Voxel data for wall temperature parameterization (.npz)"), extension="npz", optional=True, ) @@ -474,8 +468,7 @@ def initAlgorithm(self, config): optional=True, ) woifile.setFlags( - woifile.flags() - | QgsProcessingParameterDefinition.Flag.FlagAdvanced + woifile.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced ) self.addParameter(woifile) woi_field = QgsProcessingParameterField( @@ -487,8 +480,7 @@ def initAlgorithm(self, config): optional=True, ) woi_field.setFlags( - woi_field.flags() - | QgsProcessingParameterDefinition.Flag.FlagAdvanced + woi_field.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced ) self.addParameter(woi_field) @@ -506,8 +498,7 @@ def initAlgorithm(self, config): optional=True, ) poifile.setFlags( - poifile.flags() - | QgsProcessingParameterDefinition.Flag.FlagAdvanced + poifile.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced ) self.addParameter(poifile) poi_field = QgsProcessingParameterField( @@ -519,8 +510,7 @@ def initAlgorithm(self, config): optional=True, ) poi_field.setFlags( - poi_field.flags() - | QgsProcessingParameterDefinition.Flag.FlagAdvanced + poi_field.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced ) self.addParameter(poi_field) @@ -534,9 +524,7 @@ def initAlgorithm(self, config): minValue=0, maxValue=120, ) - age.setFlags( - age.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) + age.setFlags(age.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) self.addParameter(age) act = QgsProcessingParameterNumber( self.ACTIVITY, @@ -547,9 +535,7 @@ def initAlgorithm(self, config): minValue=0, maxValue=1000, ) - act.setFlags( - act.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) + act.setFlags(act.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) self.addParameter(act) clo = QgsProcessingParameterNumber( self.CLO, @@ -560,9 +546,7 @@ def initAlgorithm(self, config): minValue=0, maxValue=10, ) - clo.setFlags( - clo.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) + clo.setFlags(clo.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) self.addParameter(clo) wei = QgsProcessingParameterNumber( self.WEIGHT, @@ -573,9 +557,7 @@ def initAlgorithm(self, config): minValue=0, maxValue=500, ) - wei.setFlags( - wei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) + wei.setFlags(wei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) self.addParameter(wei) hei = QgsProcessingParameterNumber( self.HEIGHT, @@ -586,9 +568,7 @@ def initAlgorithm(self, config): minValue=0, maxValue=250, ) - hei.setFlags( - hei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) + hei.setFlags(hei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) self.addParameter(hei) sex = QgsProcessingParameterEnum( self.SEX, @@ -597,9 +577,7 @@ def initAlgorithm(self, config): optional=True, defaultValue=0, ) - sex.setFlags( - sex.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) + sex.setFlags(sex.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) self.addParameter(sex) shei = QgsProcessingParameterNumber( self.SENSOR_HEIGHT, @@ -610,9 +588,7 @@ def initAlgorithm(self, config): minValue=0, maxValue=250, ) - shei.setFlags( - shei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) + shei.setFlags(shei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced) self.addParameter(shei) # OUTPUT @@ -668,9 +644,7 @@ def initAlgorithm(self, config): ) ) self.addParameter( - QgsProcessingParameterFolderDestination( - self.OUTPUT_DIR, "Output folder" - ) + QgsProcessingParameterFolderDestination(self.OUTPUT_DIR, "Output folder") ) self.plugin_dir = os.path.dirname(__file__) @@ -680,49 +654,25 @@ def processAlgorithm(self, parameters, context, feedback): np.seterr(divide="ignore", invalid="ignore") # InputParameters - dsmlayer = self.parameterAsRasterLayer( - parameters, self.INPUT_DSM, context - ) - transVeg = ( - self.parameterAsDouble(parameters, self.TRANS_VEG, context) / 100.0 - ) - firstdayleaf = self.parameterAsInt( - parameters, self.LEAF_START, context - ) + dsmlayer = self.parameterAsRasterLayer(parameters, self.INPUT_DSM, context) + transVeg = self.parameterAsDouble(parameters, self.TRANS_VEG, context) / 100.0 + firstdayleaf = self.parameterAsInt(parameters, self.LEAF_START, context) lastdayleaf = self.parameterAsInt(parameters, self.LEAF_END, context) - conifer_bool = self.parameterAsBool( - parameters, self.CONIFER_TREES, context - ) - vegdsm = self.parameterAsRasterLayer( - parameters, self.INPUT_CDSM, context - ) - vegdsm2 = self.parameterAsRasterLayer( - parameters, self.INPUT_TDSM, context - ) - lcgrid = self.parameterAsRasterLayer( - parameters, self.INPUT_LC, context - ) - useLcBuild = self.parameterAsBool( - parameters, self.USE_LC_BUILD, context - ) + conifer_bool = self.parameterAsBool(parameters, self.CONIFER_TREES, context) + vegdsm = self.parameterAsRasterLayer(parameters, self.INPUT_CDSM, context) + vegdsm2 = self.parameterAsRasterLayer(parameters, self.INPUT_TDSM, context) + lcgrid = self.parameterAsRasterLayer(parameters, self.INPUT_LC, context) + useLcBuild = self.parameterAsBool(parameters, self.USE_LC_BUILD, context) dem = None inputSVF = self.parameterAsString(parameters, self.INPUT_SVF, context) - whlayer = self.parameterAsRasterLayer( - parameters, self.INPUT_HEIGHT, context - ) - walayer = self.parameterAsRasterLayer( - parameters, self.INPUT_ASPECT, context - ) - trunkr = self.parameterAsDouble( - parameters, self.INPUT_THEIGHT, context - ) + whlayer = self.parameterAsRasterLayer(parameters, self.INPUT_HEIGHT, context) + walayer = self.parameterAsRasterLayer(parameters, self.INPUT_ASPECT, context) + trunkr = self.parameterAsDouble(parameters, self.INPUT_THEIGHT, context) onlyglobal = self.parameterAsBool(parameters, self.ONLYGLOBAL, context) utc = self.parameterAsDouble(parameters, self.UTC, context) inputMet = self.parameterAsString(parameters, self.INPUT_MET, context) # usePOI = self.parameterAsBool(parameters, self.POI, context) - poilyr = self.parameterAsVectorLayer( - parameters, self.POI_FILE, context - ) + poilyr = self.parameterAsVectorLayer(parameters, self.POI_FILE, context) poi_field = None mbody = None ht = None @@ -733,22 +683,16 @@ def processAlgorithm(self, parameters, context, feedback): sensorheight = None saveBuild = self.parameterAsBool(parameters, self.SAVE_BUILD, context) demforbuild = 0 - folderPathPerez = self.parameterAsString( - parameters, self.INPUT_ANISO, context - ) + folderPathPerez = self.parameterAsString(parameters, self.INPUT_ANISO, context) folderWallScheme = self.parameterAsString( parameters, self.INPUT_WALLSCHEME, context ) - wallNetCDF = self.parameterAsBool( - parameters, self.WALLTEMP_NETCDF, context - ) + wallNetCDF = self.parameterAsBool(parameters, self.WALLTEMP_NETCDF, context) poisxy = None poiname = None # Wall of interest - woilyr = self.parameterAsVectorLayer( - parameters, self.WOI_FILE, context - ) + woilyr = self.parameterAsVectorLayer(parameters, self.WOI_FILE, context) woi_field = None woisxy = None woiname = None @@ -774,33 +718,21 @@ def processAlgorithm(self, parameters, context, feedback): height = 0.75 Fcyl = 0.2 - albedo_b = self.parameterAsDouble( - parameters, self.ALBEDO_WALLS, context - ) - albedo_g = self.parameterAsDouble( - parameters, self.ALBEDO_GROUND, context - ) + albedo_b = self.parameterAsDouble(parameters, self.ALBEDO_WALLS, context) + albedo_g = self.parameterAsDouble(parameters, self.ALBEDO_GROUND, context) ewall = self.parameterAsDouble(parameters, self.EMIS_WALLS, context) eground = self.parameterAsDouble(parameters, self.EMIS_GROUND, context) elvis = 0 # option removed 20200907 in processing UMEP # thermal_effusivity = self.parameterAsDouble(parameters, self.EFFUS_WALL, context) wall_type = None - outputDir = self.parameterAsString( - parameters, self.OUTPUT_DIR, context - ) - outputTmrt = self.parameterAsBool( - parameters, self.OUTPUT_TMRT, context - ) + outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) + outputTmrt = self.parameterAsBool(parameters, self.OUTPUT_TMRT, context) outputSh = self.parameterAsBool(parameters, self.OUTPUT_SH, context) outputKup = self.parameterAsBool(parameters, self.OUTPUT_KUP, context) - outputKdown = self.parameterAsBool( - parameters, self.OUTPUT_KDOWN, context - ) + outputKdown = self.parameterAsBool(parameters, self.OUTPUT_KDOWN, context) outputLup = self.parameterAsBool(parameters, self.OUTPUT_LUP, context) - outputLdown = self.parameterAsBool( - parameters, self.OUTPUT_LDOWN, context - ) + outputLdown = self.parameterAsBool(parameters, self.OUTPUT_LDOWN, context) outputTreeplanter = self.parameterAsBool( parameters, self.OUTPUT_TREEPLANTER, context ) @@ -840,9 +772,7 @@ def processAlgorithm(self, parameters, context, feedback): "Cobble_stone_2014a" ] = albedo_g solweig_parameters["Albedo"]["Effective"]["Value"]["Walls"] = albedo_b - solweig_parameters["Emissivity"]["Value"][ - "Cobble_stone_2014a" - ] = eground + solweig_parameters["Emissivity"]["Value"]["Cobble_stone_2014a"] = eground solweig_parameters["Emissivity"]["Value"]["Walls"] = ewall # Code from old plugin @@ -895,11 +825,7 @@ def processAlgorithm(self, parameters, context, feedback): heightx = gdal_dsm.RasterYSize geotransform = gdal_dsm.GetGeoTransform() minx = geotransform[0] - miny = ( - geotransform[3] - + widthx * geotransform[4] - + heightx * geotransform[5] - ) + miny = geotransform[3] + widthx * geotransform[4] + heightx * geotransform[5] lonlat = transform.TransformPoint(minx, miny) gdalver = float(gdal.__version__[0]) @@ -1011,9 +937,7 @@ def processAlgorithm(self, parameters, context, feedback): # DEM # if not useLcBuild: demforbuild = 1 - dem = self.parameterAsRasterLayer( - parameters, self.INPUT_DEM, context - ) + dem = self.parameterAsRasterLayer(parameters, self.INPUT_DEM, context) if dem is None: raise QgsProcessingException("Error: No valid DEM selected") @@ -1164,9 +1088,7 @@ def processAlgorithm(self, parameters, context, feedback): Twater = [] try: - self.metdata = np.loadtxt( - inputMet, skiprows=headernum, delimiter=delim - ) + self.metdata = np.loadtxt(inputMet, skiprows=headernum, delimiter=delim) metfileexist = 1 except: raise QgsProcessingException( @@ -1193,9 +1115,7 @@ def processAlgorithm(self, parameters, context, feedback): "the Pre-processor" ) - feedback.setProgressText( - "Calculating sun positions for each time step" - ) + feedback.setProgressText("Calculating sun positions for each time step") location = {"longitude": lon, "latitude": lat, "altitude": alt} YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax = ( Solweig_2015a_metdata_noload(self.metdata, location, utc) @@ -1244,9 +1164,7 @@ def processAlgorithm(self, parameters, context, feedback): # if poilyr is None: # raise QgsProcessingException("No valid point layer is selected") - poi_field = self.parameterAsFields( - parameters, self.POI_FIELD, context - ) + poi_field = self.parameterAsFields(parameters, self.POI_FIELD, context) # if poi_field[0] is None: # raise QgsProcessingException("An attribute field with unique values must be selected when using a POI vector file") vlayer = poilyr @@ -1265,13 +1183,9 @@ def processAlgorithm(self, parameters, context, feedback): poisxy[ind, 0] = ind poisxy[ind, 1] = np.round((x - minx) * scale) if miny >= 0: - poisxy[ind, 2] = np.round( - (miny + rows * (1.0 / scale) - y) * scale - ) + poisxy[ind, 2] = np.round((miny + rows * (1.0 / scale) - y) * scale) else: - poisxy[ind, 2] = np.round( - (miny + rows * (1.0 / scale) - y) * scale - ) + poisxy[ind, 2] = np.round((miny + rows * (1.0 / scale) - y) * scale) ind += 1 @@ -1291,10 +1205,7 @@ def processAlgorithm(self, parameters, context, feedback): # Other PET variables mbody = self.parameterAsDouble(parameters, self.WEIGHT, context) - ht = ( - self.parameterAsDouble(parameters, self.HEIGHT, context) - / 100.0 - ) + ht = self.parameterAsDouble(parameters, self.HEIGHT, context) / 100.0 clo = self.parameterAsDouble(parameters, self.CLO, context) age = self.parameterAsDouble(parameters, self.AGE, context) activity = self.parameterAsDouble(parameters, self.WEIGHT, context) @@ -1412,9 +1323,7 @@ def processAlgorithm(self, parameters, context, feedback): if usevegdem == 1: diffsh = np.zeros((rows, cols, shmat.shape[2])) for i in range(0, shmat.shape[2]): - diffsh[:, :, i] = shmat[:, :, i] - ( - 1 - vegshmat[:, :, i] - ) * ( + diffsh[:, :, i] = shmat[:, :, i] - (1 - vegshmat[:, :, i]) * ( 1 - transVeg ) # changes in psi not implemented yet else: @@ -1461,10 +1370,7 @@ def processAlgorithm(self, parameters, context, feedback): if folderWallScheme: unique_landcover = np.unique(lcgrid) unique_landcover = unique_landcover[unique_landcover < 100] - if ( - np.max(unique_landcover) > 7 - or np.min(unique_landcover) < 1 - ): + if np.max(unique_landcover) > 7 or np.min(unique_landcover) < 1: raise QgsProcessingException( "The land cover grid includes integer values higher (or lower) than UMEP-formatted " "land cover grid (should be integer between 1 and 7). If other LC-classes should be included they also need to be included in landcoverclasses_2016a.txt" @@ -1493,17 +1399,11 @@ def processAlgorithm(self, parameters, context, feedback): ] = Tgmaps_v1(lcgrid.copy(), solweig_parameters) else: - TgK = ( - Knight - + solweig_parameters["Ts_deg"]["Value"]["Cobble_stone_2014a"] - ) + TgK = Knight + solweig_parameters["Ts_deg"]["Value"]["Cobble_stone_2014a"] Tstart = ( - Knight - - solweig_parameters["Tstart"]["Value"]["Cobble_stone_2014a"] + Knight - solweig_parameters["Tstart"]["Value"]["Cobble_stone_2014a"] ) - TmaxLST = solweig_parameters["TmaxLST"]["Value"][ - "Cobble_stone_2014a" - ] + TmaxLST = solweig_parameters["TmaxLST"]["Value"]["Cobble_stone_2014a"] alb_grid = ( Knight + solweig_parameters["Albedo"]["Effective"]["Value"][ @@ -1511,10 +1411,7 @@ def processAlgorithm(self, parameters, context, feedback): ] ) emis_grid = ( - Knight - + solweig_parameters["Emissivity"]["Value"][ - "Cobble_stone_2014a" - ] + Knight + solweig_parameters["Emissivity"]["Value"]["Cobble_stone_2014a"] ) TgK_wall = solweig_parameters["Ts_deg"]["Value"]["Walls"] Tstart_wall = solweig_parameters["Tstart"]["Value"]["Walls"] @@ -1528,10 +1425,7 @@ def processAlgorithm(self, parameters, context, feedback): voxelTable = wallData["voxelTable"] # Get wall type set in GUI wall_type = str( - 100 - + int( - self.parameterAsString(parameters, self.WALL_TYPE, context) - ) + 100 + int(self.parameterAsString(parameters, self.WALL_TYPE, context)) ) # Calculate wall height for wall scheme, i.e. include corners (thicker walls) @@ -1596,9 +1490,7 @@ def processAlgorithm(self, parameters, context, feedback): dsm_y_size, ) = gdal_dsm.GetGeoTransform() - woi_field = self.parameterAsStrings( - parameters, self.WOI_FIELD, context - ) + woi_field = self.parameterAsStrings(parameters, self.WOI_FIELD, context) woisxy, woiname = wallOfInterest( woilyr, woi_field, @@ -1687,29 +1579,21 @@ def processAlgorithm(self, parameters, context, feedback): # Save svf if anisotropic_sky: if not poisxy is None: - patch_characteristics = np.zeros( - (shmat.shape[2], poisxy.shape[0]) - ) + patch_characteristics = np.zeros((shmat.shape[2], poisxy.shape[0])) for idx in range(poisxy.shape[0]): for idy in range(shmat.shape[2]): # Calculations for patches on sky, shmat = 1 = sky is visible - temp_sky = (shmat[:, :, idy] == 1) & ( - vegshmat[:, :, idy] == 1 - ) + temp_sky = (shmat[:, :, idy] == 1) & (vegshmat[:, :, idy] == 1) # Calculations for patches that are vegetation, vegshmat = 0 = shade from vegetation temp_vegsh = (vegshmat[:, :, idy] == 0) | ( vbshvegshmat[:, :, idy] == 0 ) # Calculations for patches that are buildings, shmat = 0 = shade from buildings - temp_vbsh = (1 - shmat[:, :, idy]) * vbshvegshmat[ - :, :, idy - ] + temp_vbsh = (1 - shmat[:, :, idy]) * vbshvegshmat[:, :, idy] temp_sh = temp_vbsh == 1 if wallScheme: temp_sh_w = temp_sh * voxelMaps[:, :, idy] - temp_sh_roof = temp_sh * ( - voxelMaps[:, :, idy] == 0 - ) + temp_sh_roof = temp_sh * (voxelMaps[:, :, idy] == 0) else: temp_sh_w = 0 temp_sh_roof = 0 @@ -1717,16 +1601,12 @@ def processAlgorithm(self, parameters, context, feedback): if temp_sky[int(poisxy[idx, 2]), int(poisxy[idx, 1])]: patch_characteristics[idy, idx] = 1.8 # Vegetation patch - elif temp_vegsh[ - int(poisxy[idx, 2]), int(poisxy[idx, 1]) - ]: + elif temp_vegsh[int(poisxy[idx, 2]), int(poisxy[idx, 1])]: patch_characteristics[idy, idx] = 2.5 # Building patch elif temp_sh[int(poisxy[idx, 2]), int(poisxy[idx, 1])]: if wallScheme: - if temp_sh_w[ - int(poisxy[idx, 2]), int(poisxy[idx, 1]) - ]: + if temp_sh_w[int(poisxy[idx, 2]), int(poisxy[idx, 1])]: patch_characteristics[idy, idx] = 4.5 elif temp_sh_roof[ int(poisxy[idx, 2]), int(poisxy[idx, 1]) @@ -2058,59 +1938,29 @@ def processAlgorithm(self, parameters, context, feedback): poi_save[0, 7] = radIout poi_save[0, 8] = radDout poi_save[0, 9] = radG[i] - poi_save[0, 10] = Kdown[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] + poi_save[0, 10] = Kdown[int(poisxy[k, 2]), int(poisxy[k, 1])] poi_save[0, 11] = Kup[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 12] = Keast[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 13] = Ksouth[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 14] = Kwest[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 15] = Knorth[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 16] = Ldown[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] + poi_save[0, 12] = Keast[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 13] = Ksouth[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 14] = Kwest[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 15] = Knorth[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 16] = Ldown[int(poisxy[k, 2]), int(poisxy[k, 1])] poi_save[0, 17] = Lup[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 18] = Least[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 19] = Lsouth[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 20] = Lwest[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 21] = Lnorth[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] + poi_save[0, 18] = Least[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 19] = Lsouth[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 20] = Lwest[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 21] = Lnorth[int(poisxy[k, 2]), int(poisxy[k, 1])] poi_save[0, 22] = Ta[i] - poi_save[0, 23] = TgOut[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] + poi_save[0, 23] = TgOut[int(poisxy[k, 2]), int(poisxy[k, 1])] poi_save[0, 24] = RH[i] poi_save[0, 25] = esky - poi_save[0, 26] = Tmrt[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] + poi_save[0, 26] = Tmrt[int(poisxy[k, 2]), int(poisxy[k, 1])] poi_save[0, 27] = I0 poi_save[0, 28] = CI - poi_save[0, 29] = shadow[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] + poi_save[0, 29] = shadow[int(poisxy[k, 2]), int(poisxy[k, 1])] poi_save[0, 30] = svf[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 31] = svfbuveg[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 32] = KsideI[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] + poi_save[0, 31] = svfbuveg[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 32] = KsideI[int(poisxy[k, 2]), int(poisxy[k, 1])] # Recalculating wind speed based on powerlaw WsPET = (1.1 / sensorheight) ** 0.2 * Ws[i] WsUTCI = (10.0 / sensorheight) ** 0.2 * Ws[i] @@ -2136,18 +1986,10 @@ def processAlgorithm(self, parameters, context, feedback): poi_save[0, 34] = resultUTCI poi_save[0, 35] = CI_Tg poi_save[0, 36] = CI_TgG - poi_save[0, 37] = KsideD[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 38] = Lside[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 39] = dRad[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 40] = Kside[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] + poi_save[0, 37] = KsideD[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 38] = Lside[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 39] = dRad[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 40] = Kside[int(poisxy[k, 2]), int(poisxy[k, 1])] data_out = outputDir + "/POI_" + str(poiname[k]) + ".txt" # f_handle = file(data_out, 'a') f_handle = open(data_out, "ab") @@ -2187,16 +2029,12 @@ def processAlgorithm(self, parameters, context, feedback): ), "wallShade", ].to_numpy() - temp_all = np.concatenate( - [temp_wall, K_in, L_in, wallShade] - ) + temp_all = np.concatenate([temp_wall, K_in, L_in, wallShade]) # temp_all = np.concatenate([temp_wall]) # wall_data = np.zeros((1, 7 + temp_wall.shape[0])) wall_data = np.zeros((1, 7 + temp_all.shape[0])) # Part of file name (wallid), i.e. WOI_wallid.txt - data_out = ( - outputDir + "/WOI_" + str(woiname[k]) + ".txt" - ) + data_out = outputDir + "/WOI_" + str(woiname[k]) + ".txt" if i == 0: # Output file header # header = 'yyyy id it imin dectime Ta SVF Ts' @@ -2224,15 +2062,12 @@ def processAlgorithm(self, parameters, context, feedback): wall_data[0, 3] = minu[i] wall_data[0, 4] = dectime[i] wall_data[0, 5] = Ta[i] - wall_data[0, 6] = svf[ - int(woisxy[k, 2]), int(woisxy[k, 1]) - ] + wall_data[0, 6] = svf[int(woisxy[k, 2]), int(woisxy[k, 1])] wall_data[0, 7:] = temp_all # Num format for output file data woi_numformat = ( - "%d %d %d %d %.5f %.2f %.2f" - + " %.2f" * temp_all.shape[0] + "%d %d %d %d %.5f %.2f %.2f" + " %.2f" * temp_all.shape[0] ) # Open file, add data, save f_handle = open(data_out, "ab") @@ -2386,12 +2221,8 @@ def processAlgorithm(self, parameters, context, feedback): # Sky view image of patches if (anisotropic_sky == 1) & (i == 0) & (not poisxy is None): for k in range(poisxy.shape[0]): - Lsky_patch_characteristics[:, 2] = patch_characteristics[ - :, k - ] - skyviewimage_out = ( - outputDir + "/POI_" + str(poiname[k]) + ".png" - ) + Lsky_patch_characteristics[:, 2] = patch_characteristics[:, k] + skyviewimage_out = outputDir + "/POI_" + str(poiname[k]) + ".png" PolarBarPlot( Lsky_patch_characteristics, altitude[0][i], @@ -2462,9 +2293,7 @@ def processAlgorithm(self, parameters, context, feedback): # Copying met file for SpatialTC copyfile(inputMet, outputDir + "/metforcing.txt") - tmrtplot = ( - tmrtplot / Ta.__len__() - ) # fix average Tmrt instead of sum, 20191022 + tmrtplot = tmrtplot / Ta.__len__() # fix average Tmrt instead of sum, 20191022 saveraster(gdal_dsm, outputDir + "/Tmrt_average.tif", tmrtplot) feedback.setProgressText("SOLWEIG: Model calculation finished.") diff --git a/processor/suews_algorithm.py b/processor/suews_algorithm.py index 98ef0f4..d86698f 100644 --- a/processor/suews_algorithm.py +++ b/processor/suews_algorithm.py @@ -93,15 +93,11 @@ def initAlgorithm(self, config): self.net = ( (self.tr("0. (OBSERVED) from forcing file"), "0"), ( - self.tr( - "1. (LDOWN_OBSERVED) Modelled (NARP) but Ldown observed" - ), + self.tr("1. (LDOWN_OBSERVED) Modelled (NARP) but Ldown observed"), "1", ), ( - self.tr( - "2. (LDOWN_CLOUD) Modelled (NARP), Ldown from cloud cover" - ), + self.tr("2. (LDOWN_CLOUD) Modelled (NARP), Ldown from cloud cover"), "2", ), ( @@ -173,21 +169,15 @@ def initAlgorithm(self, config): self.z0m = ( (self.tr("1. (FIXED) = Fixed from site parameters"), "1"), ( - self.tr( - "2. (VARIABLE) = Varies with vegetation LAI (Default)" - ), + self.tr("2. (VARIABLE) = Varies with vegetation LAI (Default)"), "2", ), ( - self.tr( - "3. (MACDONALD) = MacDonald et al. 1998 morphometric method" - ), + self.tr("3. (MACDONALD) = MacDonald et al. 1998 morphometric method"), "3", ), ( - self.tr( - "4. (LAMBDAP_DEPENDENT) = Varies with plan area fraction" - ), + self.tr("4. (LAMBDAP_DEPENDENT) = Varies with plan area fraction"), "4", ), ) @@ -199,15 +189,11 @@ def initAlgorithm(self, config): "1", ), ( - self.tr( - "2. (KAWAI) = Kawai et al. (2009) formulation (Default)" - ), + self.tr("2. (KAWAI) = Kawai et al. (2009) formulation (Default)"), "2", ), ( - self.tr( - "3. (VOOGT_GRIMMOND) = Voogt and Grimmond (2000) formulation" - ), + self.tr("3. (VOOGT_GRIMMOND) = Voogt and Grimmond (2000) formulation"), "3", ), (self.tr("4. (KANDA) = Kanda et al. (2007) formulation"), "4"), @@ -350,9 +336,7 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterEnum( self.Z0M, - self.tr( - "Method for calculating momentum roughness length (z0m)" - ), + self.tr("Method for calculating momentum roughness length (z0m)"), options=[i[0] for i in self.z0m], defaultValue=1, ) @@ -360,9 +344,7 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterEnum( self.Z0H, - self.tr( - "Method for calculating thermal roughness length (z0h)" - ), + self.tr("Method for calculating thermal roughness length (z0h)"), options=[i[0] for i in self.z0h], defaultValue=1, ) @@ -388,9 +370,7 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterEnum( self.WU, - self.tr( - "Method for determining external water use (irrigation)" - ), + self.tr("Method for determining external water use (irrigation)"), options=[i[0] for i in self.wu], defaultValue=0, ) @@ -421,9 +401,7 @@ def initAlgorithm(self, config): ) ) self.addParameter( - QgsProcessingParameterFolderDestination( - self.OUTPUT_DIR, "Output folder" - ) + QgsProcessingParameterFolderDestination(self.OUTPUT_DIR, "Output folder") ) # Advanced parameters @@ -435,8 +413,7 @@ def initAlgorithm(self, config): defaultValue=False, ) chunkBool.setFlags( - chunkBool.flags() - | QgsProcessingParameterDefinition.Flag.FlagAdvanced + chunkBool.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced ) self.addParameter(chunkBool) @@ -472,9 +449,7 @@ def processAlgorithm(self, parameters, context, feedback): self.supylib = sys.modules["supy"].__path__[0] feedback.setProgressText(self.supylib) infile = self.parameterAsString(parameters, self.INPUT_FILE, context) - outfolder = self.parameterAsString( - parameters, self.OUTPUT_DIR, context - ) + outfolder = self.parameterAsString(parameters, self.OUTPUT_DIR, context) net = self.parameterAsString(parameters, self.NET, context) qf = self.parameterAsString(parameters, self.ANTHRO, context) @@ -507,9 +482,7 @@ def processAlgorithm(self, parameters, context, feedback): yaml_dict["model"]["physics"]["emissionsmethod"]["value"] = int( self.anthro[int(qf)][1] ) - yaml_dict["model"]["physics"]["ohmincqf"]["value"] = int( - self.ohm[int(ohm)][1] - ) + yaml_dict["model"]["physics"]["ohmincqf"]["value"] = int(self.ohm[int(ohm)][1]) yaml_dict["model"]["physics"]["stabilitymethod"]["value"] = int( self.stab[int(stab)][1] ) @@ -522,21 +495,15 @@ def processAlgorithm(self, parameters, context, feedback): yaml_dict["model"]["physics"]["roughlenheatmethod"]["value"] = int( self.z0h[int(z0h)][1] ) - yaml_dict["model"]["physics"]["smdmethod"]["value"] = int( - self.smd[int(smd)][1] - ) + yaml_dict["model"]["physics"]["smdmethod"]["value"] = int(self.smd[int(smd)][1]) yaml_dict["model"]["physics"]["waterusemethod"]["value"] = int( self.wu[int(wu)][1] ) yaml_dict["model"]["physics"]["rslmethod"] = int( self.rslmethod[int(rslmethod)][1] ) - yaml_dict["model"]["physics"]["rsllevel"] = int( - self.rsllevel[int(rsllevel)][1] - ) - yaml_dict["model"]["control"]["output_file"]["path"] = ( - str(outfolder) + "/" - ) + yaml_dict["model"]["physics"]["rsllevel"] = int(self.rsllevel[int(rsllevel)][1]) + yaml_dict["model"]["control"]["output_file"]["path"] = str(outfolder) + "/" with open(infile, "w") as file: yaml.dump(yaml_dict, file, sort_keys=False) @@ -552,9 +519,7 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText("Loading forcing data") grid = df_state_init.index[0] - df_forcing = sp.load_forcing_grid( - infile, grid, df_state_init=df_state_init - ) + df_forcing = sp.load_forcing_grid(infile, grid, df_state_init=df_state_init) if chunkBool: noOfDays = (df_forcing.index.max() - df_forcing.index.min()).days diff --git a/processor/target_algorithm.py b/processor/target_algorithm.py index b93d3a5..f39761a 100644 --- a/processor/target_algorithm.py +++ b/processor/target_algorithm.py @@ -155,15 +155,11 @@ def initAlgorithm(self, config): def processAlgorithm(self, parameters, context, feedback): # InputParameters - inputDir = self.parameterAsString( - parameters, self.INPUT_FOLDER, context - ) + inputDir = self.parameterAsString(parameters, self.INPUT_FOLDER, context) inputPolygonlayer = self.parameterAsVectorLayer( parameters, self.INPUT_POLYGONLAYER, context ) - startDate = self.parameterAsString( - parameters, self.START_DATE, context - ) + startDate = self.parameterAsString(parameters, self.START_DATE, context) startDateInterest = self.parameterAsString( parameters, self.START_DATE_INTEREST, context ) @@ -172,17 +168,11 @@ def processAlgorithm(self, parameters, context, feedback): ) inputMet = self.parameterAsString(parameters, self.INPUT_MET, context) # outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) - outputCSV = self.parameterAsBoolean( - parameters, self.OUTPUT_CSV, context - ) + outputCSV = self.parameterAsBoolean(parameters, self.OUTPUT_CSV, context) # dtSim = self.parameterAsDouble(parameters, self.DTSIM, context) - mod_Ldown = self.parameterAsBoolean( - parameters, self.MOD_LDOWN, context - ) + mod_Ldown = self.parameterAsBoolean(parameters, self.MOD_LDOWN, context) runName = self.parameterAsString(parameters, self.RUN_NAME, context) - umepformat = self.parameterAsBoolean( - parameters, self.OUTPUT_UMEP, context - ) + umepformat = self.parameterAsBoolean(parameters, self.OUTPUT_UMEP, context) # getting extent, lat lon, and number of x and y grids vlayer = inputPolygonlayer ext = vlayer.extent() @@ -216,12 +206,8 @@ def processAlgorithm(self, parameters, context, feedback): nGridY = int(yExt / gridsize) break - latmin, lonmin = xy2latlon( - vlayer.crs().toWkt(), ext.xMinimum(), ext.yMinimum() - ) - latmax, lonmax = xy2latlon( - vlayer.crs().toWkt(), ext.xMaximum(), ext.yMaximum() - ) + latmin, lonmin = xy2latlon(vlayer.crs().toWkt(), ext.xMinimum(), ext.yMinimum()) + latmax, lonmax = xy2latlon(vlayer.crs().toWkt(), ext.xMaximum(), ext.yMaximum()) # Converting UMEP met-file to target met-file try: @@ -234,14 +220,10 @@ def processAlgorithm(self, parameters, context, feedback): ) metfile["datetime"] = pd.to_datetime( - metfile[["%iy", "id", "it", "imin"]] - .astype(str) - .agg("-".join, axis=1), + metfile[["%iy", "id", "it", "imin"]].astype(str).agg("-".join, axis=1), format="%Y-%j-%H-%M", ) - metfile = metfile[ - ["datetime", "Td", "RH", "Wind", "press", "Kdn", "ldown"] - ] + metfile = metfile[["datetime", "Td", "RH", "Wind", "press", "Kdn", "ldown"]] metfile.columns = ["datetime", "Ta", "RH", "WS", "P", "Kd", "Ld"] startmetfile = metfile["datetime"].min() endmetfile = metfile["datetime"].max() @@ -273,9 +255,7 @@ def processAlgorithm(self, parameters, context, feedback): cfM["mod_ldown"] = "N" if -999 in metfile["Ld"].values: cfM["mod_ldown"] = "Y" - feedback.pushWarning( - "-999 found in Ldown. Ldown will be modelled." - ) + feedback.pushWarning("-999 found in Ldown. Ldown will be modelled.") cfM["domaindim"] = str(nGridX) + "," + str(nGridY) cfM["latedge"] = str(latmin) @@ -283,9 +263,7 @@ def processAlgorithm(self, parameters, context, feedback): cfM["latresolution"] = str(abs(latmin - latmax)) cfM["lonresolution"] = str(abs(lonmin - lonmax)) cfM["date1a"] = ( - datetime.datetime.strptime(startDate, "%Y-%m-%d").strftime( - "%Y,%m,%d" - ) + datetime.datetime.strptime(startDate, "%Y-%m-%d").strftime("%Y,%m,%d") + ",0" ) cfM["date1"] = ( @@ -343,9 +321,7 @@ def processAlgorithm(self, parameters, context, feedback): # run simulation if outputCSV: - tar.run_simulation( - save_csv=True - ) # save model results in csv format + tar.run_simulation(save_csv=True) # save model results in csv format else: tar.run_simulation(save_csv=False) @@ -353,9 +329,7 @@ def processAlgorithm(self, parameters, context, feedback): tar.save_simulation_parameters() end = datetime.datetime.now() - start feedback.setProgressText( - "Model calculation finished. Output is found in " - + inputDir - + "/output" + "Model calculation finished. Output is found in " + inputDir + "/output" ) feedback.setProgressText( "Model calculation time: " + str(end.total_seconds()) + " seconds" @@ -363,9 +337,7 @@ def processAlgorithm(self, parameters, context, feedback): # saving output as umep formatted metfile if umepformat: - feedback.setProgressText( - "Saving data in UMEP formatted text-files." - ) + feedback.setProgressText("Saving data in UMEP formatted text-files.") header = ( "%iy id it imin Q* QH QE Qs Qf Wind RH Td press rain " " Kdn snow ldown fcld wuh xsmd lai_hr Kdiff Kdir Wd" @@ -380,9 +352,7 @@ def processAlgorithm(self, parameters, context, feedback): ) dfin = pd.read_csv(inputMet, sep="\s+") dfin["datetime"] = pd.to_datetime( - dfin[["%iy", "id", "it", "imin"]] - .astype(str) - .agg("-".join, axis=1), + dfin[["%iy", "id", "it", "imin"]].astype(str).agg("-".join, axis=1), format="%Y-%j-%H-%M", ) dfin.set_index("datetime", inplace=True) diff --git a/processor/urock_processing_algorithm.py b/processor/urock_processing_algorithm.py index 2d4677a..9f8670d 100644 --- a/processor/urock_processing_algorithm.py +++ b/processor/urock_processing_algorithm.py @@ -376,9 +376,7 @@ def processAlgorithm(self, parameters, context, feedback): # Get the default value of the Java environment path if already exists javaDirDefault = getJavaDir(plugin_directory) - if ( - not javaDirDefault - ): # Raise an error if could not find a Java installation + if not javaDirDefault: # Raise an error if could not find a Java installation raise QgsProcessingException("No Java installation found") elif ("Program Files (x86)" in javaDirDefault) and ( struct.calcsize("P") * 8 != 32 @@ -391,30 +389,20 @@ def processAlgorithm(self, parameters, context, feedback): ) else: # Set a Java dir if not exist and save it into a file in the plugin repository setJavaDir(javaDirDefault) - saveJavaDir( - javaPath=javaDirDefault, pluginDirectory=plugin_directory - ) + saveJavaDir(javaPath=javaDirDefault, pluginDirectory=plugin_directory) javaEnvVar = javaDirDefault # Get the resource folder where styles are located - resourceDir = os.path.join( - Path(plugin_directory).parent, "functions", "URock" - ) + resourceDir = os.path.join(Path(plugin_directory).parent, "functions", "URock") # Defines inputs - z_ref = self.parameterAsDouble( - parameters, self.INPUT_WIND_HEIGHT, context - ) - v_ref = self.parameterAsDouble( - parameters, self.INPUT_WIND_SPEED, context - ) + z_ref = self.parameterAsDouble(parameters, self.INPUT_WIND_HEIGHT, context) + v_ref = self.parameterAsDouble(parameters, self.INPUT_WIND_SPEED, context) windDirection = self.parameterAsDouble( parameters, self.INPUT_WIND_DIRECTION, context ) - meshSize = self.parameterAsInt( - parameters, self.HORIZONTAL_RESOLUTION, context - ) + meshSize = self.parameterAsInt(parameters, self.HORIZONTAL_RESOLUTION, context) dz = self.parameterAsInt(parameters, self.VERTICAL_RESOLUTION, context) profileType = self.LIST_OF_PROFILES.loc[ self.parameterAsInt(parameters, self.INPUT_PROFILE_TYPE, context) @@ -529,9 +517,9 @@ def processAlgorithm(self, parameters, context, feedback): # prefix = self.parameterAsString(parameters, self.PREFIX, context) # Defines outputs - z_out_str = self.parameterAsString( - parameters, self.WIND_HEIGHT, context - ).split(",") + z_out_str = self.parameterAsString(parameters, self.WIND_HEIGHT, context).split( + "," + ) z_out = [float(i) for i in z_out_str] outputDirectory = self.parameterAsString( parameters, self.OUTPUT_DIRECTORY, context @@ -539,18 +527,10 @@ def processAlgorithm(self, parameters, context, feedback): outputFilename = self.parameterAsString( parameters, self.OUTPUT_FILENAME, context ) - saveRaster = self.parameterAsBool( - parameters, self.SAVE_RASTER, context - ) - saveVector = self.parameterAsBool( - parameters, self.SAVE_VECTOR, context - ) - saveNetcdf = self.parameterAsBool( - parameters, self.SAVE_NETCDF, context - ) - loadOutput = self.parameterAsBool( - parameters, self.LOAD_OUTPUT, context - ) + saveRaster = self.parameterAsBool(parameters, self.SAVE_RASTER, context) + saveVector = self.parameterAsBool(parameters, self.SAVE_VECTOR, context) + saveNetcdf = self.parameterAsBool(parameters, self.SAVE_NETCDF, context) + loadOutput = self.parameterAsBool(parameters, self.LOAD_OUTPUT, context) # Creates the output folder if it does not exist if not os.path.exists(outputDirectory): @@ -568,12 +548,10 @@ def processAlgorithm(self, parameters, context, feedback): "Coordinate system of input building layer and output Raster layer differ!" ) xres = ( - outputRaster.extent().xMaximum() - - outputRaster.extent().xMinimum() + outputRaster.extent().xMaximum() - outputRaster.extent().xMinimum() ) / outputRaster.width() yres = ( - outputRaster.extent().yMaximum() - - outputRaster.extent().yMinimum() + outputRaster.extent().yMaximum() - outputRaster.extent().yMinimum() ) / outputRaster.height() # If there is a raster and no meshSize, take the mean of x and y raster resolution if not meshSize: @@ -702,9 +680,7 @@ def processAlgorithm(self, parameters, context, feedback): os.path.join( outputDirectory, "z{0}".format(str(z_i).replace(".", "_")), - outputFilename - + WIND_SPEED - + OUTPUT_RASTER_EXTENSION, + outputFilename + WIND_SPEED + OUTPUT_RASTER_EXTENSION, ), "Wind speed at {0} m".format(z_i), "gdal", diff --git a/processor/uwg_algorithm.py b/processor/uwg_algorithm.py index a46cc08..773fb1d 100644 --- a/processor/uwg_algorithm.py +++ b/processor/uwg_algorithm.py @@ -120,9 +120,7 @@ def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterBoolean( self.EXCLUDE_RURAL, - self.tr( - "Exculde grids with very small building fractions (< 0.5%)" - ), + self.tr("Exculde grids with very small building fractions (< 0.5%)"), defaultValue=False, ) ) @@ -152,28 +150,18 @@ def processAlgorithm(self, parameters, context, feedback): ) # InputParameters - inputDir = self.parameterAsString( - parameters, self.INPUT_FOLDER, context - ) + inputDir = self.parameterAsString(parameters, self.INPUT_FOLDER, context) inputPolygonlayer = self.parameterAsVectorLayer( parameters, self.INPUT_POLYGONLAYER, context ) idField = self.parameterAsFields(parameters, self.ID_FIELD, context) - startDate = self.parameterAsString( - parameters, self.START_DATE, context - ) + startDate = self.parameterAsString(parameters, self.START_DATE, context) nDays = self.parameterAsDouble(parameters, self.NDAYS, context) inputMet = self.parameterAsString(parameters, self.INPUT_MET, context) - outputDir = self.parameterAsString( - parameters, self.OUTPUT_DIR, context - ) - umepformat = self.parameterAsBoolean( - parameters, self.OUTPUT_FORMAT, context - ) + outputDir = self.parameterAsString(parameters, self.OUTPUT_DIR, context) + umepformat = self.parameterAsBoolean(parameters, self.OUTPUT_FORMAT, context) dtSim = self.parameterAsDouble(parameters, self.DTSIM, context) - excludeRural = self.parameterAsBoolean( - parameters, self.EXCLUDE_RURAL, context - ) + excludeRural = self.parameterAsBoolean(parameters, self.EXCLUDE_RURAL, context) if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): @@ -191,9 +179,7 @@ def processAlgorithm(self, parameters, context, feedback): idx = vlayer.fields().indexFromName(poly_field[0]) nGrids = vlayer.featureCount() - feedback.setProgressText( - "Number of grids to calculate: " + str(nGrids) - ) + feedback.setProgressText("Number of grids to calculate: " + str(nGrids)) mm = startDate[5:7] dd = startDate[8:10] @@ -287,23 +273,12 @@ def processAlgorithm(self, parameters, context, feedback): shutil.move( epw_path, - Path( - outputDir - + "/" - + prefix - + "_" - + str(attr) - + "_UWG.epw" - ), + Path(outputDir + "/" + prefix + "_" + str(attr) + "_UWG.epw"), ) else: - feedback.setProgressText( - "UWG calculating grid: " + str(attr) - ) + feedback.setProgressText("UWG calculating grid: " + str(attr)) try: - model = UWG.from_param_file( - param_path, epw_path=epw_path - ) + model = UWG.from_param_file(param_path, epw_path=epw_path) model.generate() model.simulate() model.write_epw() @@ -333,12 +308,7 @@ def processAlgorithm(self, parameters, context, feedback): shutil.move( uwg_path, Path( - outputDir - + "/" - + prefix - + "_" - + str(attr) - + "_UWG.epw" + outputDir + "/" + prefix + "_" + str(attr) + "_UWG.epw" ), ) @@ -346,10 +316,7 @@ def processAlgorithm(self, parameters, context, feedback): except Exception as e: feedback.pushWarning( - "Calculating grid " - + str(attr) - + " failed: " - + str(e) + "Calculating grid " + str(attr) + " failed: " + str(e) ) feedback.pushWarning( "To get the full traceback error message, open the Python console in QGIS and re-run the simulation." @@ -396,12 +363,7 @@ def processAlgorithm(self, parameters, context, feedback): shutil.move( uwg_path, Path( - outputDir - + "/" - + prefix - + "_" - + str(attr) - + "_UWG.epw" + outputDir + "/" + prefix + "_" + str(attr) + "_UWG.epw" ), ) @@ -418,8 +380,7 @@ def processAlgorithm(self, parameters, context, feedback): "If you cannot solve the error yourself, report an issue to our code reporitory (see UMEP-Manual for details)." ) print( - "Traceback error message while caclulation grid: " - + str(attr) + "Traceback error message while caclulation grid: " + str(attr) ) print(traceback.format_exc()) diff --git a/util/RoughnessCalcFunctionV2.py b/util/RoughnessCalcFunctionV2.py index 180a016..9293c25 100644 --- a/util/RoughnessCalcFunctionV2.py +++ b/util/RoughnessCalcFunctionV2.py @@ -41,9 +41,7 @@ def RoughnessCalcMany(Roughnessmethod, zH, fai, pai, zMax, zSdev): Cdl = 7.5 k = 0.4 RauZdexpW = (math.exp(-((Cdl * 2 * fai[i]) ** 0.5))) - 1 - z_d_output[i] = ( - 1 + (RauZdexpW / ((Cdl * 2 * fai[i]) ** 0.5)) - ) * zH[i] + z_d_output[i] = (1 + (RauZdexpW / ((Cdl * 2 * fai[i]) ** 0.5))) * zH[i] RauZoUtermW = 1 / (min(((Cs + (Cr * fai[i])) ** 0.5), UdivUmax)) RauZoexpW = np.exp((-k * RauZoUtermW) + Stab) z_0_output[i] = ((1 - (z_d_output[i] / zH[i])) * RauZoexpW) * zH[i] @@ -66,9 +64,7 @@ def RoughnessCalcMany(Roughnessmethod, zH, fai, pai, zMax, zSdev): # Alph = 3.59 # Beet = 0.55 if zH[i] > 0.0: - z_d_output[i] = (1 + ((Alph ** -pai[i]) * (pai[i] - 1))) * zH[ - i - ] + z_d_output[i] = (1 + ((Alph ** -pai[i]) * (pai[i] - 1))) * zH[i] if z_d_output[i] != zH[i]: z_0_output[i] = ( zH[i] @@ -118,9 +114,7 @@ def RoughnessCalcMany(Roughnessmethod, zH, fai, pai, zMax, zSdev): # Alph = 3.59 # Beet = 0.55 if zH[i] > 0.0: - z_d_output[i] = (1 + ((Alph ** -pai[i]) * (pai[i] - 1))) * zH[ - i - ] + z_d_output[i] = (1 + ((Alph ** -pai[i]) * (pai[i] - 1))) * zH[i] if z_d_output[i] != zH[i]: z0Mac = ( zH[i] @@ -172,8 +166,7 @@ def RoughnessCalcMany(Roughnessmethod, zH, fai, pai, zMax, zSdev): ZdMho_U = ( ( (117 * pai[i]) - + ((187.2 * (pai[i] ** 3)) - 6.1) - * (1 - np.exp(-19.2 * pai[i])) + + ((187.2 * (pai[i] ** 3)) - 6.1) * (1 - np.exp(-19.2 * pai[i])) ) / ( (1 + (114 * pai[i]) + (187 * pai[i] ** 3)) @@ -242,12 +235,7 @@ def RoughnessCalc(Roughnessmethod, zH, fai, pai, zMax, zSdev): * ((1 - z_d_output / zH)) * np.exp( -( - ( - 0.5 - * (1.2 / 0.4**2) - * (1 - (z_d_output / zH)) - * fai - ) + (0.5 * (1.2 / 0.4**2) * (1 - (z_d_output / zH)) * fai) ** -0.5 ) ) @@ -292,12 +280,7 @@ def RoughnessCalc(Roughnessmethod, zH, fai, pai, zMax, zSdev): * ((1 - z_d_output / zH)) * np.exp( -( - ( - 0.5 - * (1.2 / 0.4**2) - * (1 - (z_d_output / zH)) - * fai - ) + (0.5 * (1.2 / 0.4**2) * (1 - (z_d_output / zH)) * fai) ** -0.5 ) ) @@ -306,9 +289,7 @@ def RoughnessCalc(Roughnessmethod, zH, fai, pai, zMax, zSdev): z0Mac = 0.0 X = (zSdev + zH) / zMax if 0 < X <= 1: - z_d_output = ( - (Co * (X**2)) + ((((Ao * (pai**Bo)) - Co)) * X) - ) * zMax + z_d_output = ((Co * (X**2)) + ((((Ao * (pai**Bo)) - Co)) * X)) * zMax else: z_d_output = (Ao * (pai**Bo)) * zH Y = (pai * zSdev) / zH @@ -335,28 +316,18 @@ def RoughnessCalc(Roughnessmethod, zH, fai, pai, zMax, zSdev): ) * zH elif pai < 0.19: ZdMho_U = ( - ( - (117 * pai) - + ((187.2 * (pai**3)) - 6.1) * (1 - np.exp(-19.2 * pai)) - ) - / ( - (1 + (114 * pai) + (187 * pai**3)) - * (1 - (np.exp(-19.2 * pai))) - ) + ((117 * pai) + ((187.2 * (pai**3)) - 6.1) * (1 - np.exp(-19.2 * pai))) + / ((1 + (114 * pai) + (187 * pai**3)) * (1 - (np.exp(-19.2 * pai)))) ) * zH ZoMhoexp_U = np.exp(-((0.5 * CD * (k**-2) * fai) ** -0.5)) if zH > 0.0: ZoMho_U = ((1 - (ZdMho_U / zH)) * ZoMhoexp_U) * zH ZdMho_UCor = zH * ( - (ZdMho_U / zH) - + ((0.2375 * np.log(pai) + 1.1738) * (zSdev / zH)) + (ZdMho_U / zH) + ((0.2375 * np.log(pai) + 1.1738) * (zSdev / zH)) ) ZoMho_UCor = zH * ( (ZoMho_U / zH) - + ( - np.exp((0.8867 * fai) - 1) - * ((zSdev / zH) ** np.exp(2.3271 * fai)) - ) + + (np.exp((0.8867 * fai) - 1) * ((zSdev / zH) ** np.exp(2.3271 * fai))) ) z_d_output = ZdMho_UCor z_0_output = ZoMho_UCor diff --git a/util/SEBESOLWEIGCommonFiles/Perez_v3.py b/util/SEBESOLWEIGCommonFiles/Perez_v3.py index e9ef8a2..f62134b 100644 --- a/util/SEBESOLWEIGCommonFiles/Perez_v3.py +++ b/util/SEBESOLWEIGCommonFiles/Perez_v3.py @@ -3,9 +3,7 @@ import torch -def Perez_v3( - zen, azimuth, radD, radI, jday, patchchoice, patch_option, device -): +def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice, patch_option, device): """ This function calculates distribution of luminance on the skyvault based on Perez luminince distribution model. @@ -262,20 +260,17 @@ def Perez_v3( m_a = ( acoeff[intClearness, 0] + acoeff[intClearness, 1] * zen - + PerezBrightness - * (acoeff[intClearness, 2] + acoeff[intClearness, 3] * zen) + + PerezBrightness * (acoeff[intClearness, 2] + acoeff[intClearness, 3] * zen) ) m_b = ( bcoeff[intClearness, 0] + bcoeff[intClearness, 1] * zen - + PerezBrightness - * (bcoeff[intClearness, 2] + bcoeff[intClearness, 3] * zen) + + PerezBrightness * (bcoeff[intClearness, 2] + bcoeff[intClearness, 3] * zen) ) m_e = ( ecoeff[intClearness, 0] + ecoeff[intClearness, 1] * zen - + PerezBrightness - * (ecoeff[intClearness, 2] + ecoeff[intClearness, 3] * zen) + + PerezBrightness * (ecoeff[intClearness, 2] + ecoeff[intClearness, 3] * zen) ) if intClearness > 0: @@ -296,9 +291,7 @@ def Perez_v3( torch.exp( torch.pow( PerezBrightness - * ( - ccoeff[intClearness, 0] + ccoeff[intClearness, 1] * zen - ), + * (ccoeff[intClearness, 0] + ccoeff[intClearness, 1] * zen), ccoeff[intClearness, 2], ) ) diff --git a/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload.py b/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload.py index 551e12b..85315eb 100644 --- a/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload.py +++ b/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload.py @@ -21,9 +21,7 @@ def Solweig_2015a_metdata_noload(inputdata, location, UTC): """ met = inputdata - device = ( - met.device if isinstance(met, torch.Tensor) else torch.device("cpu") - ) + device = met.device if isinstance(met, torch.Tensor) else torch.device("cpu") data_len = len(met[:, 0]) dectime = torch.tensor(met[:, 1] + met[:, 2] / 24 + met[:, 3] / (60 * 24.0)) dectimemin = met[:, 3] / (60 * 24.0) @@ -56,9 +54,7 @@ def Solweig_2015a_metdata_noload(inputdata, location, UTC): int(met[i, 1]) - 1 ) # Finding maximum altitude in 15 min intervals (20141027) - if (i == 0) or ( - torch.remainder(dectime[i], torch.floor(dectime[i])) == 0 - ): + if (i == 0) or (torch.remainder(dectime[i], torch.floor(dectime[i])) == 0): fifteen = 0.0 sunmaximum = -90.0 sunmax["zenith"] = 90.0 diff --git a/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b.py b/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b.py index eca194f..451f63e 100644 --- a/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b.py +++ b/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b.py @@ -31,9 +31,7 @@ def clearnessindex_2013b(zen, jday, Ta, RH, radG, location, P): jday ) # irradiance differences due to Sun-Earth distances m = ( - 35.0 - * torch.cos(zen) - * ((1224.0 * (torch.cos(zen) ** 2) + 1) ** (-1 / 2.0)) + 35.0 * torch.cos(zen) * ((1224.0 * (torch.cos(zen) ** 2) + 1) ** (-1 / 2.0)) ) # optical air mass at p=1013 Trpg = ( 1.021 - 0.084 * (m * (0.000949 * p + 0.051)) ** 0.5 @@ -67,9 +65,7 @@ def clearnessindex_2013b(zen, jday, Ta, RH, radG, location, P): G = G[2] elif jday > 244 and jday <= 335: G = G[3] - device = ( - zen.device if isinstance(zen, torch.Tensor) else torch.device("cpu") - ) + device = zen.device if isinstance(zen, torch.Tensor) else torch.device("cpu") G = torch.tensor(G, device=device) # dewpoint calculation @@ -79,12 +75,8 @@ def clearnessindex_2013b(zen, jday, Ta, RH, radG, location, P): a2 - (((a2 * Ta) / (b2 + Ta)) + torch.log(RH)) ) Td = (Td * 1.8) + 32 # Dewpoint (F) - u = torch.exp( - 0.1133 - torch.log(G + 1) + 0.0393 * Td - ) # Precipitable water - Tw = 1 - 0.077 * ( - (u * m) ** 0.3 - ) # Transmission coefficient for water vapor + u = torch.exp(0.1133 - torch.log(G + 1) + 0.0393 * Td) # Precipitable water + Tw = 1 - 0.077 * ((u * m) ** 0.3) # Transmission coefficient for water vapor Tar = 0.935**m # Transmission coefficient for aerosols I0 = Itoa * torch.cos(zen) * Trpg * Tw * D * Tar diff --git a/util/SEBESOLWEIGCommonFiles/create_patches.py b/util/SEBESOLWEIGCommonFiles/create_patches.py index 02f0e0f..31c7c2c 100644 --- a/util/SEBESOLWEIGCommonFiles/create_patches.py +++ b/util/SEBESOLWEIGCommonFiles/create_patches.py @@ -16,9 +16,7 @@ def create_patches(patch_option, device): # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) # Patch option 1, 145 patches, Original Robinson & Stone (2004) after Tregenza (1987)/Tregenza & Sharples (1993) if patch_option == 1: - annulino = torch.tensor( - [0, 12, 24, 36, 48, 60, 72, 84, 90], device=device - ) + annulino = torch.tensor([0, 12, 24, 36, 48, 60, 72, 84, 90], device=device) skyvaultaltint = torch.tensor( [6, 18, 30, 42, 54, 66, 78, 90], device=device ) # Robinson & Stone (2004) @@ -30,9 +28,7 @@ def create_patches(patch_option, device): ) # Robinson & Stone (2004) # Patch option 2, 153 patches, Wallenberg et al. (2022) elif patch_option == 2: - annulino = torch.tensor( - [0, 12, 24, 36, 48, 60, 72, 84, 90], device=device - ) + annulino = torch.tensor([0, 12, 24, 36, 48, 60, 72, 84, 90], device=device) skyvaultaltint = torch.tensor( [6, 18, 30, 42, 54, 66, 78, 90], device=device ) # Robinson & Stone (2004) @@ -44,9 +40,7 @@ def create_patches(patch_option, device): ) # Nils # Patch option 3, 306 patches, test elif patch_option == 3: - annulino = torch.tensor( - [0, 12, 24, 36, 48, 60, 72, 84, 90], device=device - ) + annulino = torch.tensor([0, 12, 24, 36, 48, 60, 72, 84, 90], device=device) skyvaultaltint = torch.tensor( [6, 18, 30, 42, 54, 66, 78, 90], device=device ) # Robinson & Stone (2004) @@ -91,9 +85,7 @@ def create_patches(patch_option, device): [0, 0, 4, 4, 2, 2, 5, 5, 8, 8, 0, 0, 10, 10, 0], device=device ) # Nils - skyvaultaziint = torch.tensor( - [360 / patches for patches in patches_in_band] - ) + skyvaultaziint = torch.tensor([360 / patches for patches in patches_in_band]) for j in range(0, skyvaultaltint.shape[0]): for k in range(0, patches_in_band[j]): diff --git a/util/SEBESOLWEIGCommonFiles/diffusefraction.py b/util/SEBESOLWEIGCommonFiles/diffusefraction.py index 5c7970d..a4f96e7 100644 --- a/util/SEBESOLWEIGCommonFiles/diffusefraction.py +++ b/util/SEBESOLWEIGCommonFiles/diffusefraction.py @@ -28,26 +28,15 @@ def diffusefraction(radG, altitude, Kt, Ta, RH): RH = RH / 100 if Kt <= 0.3: radD = radG * ( - 1 - - 0.232 * Kt - + 0.0239 * torch.sin(alfa) - - 0.000682 * Ta - + 0.0195 * RH + 1 - 0.232 * Kt + 0.0239 * torch.sin(alfa) - 0.000682 * Ta + 0.0195 * RH ) elif Kt > 0.3 and Kt < 0.78: radD = radG * ( - 1.329 - - 1.716 * Kt - + 0.267 * torch.sin(alfa) - - 0.00357 * Ta - + 0.106 * RH + 1.329 - 1.716 * Kt + 0.267 * torch.sin(alfa) - 0.00357 * Ta + 0.106 * RH ) else: radD = radG * ( - 0.426 * Kt - - 0.256 * torch.sin(alfa) - + 0.00349 * Ta - + 0.0734 * RH + 0.426 * Kt - 0.256 * torch.sin(alfa) + 0.00349 * Ta + 0.0734 * RH ) radI = (radG - radD) / (torch.sin(alfa)) diff --git a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py index 1d08ca6..4d70016 100644 --- a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py +++ b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py @@ -41,9 +41,7 @@ def shade_on_walls(azimuth, aspect, walls, dsm, f, device): # Calculate the height of the wall segment receiving direct sunlight wallsun = walls - sh wallsun = torch.clamp(wallsun, min=0.0) - wallsun = torch.where( - facesh == 1, torch.tensor(0.0, device=device), wallsun - ) + wallsun = torch.where(facesh == 1, torch.tensor(0.0, device=device), wallsun) # The shadowed wall height is the total wall height minus the sunlit segment wallsh = walls - wallsun diff --git a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py index 72c2c43..3826600 100644 --- a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py +++ b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py @@ -15,9 +15,7 @@ def shade_on_walls(azimuth, aspect, walls, dsm, f, shvoveg, device): wallsun = walls - shvo wallsun = torch.clamp(wallsun, min=0.0) - wallsun = torch.where( - facesh == 1, torch.tensor(0.0, device=device), wallsun - ) + wallsun = torch.where(facesh == 1, torch.tensor(0.0, device=device), wallsun) wallsh = walls - wallsun @@ -190,22 +188,16 @@ def shadowingfunction_wallheight_23( is_pergola = fabovea & gabovea & lastfabovea & lastgabovea # The shadow exists if one of the layers is True, UNLESS it's a pergola. - vegsh2 = (fabovea | gabovea | lastfabovea | lastgabovea) & ( - ~is_pergola - ) + vegsh2 = (fabovea | gabovea | lastfabovea | lastgabovea) & (~is_pergola) vegsh2 = vegsh2.float() # Accumulation of vegetation shadows vegsh = torch.maximum(vegsh, vegsh2) - vegsh = torch.where( - vegsh * sh > 0, torch.tensor(0.0, device=device), vegsh - ) + vegsh = torch.where(vegsh * sh > 0, torch.tensor(0.0, device=device), vegsh) vbshvegsh.add_(vegsh) sh = 1 - sh - vbshvegsh = torch.where( - vbshvegsh > 0, torch.tensor(1.0, device=device), vbshvegsh - ) + vbshvegsh = torch.where(vbshvegsh > 0, torch.tensor(1.0, device=device), vbshvegsh) vbshvegsh = vbshvegsh - vegsh vegsh = torch.where(vegsh > 0, torch.tensor(1.0, device=device), vegsh) @@ -224,9 +216,7 @@ def shadowingfunction_wallheight_23( ) # print(torch.max(wallshve_)) shade_on_wall = wallsh_.clone() - shade_on_wall[shade_on_wall < wallshve_] = wallshve_[ - shade_on_wall < wallshve_ - ] + shade_on_wall[shade_on_wall < wallshve_] = wallshve_[shade_on_wall < wallshve_] return ( ( diff --git a/util/SEBESOLWEIGCommonFiles/sun_position.py b/util/SEBESOLWEIGCommonFiles/sun_position.py index 1f309ff..8f13945 100644 --- a/util/SEBESOLWEIGCommonFiles/sun_position.py +++ b/util/SEBESOLWEIGCommonFiles/sun_position.py @@ -109,9 +109,7 @@ def sun_position(time, location): # 2. Calculate the Earth heliocentric longitude, latitude, and radius # vector (L, B, and R) - earth_heliocentric_position = earth_heliocentric_position_calculation( - julian - ) + earth_heliocentric_position = earth_heliocentric_position_calculation(julian) # 3. Calculate the geocentric longitude and latitude sun_geocentric_position = sun_geocentric_position_calculation( @@ -222,9 +220,7 @@ def julian_calculation(t_input): # In 1582, the gregorian calendar was adopted if time["year"] == 1582: if time["month"] == 10: - if ( - time["day"] <= 4 - ): # The Julian calendar ended on October 4, 1582 + if time["day"] <= 4: # The Julian calendar ended on October 4, 1582 B = torch.tensor(0, device=device) elif ( time["day"] >= 15 @@ -502,9 +498,7 @@ def earth_heliocentric_position_calculation(julian): device=device, ) - B1_terms = torch.tensor( - [[9, 3.9, 5507.55], [6, 1.73, 5223.69]], device=device - ) + B1_terms = torch.tensor([[9, 3.9, 5507.55], [6, 1.73, 5223.69]], device=device) A0 = B0_terms[:, 0] B0 = B0_terms[:, 1] @@ -667,9 +661,7 @@ def sun_geocentric_position_calculation(earth_heliocentric_position): sun_geocentric_position["longitude"], 0, 360 ) - sun_geocentric_position["latitude"] = -earth_heliocentric_position[ - "latitude" - ] + sun_geocentric_position["latitude"] = -earth_heliocentric_position["latitude"] # Limit the range to [0,360] sun_geocentric_position["latitude"] = set_to_range( sun_geocentric_position["latitude"], 0, 360 @@ -691,9 +683,7 @@ def nutation_calculation(julian): # 1. Mean elongation of the moon from the sun p = torch.atleast_2d( - torch.tensor( - [(1 / 189474), -0.0019142, 445267.11148, 297.85036], device=device - ) + torch.tensor([(1 / 189474), -0.0019142, 445267.11148, 297.85036], device=device) ) # X0 = polyval(p, JCE); @@ -706,9 +696,7 @@ def nutation_calculation(julian): # 2. Mean anomaly of the sun (earth) p = torch.atleast_2d( - torch.tensor( - [-(1 / 300000), -0.0001603, 35999.05034, 357.52772], device=device - ) + torch.tensor([-(1 / 300000), -0.0001603, 35999.05034, 357.52772], device=device) ) # X1 = polyval(p, JCE) @@ -721,9 +709,7 @@ def nutation_calculation(julian): # 3. Mean anomaly of the moon p = torch.atleast_2d( - torch.tensor( - [(1 / 56250), 0.0086972, 477198.867398, 134.96298], device=device - ) + torch.tensor([(1 / 56250), 0.0086972, 477198.867398, 134.96298], device=device) ) # X2 = polyval(p, JCE); @@ -736,9 +722,7 @@ def nutation_calculation(julian): # 4. Moon argument of latitude p = torch.atleast_2d( - torch.tensor( - [(1 / 327270), -0.0036825, 483202.017538, 93.27191], device=device - ) + torch.tensor([(1 / 327270), -0.0036825, 483202.017538, 93.27191], device=device) ) # X3 = polyval(p, JCE) @@ -752,9 +736,7 @@ def nutation_calculation(julian): # 5. Longitude of the ascending node of the moon's mean orbit on the # ecliptic, measured from the mean equinox of the date p = torch.atleast_2d( - torch.tensor( - [(1 / 450000), 0.0020708, -1934.136261, 125.04452], device=device - ) + torch.tensor([(1 / 450000), 0.0020708, -1934.136261, 125.04452], device=device) ) # X4 = polyval(p, JCE); @@ -906,18 +888,16 @@ def nutation_calculation(julian): # Using the tabulated values, compute the delta_longitude and # delta_obliquity. - Xi = torch.tensor( - [X0, X1, X2, X3, X4], device=device - ) # a col mat in octave + Xi = torch.tensor([X0, X1, X2, X3, X4], device=device) # a col mat in octave tabulated_argument = torch.matmul(Y_terms.float(), Xi) * (torch.pi / 180) - delta_longitude = ( - nutation_terms[:, 0] + (nutation_terms[:, 1] * JCE) - ) * torch.sin(tabulated_argument) - delta_obliquity = ( - nutation_terms[:, 2] + (nutation_terms[:, 3] * JCE) - ) * torch.cos(tabulated_argument) + delta_longitude = (nutation_terms[:, 0] + (nutation_terms[:, 1] * JCE)) * torch.sin( + tabulated_argument + ) + delta_obliquity = (nutation_terms[:, 2] + (nutation_terms[:, 3] * JCE)) * torch.cos( + tabulated_argument + ) nutation = dict() # init nutation dictionary # Nutation in longitude @@ -987,9 +967,7 @@ def abberation_correction_calculation(earth_heliocentric_position): :param earth_heliocentric_position: :return: """ - aberration_correction = -20.4898 / ( - 3600 * earth_heliocentric_position["radius"] - ) + aberration_correction = -20.4898 / (3600 * earth_heliocentric_position["radius"]) return aberration_correction @@ -1063,9 +1041,7 @@ def sun_rigth_ascension_calculation( argument_denominator = torch.cos(apparent_sun_longitude * torch.pi / 180) sun_rigth_ascension = ( - torch.arctan2(argument_numerator, argument_denominator) - * 180 - / torch.pi + torch.arctan2(argument_numerator, argument_denominator) * 180 / torch.pi ) # Limit the range to [0,360]; sun_rigth_ascension = set_to_range(sun_rigth_ascension, 0, 360) @@ -1109,9 +1085,7 @@ def observer_local_hour_calculation( """ observer_local_hour = ( - apparent_stime_at_greenwich - + location["longitude"] - - sun_rigth_ascension + apparent_stime_at_greenwich + location["longitude"] - sun_rigth_ascension ) # Set the range to [0-360] observer_local_hour = set_to_range(observer_local_hour, 0, 360) @@ -1138,14 +1112,11 @@ def topocentric_sun_position_calculate( """ # Equatorial horizontal parallax of the sun in degrees - eq_horizontal_parallax = 8.794 / ( - 3600 * earth_heliocentric_position["radius"] - ) + eq_horizontal_parallax = 8.794 / (3600 * earth_heliocentric_position["radius"]) # Term u, used in the following calculations (in radians) u = torch.arctan( - 0.99664719 - * torch.tan(torch.as_tensor(location["latitude"]) * torch.pi / 180) + 0.99664719 * torch.tan(torch.as_tensor(location["latitude"]) * torch.pi / 180) ) # Term x, used in the following calculations @@ -1197,9 +1168,7 @@ def topocentric_sun_position_calculate( return topocentric_sun_position -def topocentric_local_hour_calculate( - observer_local_hour, topocentric_sun_position -): +def topocentric_local_hour_calculate(observer_local_hour, topocentric_sun_position): """ This function compute the topocentric local jour angle in degrees @@ -1209,8 +1178,7 @@ def topocentric_local_hour_calculate( """ topocentric_local_hour = ( - observer_local_hour - - topocentric_sun_position["rigth_ascension_parallax"] + observer_local_hour - topocentric_sun_position["rigth_ascension_parallax"] ) return topocentric_local_hour @@ -1233,16 +1201,12 @@ def sun_topocentric_zenith_angle_calculate( argument = ( torch.sin(torch.as_tensor(location["latitude"]) * torch.pi / 180) * torch.sin( - torch.as_tensor(topocentric_sun_position["declination"]) - * torch.pi - / 180 + torch.as_tensor(topocentric_sun_position["declination"]) * torch.pi / 180 ) ) + ( torch.cos(torch.as_tensor(location["latitude"]) * torch.pi / 180) * torch.cos( - torch.as_tensor(topocentric_sun_position["declination"]) - * torch.pi - / 180 + torch.as_tensor(topocentric_sun_position["declination"]) * torch.pi / 180 ) * torch.cos(torch.as_tensor(topocentric_local_hour) * torch.pi / 180) ) @@ -1265,23 +1229,17 @@ def sun_topocentric_zenith_angle_calculate( # Topocentric azimuth angle. The +180 conversion is to pass from astronomer # notation (westward from south) to navigation notation (eastward from # north); - nominator = torch.sin( - torch.as_tensor(topocentric_local_hour * torch.pi / 180) - ) + nominator = torch.sin(torch.as_tensor(topocentric_local_hour * torch.pi / 180)) denominator = ( torch.cos(torch.as_tensor(topocentric_local_hour) * torch.pi / 180) * torch.sin(torch.as_tensor(location["latitude"]) * torch.pi / 180) ) - ( torch.tan( - torch.as_tensor(topocentric_sun_position["declination"]) - * torch.pi - / 180 + torch.as_tensor(topocentric_sun_position["declination"]) * torch.pi / 180 ) * torch.cos(torch.as_tensor(location["latitude"]) * torch.pi / 180) ) - sun["azimuth"] = ( - torch.arctan2(nominator, denominator) * 180 / torch.pi - ) + 180 + sun["azimuth"] = (torch.arctan2(nominator, denominator) * 180 / torch.pi) + 180 # Set the range to [0-360] sun["azimuth"] = set_to_range(sun["azimuth"], 0, 360) diff --git a/util/f90nml/fpy.py b/util/f90nml/fpy.py index 950fcc5..cad6e37 100644 --- a/util/f90nml/fpy.py +++ b/util/f90nml/fpy.py @@ -26,9 +26,7 @@ def pycomplex(v_str): # NOTE: Failed float(str) will raise ValueError return complex(pyfloat(v_re), pyfloat(v_im)) else: - raise ValueError( - "{0} must be in complex number form (x, y)." "".format(v_str) - ) + raise ValueError("{0} must be in complex number form (x, y)." "".format(v_str)) def pybool(v_str): diff --git a/util/f90nml/namelist.py b/util/f90nml/namelist.py index 0da7f84..0a77418 100644 --- a/util/f90nml/namelist.py +++ b/util/f90nml/namelist.py @@ -95,9 +95,7 @@ def indent(self, value): if value.isspace(): self._indent = value else: - raise ValueError( - "String indentation can only contain " "whitespace." - ) + raise ValueError("String indentation can only contain " "whitespace.") # Set indent width elif isinstance(value, int): @@ -185,12 +183,9 @@ def true_repr(self): def true_repr(self, value): """Validate and set the logical true representation.""" if isinstance(value, str): - if not ( - value.lower().startswith("t") or value.lower().startswith(".t") - ): + if not (value.lower().startswith("t") or value.lower().startswith(".t")): raise ValueError( - "Logical true representation must start with " - "'T' or '.T'." + "Logical true representation must start with " "'T' or '.T'." ) else: self._logical_repr[1] = value @@ -206,12 +201,9 @@ def false_repr(self): def false_repr(self, value): """Validate and set the logical false representation.""" if isinstance(value, str): - if not ( - value.lower().startswith("f") or value.lower().startswith(".f") - ): + if not (value.lower().startswith("f") or value.lower().startswith(".f")): raise ValueError( - "Logical false representation must start " - "with 'F' or '.F'." + "Logical false representation must start " "with 'F' or '.F'." ) else: self._logical_repr[0] = value @@ -310,9 +302,7 @@ def var_strings(self, v_name, v_values): # Append any remaining values if val_line: - if self.end_comma or ( - len(v_values) > 1 and v_values[-1] is None - ): + if self.end_comma or (len(v_values) > 1 and v_values[-1] is None): val_strs.append(val_line) else: val_strs.append(val_line[:-2]) diff --git a/util/f90nml/parser.py b/util/f90nml/parser.py index 05d44f3..c3eba2c 100644 --- a/util/f90nml/parser.py +++ b/util/f90nml/parser.py @@ -251,8 +251,7 @@ def parse_variable(self, parent, patch_nml=None): # First check for implicit null values if self.prior_token in ("=", "%", ","): if self.token in (",", "/", "&", "$") and not ( - self.prior_token == "," - and self.token in ("/", "&", "$") + self.prior_token == "," and self.token in ("/", "&", "$") ): append_value(v_values, None, v_idx, n_vals) @@ -262,8 +261,7 @@ def parse_variable(self, parent, patch_nml=None): self.update_tokens(write_token) if self.token == "=" or ( - self.token in ("/", "&", "$") - and self.prior_token == "*" + self.token in ("/", "&", "$") and self.prior_token == "*" ): next_value = None else: @@ -286,9 +284,7 @@ def parse_variable(self, parent, patch_nml=None): ): quote_char = self.prior_token[0] - v_values[-1] = quote_char.join( - [v_values[-1], next_value] - ) + v_values[-1] = quote_char.join([v_values[-1], next_value]) else: append_value(v_values, next_value, v_idx, n_vals) @@ -352,23 +348,18 @@ def parse_index(self): except ValueError: if self.token == ")": raise ValueError( - "{0} stride index cannot be " - "implicit.".format(v_name) + "{0} stride index cannot be " "implicit.".format(v_name) ) else: raise if i_stride == 0: - raise ValueError( - "{0} stride index cannot be zero." "".format(v_name) - ) + raise ValueError("{0} stride index cannot be zero." "".format(v_name)) self.update_tokens() if self.token not in (",", ")"): - raise ValueError( - "{0} index did not terminate " "correctly.".format(v_name) - ) + raise ValueError("{0} index did not terminate " "correctly.".format(v_name)) idx_triplet = (i_start, i_end, i_stride) v_indices.append((idx_triplet)) diff --git a/util/imageMorphometricParms_v2.py b/util/imageMorphometricParms_v2.py index 8bb0380..6c5672f 100644 --- a/util/imageMorphometricParms_v2.py +++ b/util/imageMorphometricParms_v2.py @@ -31,9 +31,7 @@ def imagemorphparam_v2(dsm, dem, scale, mid, dtheta, feedback, imp_point): - numPixels = len( - dsm[np.where(dsm != -9999)] - ) # too deal with irregular grids + numPixels = len(dsm[np.where(dsm != -9999)]) # too deal with irregular grids build = dsm - dem build[(build < 3.0)] = ( @@ -82,9 +80,7 @@ def imagemorphparam_v2(dsm, dem, scale, mid, dtheta, feedback, imp_point): # Rotating buildings # d = sc.rotate(build, angle, order=0, reshape=False, mode='nearest') #old - a = sc.rotate( - build, angle, order=0, reshape=True, mode="constant", cval=-99 - ) + a = sc.rotate(build, angle, order=0, reshape=True, mode="constant", cval=-99) # % convolve leading edge filter with domain c = a * 0.0 @@ -102,9 +98,7 @@ def imagemorphparam_v2(dsm, dem, scale, mid, dtheta, feedback, imp_point): ) for i in np.arange(1, c.shape[0]): - c[int(i) - 1, :] = np.sum( - (filt * buildZero[int(i) - 1 : i + 1, :]), 0 - ) + c[int(i) - 1, :] = np.sum((filt * buildZero[int(i) - 1 : i + 1, :]), 0) if mid == 1: # from center point ny = a.shape[0] @@ -122,9 +116,7 @@ def imagemorphparam_v2(dsm, dem, scale, mid, dtheta, feedback, imp_point): wall = wall[np.where(wall > 2)] # wall vector fai[j] = np.sum(wall) / ((lx * ly) / scale) - bld = bld[ - np.where(bld > 2) - ] # building vector: change from 0 to 2 : 20150906 + bld = bld[np.where(bld > 2)] # building vector: change from 0 to 2 : 20150906 pai[j] = np.float32(bld.shape[0]) / (lx * ly) deg[j] = angle if np.float32(bld.shape[0]) == 0: diff --git a/util/landCoverFractions_v2.py b/util/landCoverFractions_v2.py index 9c9c85f..6931a50 100644 --- a/util/landCoverFractions_v2.py +++ b/util/landCoverFractions_v2.py @@ -26,8 +26,7 @@ def landcover_v2(lc_grid, mid, dtheta, feedback, imp_point, iter): if lc_gridvec.size > 0: # lc_frac_all[0, i] = round((lc_gridvec.size * 1.0) / (lc_grid.size * 1.0), 3) lc_frac_all[0, i] = round( - (lc_gridvec.size * 1.0) - / (lc_grid.size - (lc_grid == 0).sum()), + (lc_gridvec.size * 1.0) / (lc_grid.size - (lc_grid == 0).sum()), 3, ) # ignoring NoData (0) pixels @@ -52,9 +51,7 @@ def landcover_v2(lc_grid, mid, dtheta, feedback, imp_point, iter): feedback.setProgress(int(angle / 3.6)) # d = sc.rotate(lc_grid, angle, order=0, reshape=False, mode='nearest') #old - d = sc.rotate( - lc_grid, angle, order=0, reshape=True, mode="constant", cval=-99 - ) + d = sc.rotate(lc_grid, angle, order=0, reshape=True, mode="constant", cval=-99) n = d.shape[1] imid = np.floor((n / 2.0)) # the mid (NtoS) line of the grid diff --git a/util/misc.py b/util/misc.py index 6d9783b..eba6960 100644 --- a/util/misc.py +++ b/util/misc.py @@ -222,9 +222,7 @@ def get_resolution_from_file(folder_path): # Combine Year, DOY, Hour, Min into a datetime index df_suews["Datetime"] = to_datetime( - df_suews[["Year", "DOY", "Hour", "Min"]] - .astype(str) - .agg("-".join, axis=1), + df_suews[["Year", "DOY", "Hour", "Min"]].astype(str).agg("-".join, axis=1), format="%Y-%j-%H-%M", ) df_suews.set_index("Datetime", inplace=True) @@ -253,9 +251,7 @@ def SUEWS_txt_to_df(suews_output_path): def SUEWS_met_txt_to_df(suews_met_path): df_met_forcing = read_csv(suews_met_path, delim_whitespace=True) df_met_forcing["Datetime"] = to_datetime( - df_met_forcing[["iy", "id", "it", "imin"]] - .astype(str) - .agg("-".join, axis=1), + df_met_forcing[["iy", "id", "it", "imin"]].astype(str).agg("-".join, axis=1), format="%Y-%j-%H-%M", ) df_met_forcing.set_index("Datetime", inplace=True) @@ -302,9 +298,7 @@ def xy2latlon_fromraster(crsWtkIn, gdal_dsm): heightx = gdal_dsm.RasterYSize geotransform = gdal_dsm.GetGeoTransform() minx = geotransform[0] - miny = ( - geotransform[3] + widthx * geotransform[4] + heightx * geotransform[5] - ) + miny = geotransform[3] + widthx * geotransform[4] + heightx * geotransform[5] lonlat = transform.TransformPoint(minx, miny) gdalver = float(gdal.__version__[0]) diff --git a/util/ncWMSConnector.py b/util/ncWMSConnector.py index 3ebfffd..20331d6 100644 --- a/util/ncWMSConnector.py +++ b/util/ncWMSConnector.py @@ -204,9 +204,7 @@ def get_data( end_date_candidate = end_date + td(seconds=3600 * 24 - 1) final_date = True else: - end_date_candidate = start_dates[s + 1] - td( - seconds=self.time_res - ) + end_date_candidate = start_dates[s + 1] - td(seconds=self.time_res) final_date = False if end_date_candidate > self.end_date: @@ -244,9 +242,7 @@ def convert_to_nc3(self): # Convert each file to netcdf4_classic so it can be used with MFDataset for file_date in list(self.results.keys()): tmp = tempfile.mkstemp(suffix=".nc") - new_data = nc4.Dataset( - tmp, "w", clobber=True, format="NETCDF3_CLASSIC" - ) + new_data = nc4.Dataset(tmp, "w", clobber=True, format="NETCDF3_CLASSIC") extant = nc4.Dataset(self.results[file_date]) # from https://gist.github.com/guziy/8543562 @@ -262,13 +258,9 @@ def convert_to_nc3(self): else: dtype = varin.datatype - outVar = new_data.createVariable( - v_name, dtype, varin.dimensions - ) + outVar = new_data.createVariable(v_name, dtype, varin.dimensions) # Copy variable attributes - outVar.setncatts( - {k: varin.getncattr(k) for k in varin.ncattrs()} - ) + outVar.setncatts({k: varin.getncattr(k) for k in varin.ncattrs()}) outVar[:] = varin[:] new_data.close() @@ -304,17 +296,13 @@ def average_data(self, period, method): :return: """ - combined_data = nc4.MFDataset( - list(self.results.values()), aggdim="time" - ) + combined_data = nc4.MFDataset(list(self.results.values()), aggdim="time") # Create new netCDF file that'll contain averaged/combined data and delete the individual files # from https://gist.github.com/guziy/8543562 # Go round the variables and average them tmp = tempfile.mkstemp(suffix=".nc") - new_data = nc4.Dataset( - tmp, "w", clobber=True, format="NETCDF3_CLASSIC" - ) + new_data = nc4.Dataset(tmp, "w", clobber=True, format="NETCDF3_CLASSIC") times = combined_data.variables["time"] time_bins = nc4.num2date(times[:], units=times.units) @@ -349,9 +337,7 @@ def average_data(self, period, method): # Copy variables from first file for v_name, varin in combined_data.variables.items(): - outVar = new_data.createVariable( - v_name, varin.dtype, varin.dimensions - ) + outVar = new_data.createVariable(v_name, varin.dtype, varin.dimensions) try: if v_name == "time": outVar.units = varin.units.replace( @@ -362,9 +348,7 @@ def average_data(self, period, method): except: pass try: - outVar.setncatts( - {k: varin.getncattr(k) for k in varin.ncattrs()} - ) + outVar.setncatts({k: varin.getncattr(k) for k in varin.ncattrs()}) except: pass @@ -373,9 +357,7 @@ def average_data(self, period, method): if v_name in self.vars: # TODO: Make this efficient for i in range(0, combined_data.variables[v_name][:].shape[1]): - for j in range( - 0, combined_data.variables[v_name][:].shape[2] - ): + for j in range(0, combined_data.variables[v_name][:].shape[2]): p = pd.Series( index=time_bins, data=combined_data.variables[v_name][:, i, j], @@ -440,8 +422,7 @@ def retrieve(self, start_period, end_period): except Exception as e: os.remove(dataOut) raise Exception( - "Problem creating temporary file to store raster data: " - + str(e) + "Problem creating temporary file to store raster data: " + str(e) ) # TODO: Work out if the response is an XML error diff --git a/util/shadowingfunctions.py b/util/shadowingfunctions.py index 552e81f..b10cdae 100644 --- a/util/shadowingfunctions.py +++ b/util/shadowingfunctions.py @@ -253,16 +253,12 @@ def shadowingfunction_20( is_pergola = fabovea & gabovea & lastfabovea & lastgabovea # The shadow exists if one of the layers is True, UNLESS it's a pergola. - vegsh2 = (fabovea | gabovea | lastfabovea | lastgabovea) & ( - ~is_pergola - ) + vegsh2 = (fabovea | gabovea | lastfabovea | lastgabovea) & (~is_pergola) vegsh2 = vegsh2.float() # Accumulation of vegetation shadows vegsh = torch.maximum(vegsh, vegsh2) - vegsh = torch.where( - vegsh * sh > 0, torch.tensor(0.0, device=device), vegsh - ) + vegsh = torch.where(vegsh * sh > 0, torch.tensor(0.0, device=device), vegsh) vbshvegsh.add_(vegsh) sh = 1.0 - sh @@ -361,9 +357,7 @@ def shadowingfunction_findwallID( # Trig variables sinazimuth = torch.sin(torch.tensor(azimuth_rad, device=device)) cosazimuth = torch.cos(torch.tensor(azimuth_rad, device=device)) - tanaltitudebyscale = ( - torch.tan(torch.tensor(altitude_rad, device=device)) / scale - ) + tanaltitudebyscale = torch.tan(torch.tensor(altitude_rad, device=device)) / scale amaxvalue = torch.max(dsm) max_steps = int(amaxvalue / tanaltitudebyscale) + 2 @@ -424,9 +418,7 @@ def shadowingfunction_findwallID( voxel_height = voxel_height.to(device) if not isinstance(voxelId_list, torch.Tensor): - voxelId_list = torch.tensor( - voxelId_list, dtype=torch.long, device=device - ) + voxelId_list = torch.tensor(voxelId_list, dtype=torch.long, device=device) else: voxelId_list = voxelId_list.to(device=device, dtype=torch.long) @@ -444,9 +436,7 @@ def shadowingfunction_findwallID( mask = (wall2d_id == temp_id) & (voxel_height == temp_height) temp_fill_id = voxelId_list[mask] - pixel_mask = (buildIDSeen == temp_id) & ( - voxelHeight_ceil == temp_height - ) + pixel_mask = (buildIDSeen == temp_id) & (voxelHeight_ceil == temp_height) if temp_fill_id.numel() > 0: voxelId[pixel_mask] = temp_fill_id[0].float() diff --git a/util/ssParms.py b/util/ssParms.py index 4de863c..ea4d3a0 100644 --- a/util/ssParms.py +++ b/util/ssParms.py @@ -19,7 +19,9 @@ def ss_calc(build, cdsm, walls, numPixels, feedback): - walllimit = 0.3 # 30 centimeters height variation identifies a vegetation edge pixel + walllimit = ( + 0.3 # 30 centimeters height variation identifies a vegetation edge pixel + ) total = 100.0 / (int(build.shape[0] * build.shape[1])) if cdsm.max() > 0: @@ -152,9 +154,7 @@ def writeGridLayout(ssVect, heightMethod, vertHeights, nlayer, skew): ssDict["building_frac"].append( ssVect[0, 1] ) # first is plan area index of buildings - ssDict["veg_frac"].append( - ssVect[0, 3] - ) # first is plan area index of trees + ssDict["veg_frac"].append(ssVect[0, 3]) # first is plan area index of trees else: ssDict["building_frac"].append( np.round(np.mean(ssVect[startH:endH, 1]), 3) diff --git a/util/umep_installer.py b/util/umep_installer.py index 1d2e4bc..0e604b1 100644 --- a/util/umep_installer.py +++ b/util/umep_installer.py @@ -45,9 +45,7 @@ def locate_py(): if candidate_path.exists(): return candidate_path - raise RuntimeError( - "UMEP cannot locate the Python interpreter used by QGIS!" - ) + raise RuntimeError("UMEP cannot locate the Python interpreter used by QGIS!") # check if supy is installed @@ -55,15 +53,11 @@ def check_supy_version(): try: path_pybin = locate_py() list_cmd = f"{str(path_pybin)} -m pip show supy".split() - list_info = subprocess.check_output(list_cmd, encoding="UTF8").split( - "\n" - ) + list_info = subprocess.check_output(list_cmd, encoding="UTF8").split("\n") str_ver = list_info[1].split(":")[1].strip() return str_ver except Exception: - raise RuntimeError( - "UMEP cannot identify a supy installation!" - ) from Exception + raise RuntimeError("UMEP cannot identify a supy installation!") from Exception # install supy @@ -111,8 +105,7 @@ def install_umep_python(ver=None): str_info = ( str_info - if "Successfully installed UMEP dependent Python packages" - in str_info + if "Successfully installed UMEP dependent Python packages" in str_info else f"UMEP dependent Python packages has already been installed!" ) return str_info @@ -130,16 +123,12 @@ def uninstall_umep_python(): try: path_pybin = locate_py() list_cmd = f"{str(path_pybin)} -m pip uninstall umep-reqs -y".split() - list_info = subprocess.check_output(list_cmd, encoding="UTF8").split( - "\n" - ) + list_info = subprocess.check_output(list_cmd, encoding="UTF8").split("\n") str_info = list_info[-2].strip() return str_info except Exception: - raise RuntimeError( - f"UMEP couldn't uninstall umep-reqs!" - ) from Exception + raise RuntimeError(f"UMEP couldn't uninstall umep-reqs!") from Exception # set up umep diff --git a/util/umep_solweig_export_component.py b/util/umep_solweig_export_component.py index 0c8d34e..1a3e172 100644 --- a/util/umep_solweig_export_component.py +++ b/util/umep_solweig_export_component.py @@ -55,9 +55,7 @@ def write_solweig_config(configDict, refdir): f.write("# Point of Interest file for ground\n") f.write("poi_file={}\n".format(configDict["poi_file"])) f.write("poi_field={}\n".format(configDict["poi_field"])) - f.write( - "# Input file for wall temperture scheme (Wallenberg et al. 2025)\n" - ) + f.write("# Input file for wall temperture scheme (Wallenberg et al. 2025)\n") f.write("input_wall={}\n".format(configDict["input_wall"])) f.write("# Input file for surface temperature data\n") f.write("input_surf={}\n".format(configDict.get("input_surf", ""))) @@ -90,9 +88,7 @@ def write_solweig_config(configDict, refdir): "# scale between horisontal and vertical resolution (1 = 1 meter pixel reolution, 0.5 = 2)\n" ) f.write("scale={}\n".format(configDict["scale"])) - f.write( - "# Set to 1 if an EPW file is used as meteorological forcing data\n" - ) + f.write("# Set to 1 if an EPW file is used as meteorological forcing data\n") f.write("useepwfile={}\n".format(configDict["useepwfile"])) f.write("# land cover scheme activated (1) (Lindberg et al. 2016 UC)\n") f.write("landcover={}\n".format(configDict["landcover"])) @@ -104,9 +100,7 @@ def write_solweig_config(configDict, refdir): "# use anisotrphic sky (Wallenberg et al. XXXX and Wallenberg et al. XXXX)\n" ) f.write("aniso={}\n".format(configDict["aniso"])) - f.write( - "# use wall surface temperature scheme (Wallenberg et al. 2025, GMD)\n" - ) + f.write("# use wall surface temperature scheme (Wallenberg et al. 2025, GMD)\n") f.write("wallscheme={}\n".format(configDict["wallscheme"])) f.write( "# If building materials is not included in lc, then this is used for all buildings (Wood, Brick or Concrete)\n" @@ -130,9 +124,7 @@ def write_solweig_config(configDict, refdir): f.write("#-------------------------------------------------------\n") f.write("# dates - used if an EPW-file is used\n") f.write("#-------------------------------------------------------\n") - f.write( - "# year,month,day,hour ## the start date/time for period of interest\n" - ) + f.write("# year,month,day,hour ## the start date/time for period of interest\n") f.write("date1={}\n".format(configDict["date1"])) f.write("# year,month,day,hour # end date/time\n") f.write("date2={}\n".format(configDict["date2"])) diff --git a/util/umep_suewsss_export_component.py b/util/umep_suewsss_export_component.py index 343e487..13f63d4 100644 --- a/util/umep_suewsss_export_component.py +++ b/util/umep_suewsss_export_component.py @@ -186,13 +186,9 @@ def write_GridLayout_file(ss_object, refdir, fname): f.write("&geom\n") f.write("height = {}\n".format(str(ss_object["height"])[1:-1])) - f.write( - "building_frac = {}\n".format(str(ss_object["building_frac"])[1:-1]) - ) + f.write("building_frac = {}\n".format(str(ss_object["building_frac"])[1:-1])) f.write("veg_frac = {}\n".format(str(ss_object["veg_frac"])[1:-1])) - f.write( - "building_scale = {}\n".format(str(ss_object["building_scale"])[1:-1]) - ) + f.write("building_scale = {}\n".format(str(ss_object["building_scale"])[1:-1])) f.write("veg_scale = {}\n".format(str(ss_object["veg_scale"])[1:-1])) f.write("/\n") f.write("\n") @@ -203,21 +199,11 @@ def write_GridLayout_file(ss_object, refdir, fname): f.write("alb_roof = {}\n".format(str(ss_object["alb_roof"])[1:-1])) f.write("emis_roof = {}\n".format(str(ss_object["emis_roof"])[1:-1])) f.write("state_roof = {}\n".format(str(ss_object["state_roof"])[1:-1])) + f.write("statelimit_roof = {}\n".format(str(ss_object["statelimit_roof"])[1:-1])) + f.write("wetthresh_roof = {}\n".format(str(ss_object["wetthresh_roof"])[1:-1])) + f.write("soilstore_roof = {}\n".format(str(ss_object["soilstore_roof"])[1:-1])) f.write( - "statelimit_roof = {}\n".format( - str(ss_object["statelimit_roof"])[1:-1] - ) - ) - f.write( - "wetthresh_roof = {}\n".format(str(ss_object["wetthresh_roof"])[1:-1]) - ) - f.write( - "soilstore_roof = {}\n".format(str(ss_object["soilstore_roof"])[1:-1]) - ) - f.write( - "soilstorecap_roof = {}\n".format( - str(ss_object["soilstorecap_roof"])[1:-1] - ) + "soilstorecap_roof = {}\n".format(str(ss_object["soilstorecap_roof"])[1:-1]) ) f.write( "roof_albedo_dir_mult_fact(1,:) = {}\n".format( @@ -252,21 +238,11 @@ def write_GridLayout_file(ss_object, refdir, fname): f.write("alb_wall = {}\n".format(str(ss_object["alb_wall"])[1:-1])) f.write("emis_wall = {}\n".format(str(ss_object["emis_wall"])[1:-1])) f.write("state_wall = {}\n".format(str(ss_object["state_wall"])[1:-1])) + f.write("statelimit_wall = {}\n".format(str(ss_object["statelimit_wall"])[1:-1])) + f.write("wetthresh_wall = {}\n".format(str(ss_object["wetthresh_wall"])[1:-1])) + f.write("soilstore_wall = {}\n".format(str(ss_object["soilstore_wall"])[1:-1])) f.write( - "statelimit_wall = {}\n".format( - str(ss_object["statelimit_wall"])[1:-1] - ) - ) - f.write( - "wetthresh_wall = {}\n".format(str(ss_object["wetthresh_wall"])[1:-1]) - ) - f.write( - "soilstore_wall = {}\n".format(str(ss_object["soilstore_wall"])[1:-1]) - ) - f.write( - "soilstorecap_wall = {}\n".format( - str(ss_object["soilstorecap_wall"])[1:-1] - ) + "soilstorecap_wall = {}\n".format(str(ss_object["soilstorecap_wall"])[1:-1]) ) f.write( "wall_specular_frac(1,:) = {}\n".format( @@ -298,71 +274,33 @@ def write_GridLayout_file(ss_object, refdir, fname): f.write("&surf\n") f.write("tin_surf = {}\n".format(str(ss_object["tin_surf"])[1:-1])) - f.write( - "dz_surf(1,:) = {}\n".format(str(ss_object["dz_surf_paved"])[1:-1]) - ) + f.write("dz_surf(1,:) = {}\n".format(str(ss_object["dz_surf_paved"])[1:-1])) f.write("k_surf(1,:) = {}\n".format(str(ss_object["k_surf_paved"])[1:-1])) - f.write( - "cp_surf((1,:) = {}\n".format(str(ss_object["cp_surf_paved"])[1:-1]) - ) + f.write("cp_surf((1,:) = {}\n".format(str(ss_object["cp_surf_paved"])[1:-1])) - f.write( - "dz_surf(2,:) = {}\n".format(str(ss_object["dz_surf_buildings"])[1:-1]) - ) - f.write( - "k_surf(2,:) = {}\n".format(str(ss_object["k_surf_buildings"])[1:-1]) - ) - f.write( - "cp_surf((2,:) = {}\n".format( - str(ss_object["cp_surf_buildings"])[1:-1] - ) - ) + f.write("dz_surf(2,:) = {}\n".format(str(ss_object["dz_surf_buildings"])[1:-1])) + f.write("k_surf(2,:) = {}\n".format(str(ss_object["k_surf_buildings"])[1:-1])) + f.write("cp_surf((2,:) = {}\n".format(str(ss_object["cp_surf_buildings"])[1:-1])) - f.write( - "dz_surf(3,:) = {}\n".format(str(ss_object["dz_surf_evergreen"])[1:-1]) - ) - f.write( - "k_surf(3,:) = {}\n".format(str(ss_object["k_surf_evergreen"])[1:-1]) - ) - f.write( - "cp_surf((3,:) = {}\n".format( - str(ss_object["cp_surf_evergreen"])[1:-1] - ) - ) + f.write("dz_surf(3,:) = {}\n".format(str(ss_object["dz_surf_evergreen"])[1:-1])) + f.write("k_surf(3,:) = {}\n".format(str(ss_object["k_surf_evergreen"])[1:-1])) + f.write("cp_surf((3,:) = {}\n".format(str(ss_object["cp_surf_evergreen"])[1:-1])) - f.write( - "dz_surf(4,:) = {}\n".format(str(ss_object["dz_surf_decid"])[1:-1]) - ) + f.write("dz_surf(4,:) = {}\n".format(str(ss_object["dz_surf_decid"])[1:-1])) f.write("k_surf(4,:) = {}\n".format(str(ss_object["k_surf_decid"])[1:-1])) - f.write( - "cp_surf((4,:) = {}\n".format(str(ss_object["cp_surf_decid"])[1:-1]) - ) + f.write("cp_surf((4,:) = {}\n".format(str(ss_object["cp_surf_decid"])[1:-1])) - f.write( - "dz_surf(5,:) = {}\n".format(str(ss_object["dz_surf_grass"])[1:-1]) - ) + f.write("dz_surf(5,:) = {}\n".format(str(ss_object["dz_surf_grass"])[1:-1])) f.write("k_surf(5,:) = {}\n".format(str(ss_object["k_surf_grass"])[1:-1])) - f.write( - "cp_surf((5,:) = {}\n".format(str(ss_object["cp_surf_grass"])[1:-1]) - ) + f.write("cp_surf((5,:) = {}\n".format(str(ss_object["cp_surf_grass"])[1:-1])) - f.write( - "dz_surf(6,:) = {}\n".format(str(ss_object["dz_surf_baresoil"])[1:-1]) - ) - f.write( - "k_surf(6,:) = {}\n".format(str(ss_object["k_surf_baresoil"])[1:-1]) - ) - f.write( - "cp_surf((6,:) = {}\n".format(str(ss_object["cp_surf_baresoil"])[1:-1]) - ) + f.write("dz_surf(6,:) = {}\n".format(str(ss_object["dz_surf_baresoil"])[1:-1])) + f.write("k_surf(6,:) = {}\n".format(str(ss_object["k_surf_baresoil"])[1:-1])) + f.write("cp_surf((6,:) = {}\n".format(str(ss_object["cp_surf_baresoil"])[1:-1])) - f.write( - "dz_surf(7,:) = {}\n".format(str(ss_object["dz_surf_water"])[1:-1]) - ) + f.write("dz_surf(7,:) = {}\n".format(str(ss_object["dz_surf_water"])[1:-1])) f.write("k_surf(7,:) = {}\n".format(str(ss_object["k_surf_water"])[1:-1])) - f.write( - "cp_surf((7,:) = {}\n".format(str(ss_object["cp_surf_water"])[1:-1]) - ) + f.write("cp_surf((7,:) = {}\n".format(str(ss_object["cp_surf_water"])[1:-1])) f.write("/\n") f.write("\n") diff --git a/util/umep_uwg_export_component.py b/util/umep_uwg_export_component.py index 3596459..bc7793c 100644 --- a/util/umep_uwg_export_component.py +++ b/util/umep_uwg_export_component.py @@ -41,9 +41,7 @@ def create_uwgdict(): uwgDict["grasscover"] = ( 0.1 # Fraction of the urban ground covered in grass/shrubs only (0-1) ) - uwgDict["treeCover"] = ( - 0.1 # Fraction of the urban ground covered in trees (0-1) - ) + uwgDict["treeCover"] = 0.1 # Fraction of the urban ground covered in trees (0-1) uwgDict["vegStart"] = ( 4 # The month in which vegetation starts to evapotranspire (leaves are out) ) @@ -57,9 +55,7 @@ def create_uwgdict(): uwgDict["latTree"] = ( 0.6 # Fraction of the heat absorbed by trees that is latent (goes to evaporating water) ) - uwgDict["rurVegCover"] = ( - 0.9 # Fraction of the rural ground covered by vegetation - ) + uwgDict["rurVegCover"] = 0.9 # Fraction of the rural ground covered by vegetation # Traffic schedule [1 to 24 hour],# Weekday# Saturday# Sunday uwgDict["SchTraffic"] = [ @@ -196,9 +192,7 @@ def create_uwgdict(): # ================================================= # If not provided, optional parameters are taken from corresponding DOE Reference building uwgDict["albRoof"] = None # roof albedo (0 - 1) - uwgDict["vegRoof"] = ( - None # Fraction of the roofs covered in grass/shrubs (0 - 1) - ) + uwgDict["vegRoof"] = None # Fraction of the roofs covered in grass/shrubs (0 - 1) uwgDict["glzR"] = None # Glazing Ratio (0 - 1) uwgDict["SHGC"] = None # Solar Heat Gain Coefficient (0 - 1) uwgDict["albWall"] = None # wall albedo (0 - 1) @@ -216,18 +210,10 @@ def create_uwgdict(): uwgDict["autosize"] = 0 # autosize HVAC (1 for yes; 0 for no) uwgDict["sensOcc"] = 100 # Sensible heat per occupant (W) - uwgDict["LatFOcc"] = ( - 0.3 # Latent heat fraction from occupant (normally 0.3) - ) - uwgDict["RadFOcc"] = ( - 0.2 # Radiant heat fraction from occupant (normally 0.2) - ) - uwgDict["RadFEquip"] = ( - 0.5 # Radiant heat fraction from equipment (normally 0.5) - ) - uwgDict["RadFLight"] = ( - 0.7 # Radiant heat fraction from light (normally 0.7) - ) + uwgDict["LatFOcc"] = 0.3 # Latent heat fraction from occupant (normally 0.3) + uwgDict["RadFOcc"] = 0.2 # Radiant heat fraction from occupant (normally 0.2) + uwgDict["RadFEquip"] = 0.5 # Radiant heat fraction from equipment (normally 0.5) + uwgDict["RadFLight"] = 0.7 # Radiant heat fraction from light (normally 0.7) # Urban climate parameters uwgDict["h_ubl1"] = 1000 # ubl height - day (m) @@ -235,12 +221,8 @@ def create_uwgdict(): uwgDict["h_ref"] = 150 # inversion height (m) uwgDict["h_temp"] = 2 # temperature height (m) uwgDict["h_wind"] = 10 # wind height (m) - uwgDict["c_circ"] = ( - 1.2 # circulation coefficient (default = 1.2 per Bruno (2012)) - ) - uwgDict["c_exch"] = ( - 1 # exchange coefficient (default = 1; ref Bruno (2014)) - ) + uwgDict["c_circ"] = 1.2 # circulation coefficient (default = 1.2 per Bruno (2012)) + uwgDict["c_exch"] = 1 # exchange coefficient (default = 1; ref Bruno (2014)) uwgDict["maxDay"] = 150 # max day threshold (W/m^2) uwgDict["maxNight"] = 20 # max night threshold (W/m^2) uwgDict["windMin"] = 1 # min wind speed (m/s) @@ -265,12 +247,8 @@ def get_uwg_file(uwg_object, refdir, fname): f.write( "charLength,{},\n".format(uwg_object["charLength"]) ) # dimension of a square that encompasses the whole neighborhood [aka. characteristic length] (m) - f.write( - "albRoad,{},\n".format(uwg_object["albRoad"]) - ) # road albedo (0 - 1) - f.write( - "dRoad,{},\n".format(uwg_object["dRoad"]) - ) # road pavement thickness (m) + f.write("albRoad,{},\n".format(uwg_object["albRoad"])) # road albedo (0 - 1) + f.write("dRoad,{},\n".format(uwg_object["dRoad"])) # road pavement thickness (m) f.write( "kRoad,{},\n".format(uwg_object["kRoad"]) ) # road pavement conductivity (W/m K) @@ -332,14 +310,10 @@ def get_uwg_file(uwg_object, refdir, fname): "# If not provided, optional parameters are taken from corresponding DOE Reference building\n" ) f.write( - "albRoof,{},\n".format( - uwg_object["albRoof"] if uwg_object["albRoof"] else "" - ) + "albRoof,{},\n".format(uwg_object["albRoof"] if uwg_object["albRoof"] else "") ) # roof albedo (0 - 1) f.write( - "vegRoof,{},\n".format( - uwg_object["vegRoof"] if uwg_object["vegRoof"] else "" - ) + "vegRoof,{},\n".format(uwg_object["vegRoof"] if uwg_object["vegRoof"] else "") ) # Fraction of the roofs covered in grass/shrubs (0 - 1) f.write( "glzR,{},\n".format(uwg_object["glzR"] if uwg_object["glzR"] else "") @@ -348,14 +322,10 @@ def get_uwg_file(uwg_object, refdir, fname): "SHGC,{},\n".format(uwg_object["SHGC"] if uwg_object["SHGC"] else "") ) # Solar Heat Gain Coefficient (0 - 1) f.write( - "albWall,{},\n".format( - uwg_object["albWall"] if uwg_object["albWall"] else "" - ) + "albWall,{},\n".format(uwg_object["albWall"] if uwg_object["albWall"] else "") ) # wall albedo (0 - 1) f.write( - "flr_h,{},\n".format( - uwg_object["flr_h"] if uwg_object["flr_h"] else "" - ) + "flr_h,{},\n".format(uwg_object["flr_h"] if uwg_object["flr_h"] else "") ) # average building floor height f.write("\n") f.write("# =================================================\n") @@ -365,15 +335,9 @@ def get_uwg_file(uwg_object, refdir, fname): f.write("# Simulation parameters,\n") f.write("Month,{},\n".format(uwg_object["Month"])) # starting month (1-12) f.write("Day,{},\n".format(uwg_object["Day"])) # starting day (1-31) - f.write( - "nDay,{},\n".format(uwg_object["nDay"]) - ) # number of days to run simultion - f.write( - "dtSim,{},\n".format(uwg_object["dtSim"]) - ) # simulation time step (s) - f.write( - "dtWeather,{},\n".format(uwg_object["dtWeather"]) - ), # weather time step (s) + f.write("nDay,{},\n".format(uwg_object["nDay"])) # number of days to run simultion + f.write("dtSim,{},\n".format(uwg_object["dtSim"])) # simulation time step (s) + f.write("dtWeather,{},\n".format(uwg_object["dtWeather"])), # weather time step (s) f.write("\n") f.write("# HVAC system and internal loads\n") f.write( @@ -396,16 +360,10 @@ def get_uwg_file(uwg_object, refdir, fname): ) # Radiant heat fraction from light (normally 0.7) f.write("\n") f.write("#Urban climate parameters\n") - f.write( - "h_ubl1,{},\n".format(uwg_object["h_ubl1"]) - ) # ubl height - day (m) - f.write( - "h_ubl2,{},\n".format(uwg_object["h_ubl2"]) - ) # ubl height - night (m) + f.write("h_ubl1,{},\n".format(uwg_object["h_ubl1"])) # ubl height - day (m) + f.write("h_ubl2,{},\n".format(uwg_object["h_ubl2"])) # ubl height - night (m) f.write("h_ref,{},\n".format(uwg_object["h_ref"])) # inversion height (m) - f.write( - "h_temp,{},\n".format(uwg_object["h_temp"]) - ) # temperature height (m) + f.write("h_temp,{},\n".format(uwg_object["h_temp"])) # temperature height (m) f.write("h_wind,{},\n".format(uwg_object["h_wind"])) # wind height (m) f.write( "c_circ,{},\n".format(uwg_object["c_circ"]) @@ -413,15 +371,11 @@ def get_uwg_file(uwg_object, refdir, fname): f.write( "c_exch,{},\n".format(uwg_object["c_exch"]) ) # exchange coefficient (default = 1; ref Bruno (2014)) - f.write( - "maxDay,{},\n".format(uwg_object["maxDay"]) - ) # max day threshold (W/m^2) + f.write("maxDay,{},\n".format(uwg_object["maxDay"])) # max day threshold (W/m^2) f.write( "maxNight,{},\n".format(uwg_object["maxNight"]) ) # max night threshold (W/m^2) - f.write( - "windMin,{},\n".format(uwg_object["windMin"]) - ) # min wind speed (m/s) + f.write("windMin,{},\n".format(uwg_object["windMin"])) # min wind speed (m/s) f.write( "h_obs,{},\n".format(uwg_object["h_obs"]) ) # rural average obstacle height (m) From c50294c879c9a0423a1dce58667969710cb86150 Mon Sep 17 00:00:00 2001 From: "Mael.PHILIPPE" Date: Wed, 27 May 2026 10:14:10 +0200 Subject: [PATCH 09/20] merged some changes from eliott's PR --- functions/SOLWEIGpython/Lside_veg_v2015a.py | 231 -- functions/SOLWEIGpython/Solweig_run.py | 4 +- processor/configsolweig.ini | 9 +- processor/parametersforsolweig.json | 24 +- processor/solweig_algorithm.py | 4 +- processor/solweig_algorithm_old.py | 2539 ------------------- 6 files changed, 24 insertions(+), 2787 deletions(-) delete mode 100644 functions/SOLWEIGpython/Lside_veg_v2015a.py delete mode 100644 processor/solweig_algorithm_old.py diff --git a/functions/SOLWEIGpython/Lside_veg_v2015a.py b/functions/SOLWEIGpython/Lside_veg_v2015a.py deleted file mode 100644 index 7723f8d..0000000 --- a/functions/SOLWEIGpython/Lside_veg_v2015a.py +++ /dev/null @@ -1,231 +0,0 @@ -from __future__ import absolute_import -import numpy as np -from .Lvikt_veg import Lvikt_veg -import torch - - -def Lside_veg_v2015a( - svfS, - svfW, - svfN, - svfE, - svfEveg, - svfSveg, - svfWveg, - svfNveg, - svfEaveg, - svfSaveg, - svfWaveg, - svfNaveg, - azimuth, - altitude, - Ta, - Tw, - SBC, - ewall, - Ldown, - esky, - t, - F_sh, - CI, - LupE, - LupS, - LupW, - LupN, -): - # Load device - device = ( - Ldown.device - if isinstance(Ldown, torch.Tensor) - else ( - Ta.device - if isinstance(Ta, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "cpu") - ) - ) - # This m-file is the current one that estimates L from the four cardinal points 20100414 - - # Building height angle from svf - svfalfaE = torch.arcsin(torch.exp((torch.log(1 - svfE)) / 2)) - svfalfaS = torch.arcsin(torch.exp((torch.log(1 - svfS)) / 2)) - svfalfaW = torch.arcsin(torch.exp((torch.log(1 - svfW)) / 2)) - svfalfaN = torch.arcsin(torch.exp((torch.log(1 - svfN)) / 2)) - - vikttot = 4.4897 - aziW = azimuth + t - aziN = azimuth - 90 + t - aziE = azimuth - 180 + t - aziS = azimuth - 270 + t - - F_sh = 2 * F_sh - 1 # (cylindric_wedge scaled 0-1) - - c = 1 - CI - Lsky_allsky = esky * SBC * ((Ta + 273.15) ** 4) * (1 - c) + c * SBC * ( - (Ta + 273.15) ** 4 - ) - - ## Least - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( - svfE, svfEveg, svfEaveg, vikttot - ) - - if altitude > 0: # daytime - alfaB = torch.arctan(svfalfaE) - betaB = torch.arctan(torch.tan((svfalfaE) * F_sh)) - betasun = ((alfaB - betaB) / 2) + betaB - # betasun = torch.arctan(0.5*torch.tan(svfalfaE)*(1+F_sh)) #TODO This should be considered in future versions - if (azimuth > (180 - t)) and (azimuth <= (360 - t)): - Lwallsun = ( - SBC - * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziE * (torch.pi / 180))) - ** 4 - ) - * viktwall - * (1 - F_sh) - * torch.cos(betasun) - * 0.5 - ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) - else: - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - else: # nighttime - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - - Lsky = ((svfE + svfEveg - 1) * Lsky_allsky) * viktsky * 0.5 - Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 - Lground = LupE * 0.5 - Lrefl = (Ldown + LupE) * (viktrefl) * (1 - ewall) * 0.5 - Least = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl - - # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky - - ## Lsouth - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( - svfS, svfSveg, svfSaveg, vikttot - ) - - if altitude > 0: # daytime - alfaB = torch.arctan(svfalfaS) - betaB = torch.arctan(torch.tan((svfalfaS) * F_sh)) - betasun = ((alfaB - betaB) / 2) + betaB - # betasun = torch.arctan(0.5*torch.tan(svfalfaS)*(1+F_sh)) - if (azimuth <= (90 - t)) or (azimuth > (270 - t)): - Lwallsun = ( - SBC - * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziS * (torch.pi / 180))) - ** 4 - ) - * viktwall - * (1 - F_sh) - * torch.cos(betasun) - * 0.5 - ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) - else: - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - else: # nighttime - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - - Lsky = ((svfS + svfSveg - 1) * Lsky_allsky) * viktsky * 0.5 - Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 - Lground = LupS * 0.5 - Lrefl = (Ldown + LupS) * (viktrefl) * (1 - ewall) * 0.5 - Lsouth = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl - - # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky - - ## Lwest - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( - svfW, svfWveg, svfWaveg, vikttot - ) - - if altitude > 0: # daytime - alfaB = torch.arctan(svfalfaW) - betaB = torch.arctan(torch.tan((svfalfaW) * F_sh)) - betasun = ((alfaB - betaB) / 2) + betaB - # betasun = torch.arctan(0.5*torch.tan(svfalfaW)*(1+F_sh)) - if (azimuth > (360 - t)) or (azimuth <= (180 - t)): - Lwallsun = ( - SBC - * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziW * (torch.pi / 180))) - ** 4 - ) - * viktwall - * (1 - F_sh) - * torch.cos(betasun) - * 0.5 - ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) - else: - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - else: # nighttime - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - - Lsky = ((svfW + svfWveg - 1) * Lsky_allsky) * viktsky * 0.5 - Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 - Lground = LupW * 0.5 - Lrefl = (Ldown + LupW) * (viktrefl) * (1 - ewall) * 0.5 - Lwest = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl - - # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky - - ## Lnorth - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( - svfN, svfNveg, svfNaveg, vikttot - ) - - if altitude > 0: # daytime - alfaB = torch.arctan(svfalfaN) - betaB = torch.arctan(torch.tan((svfalfaN) * F_sh)) - betasun = ((alfaB - betaB) / 2) + betaB - # betasun = torch.arctan(0.5*torch.tan(svfalfaN)*(1+F_sh)) - if (azimuth > (90 - t)) and (azimuth <= (270 - t)): - Lwallsun = ( - SBC - * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziN * (torch.pi / 180))) - ** 4 - ) - * viktwall - * (1 - F_sh) - * torch.cos(betasun) - * 0.5 - ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) - else: - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - else: # nighttime - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - - Lsky = ((svfN + svfNveg - 1) * Lsky_allsky) * viktsky * 0.5 - Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 - Lground = LupN * 0.5 - Lrefl = (Ldown + LupN) * (viktrefl) * (1 - ewall) * 0.5 - Lnorth = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl - - # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky - - return Least, Lsouth, Lwest, Lnorth diff --git a/functions/SOLWEIGpython/Solweig_run.py b/functions/SOLWEIGpython/Solweig_run.py index 7e9ebb5..225ab28 100644 --- a/functions/SOLWEIGpython/Solweig_run.py +++ b/functions/SOLWEIGpython/Solweig_run.py @@ -86,8 +86,10 @@ def solweig_run(configPath, feedback): # Load parameters settings for SOLWEIG with open(configDict["para_json_path"], "r") as jsn: param = json.load(jsn) + # reading variables from config and parameters that is not yet presented + standAlone = int(configDict["standalone"]) cyl = int(configDict["cyl"]) albedo_b = param["Albedo"]["Effective"]["Value"]["Walls"] ewall = param["Emissivity"]["Value"]["Walls"] @@ -203,7 +205,7 @@ def solweig_run(configPath, feedback): configDict["filepath_lc"], bbox=None ) else: - lcgrid = 0 + lcgrid = torch.ones_like(dsm, device=device) # DEM for buildings #TODO: fix nodata in standalone demforbuild = int(configDict["demforbuild"]) diff --git a/processor/configsolweig.ini b/processor/configsolweig.ini index fbe7335..ce029c7 100644 --- a/processor/configsolweig.ini +++ b/processor/configsolweig.ini @@ -33,9 +33,10 @@ input_aniso= poi_file= poi_field= # Input file for wall temperture scheme (Wallenberg et al. 2025) -input_wall= +input_wall = C:\Users\xlinfr\Desktop\SOLWEIGdata\blabla.npz # Input file for surface temperature data -input_surf= +input_surf=C:\Users\xlinfr\Desktop\SOLWEIGdata\surftemp.txt +input_surf=C:\Users\xlinfr\Desktop\SOLWEIGdata\surftemp.txt #Point of Interest file for walls woi_file= woi_field= @@ -68,6 +69,10 @@ landcover=1 demforbuild=0 # use anisotrphic sky (Wallenberg et al. XXXX and Wallenberg et al. XXXX) aniso=0 +# use OHM for ground surface temperature modeling +groundmodel=1 +# compute the outgoing longwave rad using solid angles +outgoingLW=1 # use wall surface temperature scheme (Wallenberg et al. 2025, GMD) wallscheme=0 # If building materials is not included in lc, then this is used for all buildings (Wood, Brick or Concrete) diff --git a/processor/parametersforsolweig.json b/processor/parametersforsolweig.json index e9eb049..e930d2c 100644 --- a/processor/parametersforsolweig.json +++ b/processor/parametersforsolweig.json @@ -207,25 +207,25 @@ }, "Tg_ini coefficients": { "Values": { - "Cobble_stone_2014a": [-1.7, 1.5, 1.61], - "Dark_asphalt": [-2.35, 0.9, 1.6], - "Roofs(buildings)": [1, -1.3, 1.55], - "Grass_unmanaged": [-1.4, 1.1, 1.58], - "Bare_soil": [-1.8, 0.75, 1.58], + "Cobble_stone_2014a": [-1.0, -0.04, 1.5], + "Dark_asphalt": [-1.4, -0.04, 0.72], + "Roofs(buildings)": [-2.9, 0, 0.43], + "Grass_unmanaged": [-4.0, -0.02, 0.38], + "Bare_soil": [-1.6, -0.02, 0.51], "Water": [0, 0, 0] }, - "Comment": "Offset in K, Ratio between amplitude and offset and phase used to model the seasonal pattern of the initial ground surface temperature when compared to the air temperature" + "Comment": "Offset in K, Slope of offset vs latitude, Ratio between amplitude and offset and phase used to model the seasonal pattern of the initial ground surface temperature when compared to the air temperature" }, "Tm_ini coefficients": { "Values": { - "Cobble_stone_2014a": [-6.0, 1.71, 1.43], - "Dark_asphalt": [-4.9, 1.71, -0.22], - "Roofs(buildings)": [0, 0, 0.3], - "Grass_unmanaged": [-2.7, 1.7, -0.68], - "Bare_soil": [-2.9, 1.7, -0.73], + "Cobble_stone_2014a": [-4.5, -0.15, 9], + "Dark_asphalt": [-5.7, -0.18, 10], + "Roofs(buildings)": [0, 0, 6], + "Grass_unmanaged": [-6.2, -0.2, 10], + "Bare_soil": [-3.4, -0.11, 5], "Water": [0, 0, 0] }, - "Comment": "Amplitude in K, phase and offset in K used to model the seasonal pattern of the initial ground surface temperature when compared to the air temperature" + "Comment": "Amplitude in K, slope of offset vs latitude in K/deg and offset in K used to model the seasonal pattern of the initial ground surface temperature when compared to the air temperature" }, "Tmrt_params": { "Value": { diff --git a/processor/solweig_algorithm.py b/processor/solweig_algorithm.py index c196955..4165a19 100644 --- a/processor/solweig_algorithm.py +++ b/processor/solweig_algorithm.py @@ -1152,13 +1152,13 @@ def processAlgorithm(self, parameters, context, feedback): np.max(unique_landcover) > 7 or np.min(unique_landcover) < 1 ): - feedback.pushWarning( + raise QgsProcessingException( "The land cover grid includes integer values higher (or lower) than standard UMEP-formatted. " "Land cover grid (should be integer between 1 and 7). If other LC-classes should be included they also need to be included in landcoverclasses_2016a.txt" ) else: if np.max(lcgrid) > 7 or np.min(lcgrid) < 1: - feedback.pushWarning( + raise QgsProcessingException( "The land cover grid includes integer values higher (or lower) than standard UMEP-formatted. " "Land cover grid (should be integer between 1 and 7). If other LC-classes should be included they also need to be included in landcoverclasses_2016a.txt" ) diff --git a/processor/solweig_algorithm_old.py b/processor/solweig_algorithm_old.py deleted file mode 100644 index cb5047d..0000000 --- a/processor/solweig_algorithm_old.py +++ /dev/null @@ -1,2539 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -/*************************************************************************** - ProcessingUMEP - A QGIS plugin - UMEP for processing toolbox - Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ - ------------------- - begin : 2020-04-02 - copyright : (C) 2020 by Fredrik Lindberg - email : fredrikl@gvc.gu.se - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - -__author__ = "Fredrik Lindberg" -__date__ = "2020-04-02" -__copyright__ = "(C) 2020 by Fredrik Lindberg" - -# This will get replaced with a git SHA1 when you do a git archive - -__revision__ = "$Format:%H$" - -from qgis.PyQt.QtCore import QCoreApplication, QVariant, QDate, QTime, Qt -from qgis.PyQt.QtWidgets import QDateEdit, QTimeEdit -from qgis.core import ( - QgsProcessing, - QgsProcessingAlgorithm, - QgsProcessingParameterBoolean, - QgsProcessingParameterNumber, - QgsProcessingParameterFolderDestination, - QgsProcessingParameterFile, - QgsProcessingException, - QgsProcessingParameterDefinition, - QgsProcessingParameterEnum, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterField, - QgsProcessingParameterRasterLayer, -) -from processing.gui.wrappers import WidgetWrapper -import numpy as np -import pandas as pd -from osgeo import gdal, osr -from osgeo.gdalconst import * -import os -from qgis.PyQt.QtGui import QIcon -import inspect -from pathlib import Path, PurePath -from ..util.misc import get_ders, saveraster -import zipfile -from osgeo.gdalconst import * -from ..util.SEBESOLWEIGCommonFiles.Solweig_v2015_metdata_noload import ( - Solweig_2015a_metdata_noload, -) -from ..util.SEBESOLWEIGCommonFiles import ( - Solweig_v2015_metdata_noload as metload, -) -from ..util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import ( - clearnessindex_2013b, -) -from ..functions.SOLWEIGpython.Tgmaps_v1 import Tgmaps_v1 -from ..functions.SOLWEIGpython import Solweig_2022a_calc_forprocessing as so -from ..functions.SOLWEIGpython import WriteMetadataSOLWEIG -from ..functions.SOLWEIGpython import PET_calculations as p -from ..functions.SOLWEIGpython import UTCI_calculations as utci -from ..functions.SOLWEIGpython.CirclePlotBar import PolarBarPlot -from ..functions.SOLWEIGpython.wall_surface_temperature import load_walls -from ..functions import wallalgorithms as wa -from ..functions.SOLWEIGpython.wallOfInterest import wallOfInterest -from ..functions.SOLWEIGpython.wallsAsNetCDF import walls_as_netcdf - -import matplotlib.pyplot as plt -import json - -# For "Save necessary rasters for TreePlanter tool" -from shutil import copyfile - -import time - -# -import datetime - - -class ProcessingSOLWEIGAlgorithm(QgsProcessingAlgorithm): - """ - This algorithm is a processing version of SOLWEIG - """ - - # Constants used to refer to parameters and outputs. They will be - # used when calling the algorithm from another algorithm, or when - # calling from the QGIS console. - - # Spatial data - INPUT_DSM = "INPUT_DSM" - INPUT_SVF = "INPUT_SVF" - INPUT_CDSM = "INPUT_CDSM" - INPUT_TDSM = "INPUT_TDSM" - INPUT_HEIGHT = "INPUT_HEIGHT" - INPUT_ASPECT = "INPUT_ASPECT" - TRANS_VEG = "TRANS_VEG" - LEAF_START = "LEAF_START" - LEAF_END = "LEAF_END" - CONIFER_TREES = "CONIFER_TREES" - INPUT_THEIGHT = "INPUT_THEIGHT" - INPUT_LC = "INPUT_LC" - USE_LC_BUILD = "USE_LC_BUILD" - INPUT_DEM = "INPUT_DEM" - SAVE_BUILD = "SAVE_BUILD" - INPUT_ANISO = "INPUT_ANISO" - INPUT_WALLSCHEME = "INPUT_WALLSCHEME" - WALLTEMP_NETCDF = "WALLTEMP_NETCDF" - - # Enivornmental parameters - ALBEDO_WALLS = "ALBEDO_WALLS" - ALBEDO_GROUND = "ALBEDO_GROUND" - EMIS_WALLS = "EMIS_WALLS" - EMIS_GROUND = "EMIS_GROUND" - WALL_TYPE = "WALL_TYPE" - - # Tmrt parameters - ABS_S = "ABS_S" - ABS_L = "ABS_L" - POSTURE = "POSTURE" - - # Meteorology - INPUT_MET = "INPUTMET" - ONLYGLOBAL = "ONLYGLOBAL" - UTC = "UTC" - - # PET parameters - AGE = "AGE" - ACTIVITY = "ACTIVITY" - CLO = "CLO" - WEIGHT = "WEIGHT" - HEIGHT = "HEIGHT" - SEX = "SEX" - SENSOR_HEIGHT = "SENSOR_HEIGHT" - - # Optional settings - WOI_FILE = "WOI_FILE" - WOI_FIELD = "WOI_FIELD" - # POI = 'POI' - POI_FILE = "POI_FILE" - POI_FIELD = "POI_FIELD" - CYL = "CYL" - - # Output - OUTPUT_DIR = "OUTPUT_DIR" - OUTPUT_TMRT = "OUTPUT_TMRT" - OUTPUT_LUP = "OUTPUT_LUP" - OUTPUT_KUP = "OUTPUT_KUP" - OUTPUT_KDOWN = "OUTPUT_KDOWN" - OUTPUT_LDOWN = "OUTPUT_LDOWN" - OUTPUT_SH = "OUTPUT_SH" - OUTPUT_TREEPLANTER = "OUTPUT_TREEPLANTER" - - def initAlgorithm(self, config): - # spatial - self.addParameter( - QgsProcessingParameterRasterLayer( - self.INPUT_DSM, - self.tr("Building and ground Digital Surface Model (DSM)"), - None, - optional=False, - ) - ) - self.addParameter( - QgsProcessingParameterFile( - self.INPUT_SVF, - self.tr("Sky View Factor grids (.zip)"), - extension="zip", - ) - ) - self.addParameter( - QgsProcessingParameterRasterLayer( - self.INPUT_HEIGHT, - self.tr("Wall height raster"), - "", - optional=False, - ) - ) - self.addParameter( - QgsProcessingParameterRasterLayer( - self.INPUT_ASPECT, - self.tr("Wall aspect raster"), - "", - optional=False, - ) - ) - self.addParameter( - QgsProcessingParameterRasterLayer( - self.INPUT_CDSM, - self.tr("Vegetation Canopy DSM"), - "", - optional=True, - ) - ) - self.addParameter( - QgsProcessingParameterNumber( - self.TRANS_VEG, - self.tr("Transmissivity of light through vegetation (%):"), - QgsProcessingParameterNumber.Type.Integer, - QVariant(3), - True, - minValue=0, - maxValue=100, - ) - ) - - self.addParameter( - QgsProcessingParameterNumber( - self.LEAF_START, - self.tr( - "First day of year with leaves on trees (if deciduous)" - ), - QgsProcessingParameterNumber.Type.Integer, - QVariant(97), - False, - minValue=0, - maxValue=366, - ) - ) - - self.addParameter( - QgsProcessingParameterNumber( - self.LEAF_END, - self.tr( - "Last day of year with leaves on trees (if deciduous)" - ), - QgsProcessingParameterNumber.Type.Integer, - QVariant(300), - False, - minValue=0, - maxValue=366, - ) - ) - - self.addParameter( - QgsProcessingParameterBoolean( - self.CONIFER_TREES, - self.tr("Coniferous trees (deciduous default)"), - defaultValue=False, - ) - ) - - self.addParameter( - QgsProcessingParameterRasterLayer( - self.INPUT_TDSM, - self.tr("Vegetation Trunk-zone DSM"), - "", - optional=True, - ) - ) - self.addParameter( - QgsProcessingParameterNumber( - self.INPUT_THEIGHT, - self.tr( - "Trunk zone height (percent of Canopy Height). Used if no Vegetation Trunk-zone DSM is loaded" - ), - QgsProcessingParameterNumber.Type.Double, - QVariant(25.0), - optional=True, - minValue=0.1, - maxValue=99.9, - ) - ) - self.addParameter( - QgsProcessingParameterRasterLayer( - self.INPUT_LC, - self.tr("UMEP land cover grid"), - "", - optional=True, - ) - ) - self.addParameter( - QgsProcessingParameterBoolean( - self.USE_LC_BUILD, - self.tr("Use land cover grid to derive building grid"), - defaultValue=False, - optional=True, - ) - ) - self.addParameter( - QgsProcessingParameterRasterLayer( - self.INPUT_DEM, - self.tr("Digital Elevation Model (DEM)"), - "", - optional=True, - ) - ) - self.addParameter( - QgsProcessingParameterBoolean( - self.SAVE_BUILD, - self.tr("Save generated building grid"), - defaultValue=False, - optional=True, - ) - ) - self.addParameter( - QgsProcessingParameterFile( - self.INPUT_ANISO, - self.tr( - "Shadow maps used for anisotropic model for sky diffuse and longwave radiation (.npz)" - ), - extension="npz", - optional=True, - ) - ) - self.addParameter( - QgsProcessingParameterFile( - self.INPUT_WALLSCHEME, - self.tr( - "Voxel data for wall temperature parameterization (.npz)" - ), - extension="npz", - optional=True, - ) - ) - self.addParameter( - QgsProcessingParameterBoolean( - self.WALLTEMP_NETCDF, - self.tr("Save wall temperatures as NetCDF"), - defaultValue=False, - optional=True, - ) - ) - - # Environmental parameters - # self.addParameter(QgsProcessingParameterNumber(self.EFFUS_WALL, - # self.tr('Wall type (only with wall scheme)'), QgsProcessingParameterNumber.Integer, - # QVariant(1065), False, minValue=0, maxValue=20000)) - self.wallType = ( - (self.tr("Brick"), "0"), - (self.tr("Concrete"), "1"), - (self.tr("Wood"), "2"), - ) - self.addParameter( - QgsProcessingParameterEnum( - self.WALL_TYPE, - self.tr("Wall type (only with wall scheme)"), - options=[i[0] for i in self.wallType], - defaultValue=0, - ) - ) - self.addParameter( - QgsProcessingParameterNumber( - self.ALBEDO_WALLS, - self.tr("Albedo (walls)"), - QgsProcessingParameterNumber.Type.Double, - QVariant(0.20), - False, - minValue=0, - maxValue=1, - ) - ) - self.addParameter( - QgsProcessingParameterNumber( - self.ALBEDO_GROUND, - self.tr("Albedo (ground)"), - QgsProcessingParameterNumber.Type.Double, - QVariant(0.15), - False, - minValue=0, - maxValue=1, - ) - ) - self.addParameter( - QgsProcessingParameterNumber( - self.EMIS_WALLS, - self.tr("Emissivity (walls)"), - QgsProcessingParameterNumber.Type.Double, - QVariant(0.90), - False, - minValue=0, - maxValue=1, - ) - ) - self.addParameter( - QgsProcessingParameterNumber( - self.EMIS_GROUND, - self.tr("Emissivity (ground)"), - QgsProcessingParameterNumber.Type.Double, - QVariant(0.95), - False, - minValue=0, - maxValue=1, - ) - ) - - # Tmrt parameters - self.addParameter( - QgsProcessingParameterNumber( - self.ABS_S, - self.tr("Absorption of shortwave radiation of human body"), - QgsProcessingParameterNumber.Type.Double, - QVariant(0.70), - False, - minValue=0, - maxValue=1, - ) - ) - self.addParameter( - QgsProcessingParameterNumber( - self.ABS_L, - self.tr("Absorption of longwave radiation of human body"), - QgsProcessingParameterNumber.Type.Double, - QVariant(0.95), - False, - minValue=0, - maxValue=1, - ) - ) - self.addParameter( - QgsProcessingParameterEnum( - self.POSTURE, - self.tr("Posture of human body"), - ["Standing", "Sitting"], - defaultValue=0, - ) - ) - self.addParameter( - QgsProcessingParameterBoolean( - self.CYL, - self.tr("Consider human as cylinder instead of box"), - defaultValue=True, - ) - ) - - # Meteorology - self.addParameter( - QgsProcessingParameterFile( - self.INPUT_MET, - self.tr("Input meteorological file (.txt)"), - extension="txt", - ) - ) - self.addParameter( - QgsProcessingParameterBoolean( - self.ONLYGLOBAL, - self.tr( - "Estimate diffuse and direct shortwave radiation from global radiation" - ), - defaultValue=False, - ) - ) - self.addParameter( - QgsProcessingParameterNumber( - self.UTC, - self.tr("Coordinated Universal Time (UTC) "), - QgsProcessingParameterNumber.Type.Integer, - QVariant(0), - False, - minValue=-12, - maxValue=12, - ) - ) - - # ADVANCED PARAMETERS - woifile = QgsProcessingParameterFeatureSource( - self.WOI_FILE, - self.tr( - "Vector point file including Wall of Interest(s) for output with wall surface temperatures" - ), - [QgsProcessing.SourceType.TypeVectorPoint], - optional=True, - ) - woifile.setFlags( - woifile.flags() - | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) - self.addParameter(woifile) - woi_field = QgsProcessingParameterField( - self.WOI_FIELD, - self.tr("Wall ID field"), - "", - self.WOI_FILE, - QgsProcessingParameterField.DataType.Numeric, - optional=True, - ) - woi_field.setFlags( - woi_field.flags() - | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) - self.addParameter(woi_field) - - # POIs for thermal comfort estimations - # poi = QgsProcessingParameterBoolean(self.POI, - # self.tr("Include Point of Interest(s) for thermal comfort calculations (PET and UTCI)"), defaultValue=False) - # poi.setFlags(poi.flags() | QgsProcessingParameterDefinition.FlagAdvanced) - # self.addParameter(poi) - poifile = QgsProcessingParameterFeatureSource( - self.POI_FILE, - self.tr( - "Vector point file including Point of Interest(s) for thermal comfort calculations (PET and UTCI)" - ), - [QgsProcessing.SourceType.TypeVectorPoint], - optional=True, - ) - poifile.setFlags( - poifile.flags() - | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) - self.addParameter(poifile) - poi_field = QgsProcessingParameterField( - self.POI_FIELD, - self.tr("ID field"), - "", - self.POI_FILE, - QgsProcessingParameterField.DataType.Numeric, - optional=True, - ) - poi_field.setFlags( - poi_field.flags() - | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) - self.addParameter(poi_field) - - # PET parameters - age = QgsProcessingParameterNumber( - self.AGE, - self.tr("Age (yy)"), - QgsProcessingParameterNumber.Type.Integer, - QVariant(35), - optional=True, - minValue=0, - maxValue=120, - ) - age.setFlags( - age.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) - self.addParameter(age) - act = QgsProcessingParameterNumber( - self.ACTIVITY, - self.tr("Activity (W)"), - QgsProcessingParameterNumber.Type.Double, - QVariant(80), - optional=True, - minValue=0, - maxValue=1000, - ) - act.setFlags( - act.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) - self.addParameter(act) - clo = QgsProcessingParameterNumber( - self.CLO, - self.tr("Clothing (clo)"), - QgsProcessingParameterNumber.Type.Double, - QVariant(0.9), - optional=True, - minValue=0, - maxValue=10, - ) - clo.setFlags( - clo.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) - self.addParameter(clo) - wei = QgsProcessingParameterNumber( - self.WEIGHT, - self.tr("Weight (kg)"), - QgsProcessingParameterNumber.Type.Integer, - QVariant(75), - optional=True, - minValue=0, - maxValue=500, - ) - wei.setFlags( - wei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) - self.addParameter(wei) - hei = QgsProcessingParameterNumber( - self.HEIGHT, - self.tr("Height (cm)"), - QgsProcessingParameterNumber.Type.Integer, - QVariant(180), - optional=True, - minValue=0, - maxValue=250, - ) - hei.setFlags( - hei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) - self.addParameter(hei) - sex = QgsProcessingParameterEnum( - self.SEX, - self.tr("Sex"), - ["Male", "Female"], - optional=True, - defaultValue=0, - ) - sex.setFlags( - sex.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) - self.addParameter(sex) - shei = QgsProcessingParameterNumber( - self.SENSOR_HEIGHT, - self.tr("Height of wind sensor (m agl)"), - QgsProcessingParameterNumber.Type.Double, - QVariant(10), - optional=True, - minValue=0, - maxValue=250, - ) - shei.setFlags( - shei.flags() | QgsProcessingParameterDefinition.Flag.FlagAdvanced - ) - self.addParameter(shei) - - # OUTPUT - self.addParameter( - QgsProcessingParameterBoolean( - self.OUTPUT_TMRT, - self.tr("Save Mean Radiant Temperature raster(s)"), - defaultValue=True, - ) - ) - self.addParameter( - QgsProcessingParameterBoolean( - self.OUTPUT_KDOWN, - self.tr("Save Incoming shortwave radiation raster(s)"), - defaultValue=False, - ) - ) - self.addParameter( - QgsProcessingParameterBoolean( - self.OUTPUT_KUP, - self.tr("Save Outgoing shortwave radiation raster(s)"), - defaultValue=False, - ) - ) - self.addParameter( - QgsProcessingParameterBoolean( - self.OUTPUT_LDOWN, - self.tr("Save Incoming longwave radiation raster(s)"), - defaultValue=False, - ) - ) - self.addParameter( - QgsProcessingParameterBoolean( - self.OUTPUT_LUP, - self.tr("Save Outgoing longwave radiation raster(s)"), - defaultValue=False, - ) - ) - self.addParameter( - QgsProcessingParameterBoolean( - self.OUTPUT_SH, - self.tr("Save shadow raster(s)"), - defaultValue=False, - ) - ) - self.addParameter( - QgsProcessingParameterBoolean( - self.OUTPUT_TREEPLANTER, - self.tr( - "Save necessary raster(s) for the TreePlanter and Spatial TC tools" - ), - defaultValue=False, - ) - ) - self.addParameter( - QgsProcessingParameterFolderDestination( - self.OUTPUT_DIR, "Output folder" - ) - ) - - self.plugin_dir = os.path.dirname(__file__) - self.temp_dir = os.path.dirname(self.plugin_dir) + "/temp" - - def processAlgorithm(self, parameters, context, feedback): - np.seterr(divide="ignore", invalid="ignore") - - # InputParameters - dsmlayer = self.parameterAsRasterLayer( - parameters, self.INPUT_DSM, context - ) - transVeg = ( - self.parameterAsDouble(parameters, self.TRANS_VEG, context) / 100.0 - ) - firstdayleaf = self.parameterAsInt( - parameters, self.LEAF_START, context - ) - lastdayleaf = self.parameterAsInt(parameters, self.LEAF_END, context) - conifer_bool = self.parameterAsBool( - parameters, self.CONIFER_TREES, context - ) - vegdsm = self.parameterAsRasterLayer( - parameters, self.INPUT_CDSM, context - ) - vegdsm2 = self.parameterAsRasterLayer( - parameters, self.INPUT_TDSM, context - ) - lcgrid = self.parameterAsRasterLayer( - parameters, self.INPUT_LC, context - ) - useLcBuild = self.parameterAsBool( - parameters, self.USE_LC_BUILD, context - ) - dem = None - inputSVF = self.parameterAsString(parameters, self.INPUT_SVF, context) - whlayer = self.parameterAsRasterLayer( - parameters, self.INPUT_HEIGHT, context - ) - walayer = self.parameterAsRasterLayer( - parameters, self.INPUT_ASPECT, context - ) - trunkr = self.parameterAsDouble( - parameters, self.INPUT_THEIGHT, context - ) - onlyglobal = self.parameterAsBool(parameters, self.ONLYGLOBAL, context) - utc = self.parameterAsDouble(parameters, self.UTC, context) - inputMet = self.parameterAsString(parameters, self.INPUT_MET, context) - # usePOI = self.parameterAsBool(parameters, self.POI, context) - poilyr = self.parameterAsVectorLayer( - parameters, self.POI_FILE, context - ) - poi_field = None - mbody = None - ht = None - clo = None - age = None - activity = None - sex = None - sensorheight = None - saveBuild = self.parameterAsBool(parameters, self.SAVE_BUILD, context) - demforbuild = 0 - folderPathPerez = self.parameterAsString( - parameters, self.INPUT_ANISO, context - ) - folderWallScheme = self.parameterAsString( - parameters, self.INPUT_WALLSCHEME, context - ) - wallNetCDF = self.parameterAsBool( - parameters, self.WALLTEMP_NETCDF, context - ) - poisxy = None - poiname = None - - # Wall of interest - woilyr = self.parameterAsVectorLayer( - parameters, self.WOI_FILE, context - ) - woi_field = None - woisxy = None - woiname = None - - # Other parameters # - absK = self.parameterAsDouble(parameters, self.ABS_S, context) - absL = self.parameterAsDouble(parameters, self.ABS_L, context) - pos = self.parameterAsInt(parameters, self.POSTURE, context) - - if self.parameterAsBool(parameters, self.CYL, context): - cyl = 1 - else: - cyl = 0 - - if pos == 0: - Fside = 0.22 - Fup = 0.06 - height = 1.1 - Fcyl = 0.28 - else: - Fside = 0.166666 - Fup = 0.166666 - height = 0.75 - Fcyl = 0.2 - - albedo_b = self.parameterAsDouble( - parameters, self.ALBEDO_WALLS, context - ) - albedo_g = self.parameterAsDouble( - parameters, self.ALBEDO_GROUND, context - ) - ewall = self.parameterAsDouble(parameters, self.EMIS_WALLS, context) - eground = self.parameterAsDouble(parameters, self.EMIS_GROUND, context) - elvis = 0 # option removed 20200907 in processing UMEP - # thermal_effusivity = self.parameterAsDouble(parameters, self.EFFUS_WALL, context) - wall_type = None - - outputDir = self.parameterAsString( - parameters, self.OUTPUT_DIR, context - ) - outputTmrt = self.parameterAsBool( - parameters, self.OUTPUT_TMRT, context - ) - outputSh = self.parameterAsBool(parameters, self.OUTPUT_SH, context) - outputKup = self.parameterAsBool(parameters, self.OUTPUT_KUP, context) - outputKdown = self.parameterAsBool( - parameters, self.OUTPUT_KDOWN, context - ) - outputLup = self.parameterAsBool(parameters, self.OUTPUT_LUP, context) - outputLdown = self.parameterAsBool( - parameters, self.OUTPUT_LDOWN, context - ) - outputTreeplanter = self.parameterAsBool( - parameters, self.OUTPUT_TREEPLANTER, context - ) - outputKdiff = False - # outputSstr = False - - # If "Save necessary rasters for TreePlanter tool" is ticked, save the following raster for TreePlanter or Spatial TC - if outputTreeplanter: - outputTmrt = True - outputKup = True - outputKdown = True - outputLup = True - outputLdown = True - outputSh = True - saveBuild = True - outputKdiff = True - # outputSstr = True - - if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": - if not (os.path.isdir(outputDir)): - os.mkdir(outputDir) - - # Load parameters for SOLWEIG for surface temperatures, etc - data_path = self.plugin_dir + "/parametersforsolweig.json" - with open(data_path, "r") as jsn: - solweig_parameters = json.load(jsn) - - # Add GUI Tmrt settings to SOLWEIG parameter file - solweig_parameters["Tmrt_params"]["Value"]["absK"] = absK - solweig_parameters["Tmrt_params"]["Value"]["absL"] = absL - if pos == 0: - solweig_parameters["Tmrt_params"]["Value"]["posture"] = "Standing" - else: - solweig_parameters["Tmrt_params"]["Value"]["posture"] = "Sitting" - - solweig_parameters["Albedo"]["Effective"]["Value"][ - "Cobble_stone_2014a" - ] = albedo_g - solweig_parameters["Albedo"]["Effective"]["Value"]["Walls"] = albedo_b - solweig_parameters["Emissivity"]["Value"][ - "Cobble_stone_2014a" - ] = eground - solweig_parameters["Emissivity"]["Value"]["Walls"] = ewall - - # Code from old plugin - provider = dsmlayer.dataProvider() - filepath_dsm = str(provider.dataSourceUri()) - gdal_dsm = gdal.Open(filepath_dsm) - dsm = gdal_dsm.ReadAsArray().astype(float) - sizex = dsm.shape[0] - sizey = dsm.shape[1] - rows = dsm.shape[0] - cols = dsm.shape[1] - - # response to issue #85 - nd = gdal_dsm.GetRasterBand(1).GetNoDataValue() - dsm[dsm == nd] = 0.0 - # dsmcopy = np.copy(dsm) - if dsm.min() < 0: - dsmraise = np.abs(dsm.min()) - dsm = dsm + dsmraise - feedback.setProgressText( - "Digital Surface Model (DSM) included negative values. DSM raised with " - + str(dsmraise) - + "m." - ) - else: - dsmraise = 0 - - # Get latlon from grid coordinate system - old_cs = osr.SpatialReference() - dsm_ref = dsmlayer.crs().toWkt() - old_cs.ImportFromWkt(dsm_ref) - - wgs84_wkt = """ - GEOGCS["WGS 84", - DATUM["WGS_1984", - SPHEROID["WGS 84",6378137,298.257223563, - AUTHORITY["EPSG","7030"]], - AUTHORITY["EPSG","6326"]], - PRIMEM["Greenwich",0, - AUTHORITY["EPSG","8901"]], - UNIT["degree",0.01745329251994328, - AUTHORITY["EPSG","9122"]], - AUTHORITY["EPSG","4326"]]""" - - new_cs = osr.SpatialReference() - new_cs.ImportFromWkt(wgs84_wkt) - - transform = osr.CoordinateTransformation(old_cs, new_cs) - widthx = gdal_dsm.RasterXSize - heightx = gdal_dsm.RasterYSize - geotransform = gdal_dsm.GetGeoTransform() - minx = geotransform[0] - miny = ( - geotransform[3] - + widthx * geotransform[4] - + heightx * geotransform[5] - ) - - lonlat = transform.TransformPoint(minx, miny) - gdalver = float(gdal.__version__[0]) - if gdalver == 3.0: - lon = lonlat[1] # changed to gdal 3 - lat = lonlat[0] # changed to gdal 3 - else: - lon = lonlat[0] # changed to gdal 2 - lat = lonlat[1] # changed to gdal 2 - scale = 1 / geotransform[1] - - pixel_resolution = geotransform[1] - - alt = np.median(dsm) - if alt < 0: - alt = 3 - feedback.setProgressText("Longitude derived from DSM: " + str(lon)) - feedback.setProgressText("Latitude derived from DSM: " + str(lat)) - - trunkfile = 0 - trunkratio = 0 - # psi = transVeg / 100.0 - - # if useVegdem: - if vegdsm is not None: - usevegdem = 1 - feedback.setProgressText("Vegetation scheme activated") - - # load raster - gdal.AllRegister() - provider = vegdsm.dataProvider() - filePathOld = str(provider.dataSourceUri()) - dataSet = gdal.Open(filePathOld) - vegdsm = dataSet.ReadAsArray().astype(float) - filePath_cdsm = filePathOld - vegsizex = vegdsm.shape[0] - vegsizey = vegdsm.shape[1] - - if not (vegsizex == sizex) & (vegsizey == sizey): - raise QgsProcessingException( - "Error in Vegetation Canopy DSM: All rasters must be of same extent and resolution" - ) - - if vegdsm2 is not None: - gdal.AllRegister() - provider = vegdsm2.dataProvider() - filePathOld = str(provider.dataSourceUri()) - filePath_tdsm = filePathOld - dataSet = gdal.Open(filePathOld) - vegdsm2 = dataSet.ReadAsArray().astype(float) - else: - trunkratio = trunkr / 100.0 - vegdsm2 = vegdsm * trunkratio - filePath_tdsm = None - - vegsizex = vegdsm2.shape[0] - vegsizey = vegdsm2.shape[1] - - if not (vegsizex == sizex) & (vegsizey == sizey): # & - raise QgsProcessingException( - "Error in Trunk Zone DSM: All rasters must be of same extent and resolution" - ) - else: - vegdsm = np.zeros([rows, cols]) - vegdsm2 = np.zeros([rows, cols]) - usevegdem = 0 - filePath_cdsm = None - filePath_tdsm = None - - # Land cover - if lcgrid is not None: - landcover = 1 - feedback.setProgressText("Land cover scheme activated") - - # load raster - gdal.AllRegister() - provider = lcgrid.dataProvider() - filePath_lc = str(provider.dataSourceUri()) - dataSet = gdal.Open(filePath_lc) - lcgrid = dataSet.ReadAsArray().astype(float) - - lcsizex = lcgrid.shape[0] - lcsizey = lcgrid.shape[1] - - if not (lcsizex == sizex) & (lcsizey == sizey): - raise QgsProcessingException( - "Error in land cover grid: All grids must be of same extent and resolution" - ) - - baddataConifer = lcgrid == 3 - baddataDecid = lcgrid == 4 - if baddataConifer.any(): - raise QgsProcessingException( - "Error in land cover grid: Land cover grid includes Confier land cover class. Ground cover information (underneath canopy) is required." - ) - if baddataDecid.any(): - raise QgsProcessingException( - "Error in land cover grid: Land cover grid includes Decidiuous land cover class. Ground cover information (underneath canopy) is required." - ) - if np.isnan(lcgrid).any(): - raise QgsProcessingException( - "Error in land cover grid: Land cover grid includes NaN values. Use the QGIS Fill NoData cells tool to remove NaN values." - ) - else: - filePath_lc = None - landcover = 0 - lcgrid = 0 - - # DEM # - if not useLcBuild: - demforbuild = 1 - dem = self.parameterAsRasterLayer( - parameters, self.INPUT_DEM, context - ) - - if dem is None: - raise QgsProcessingException("Error: No valid DEM selected") - - # load raster - gdal.AllRegister() - provider = dem.dataProvider() - filePathOld = str(provider.dataSourceUri()) - dataSet = gdal.Open(filePathOld) - dem = dataSet.ReadAsArray().astype(float) - - demsizex = dem.shape[0] - demsizey = dem.shape[1] - - if not (demsizex == sizex) & (demsizey == sizey): - raise QgsProcessingException( - "Error in DEM: All grids must be of same extent and resolution" - ) - - # response to issue and #230 - nd = dataSet.GetRasterBand(1).GetNoDataValue() - dem[dem == nd] = 0.0 - if dem.min() < 0: - demraise = np.abs(dem.min()) - dem = dem + demraise - feedback.setProgressText( - "Digital Evevation Model (DEM) included negative values. DEM raised with " - + str(demraise) - + "m." - ) - else: - demraise = 0 - - alt = np.median(dem) - if alt > 0: - alt = 3.0 - - if (dsmraise != demraise) and (dsmraise - demraise > 0.5): - feedback.setProgressText( - "WARNiNG! DEM and DSM was raised unequally (difference > 0.5 m). Check your input data!" - ) - - # SVFs - zip = zipfile.ZipFile(inputSVF, "r") - zip.extractall(self.temp_dir) - zip.close() - - try: - dataSet = gdal.Open(self.temp_dir + "/svf.tif") - svf = dataSet.ReadAsArray().astype(float) - dataSet = gdal.Open(self.temp_dir + "/svfN.tif") - svfN = dataSet.ReadAsArray().astype(float) - dataSet = gdal.Open(self.temp_dir + "/svfS.tif") - svfS = dataSet.ReadAsArray().astype(float) - dataSet = gdal.Open(self.temp_dir + "/svfE.tif") - svfE = dataSet.ReadAsArray().astype(float) - dataSet = gdal.Open(self.temp_dir + "/svfW.tif") - svfW = dataSet.ReadAsArray().astype(float) - - if usevegdem == 1: - dataSet = gdal.Open(self.temp_dir + "/svfveg.tif") - svfveg = dataSet.ReadAsArray().astype(float) - dataSet = gdal.Open(self.temp_dir + "/svfNveg.tif") - svfNveg = dataSet.ReadAsArray().astype(float) - dataSet = gdal.Open(self.temp_dir + "/svfSveg.tif") - svfSveg = dataSet.ReadAsArray().astype(float) - dataSet = gdal.Open(self.temp_dir + "/svfEveg.tif") - svfEveg = dataSet.ReadAsArray().astype(float) - dataSet = gdal.Open(self.temp_dir + "/svfWveg.tif") - svfWveg = dataSet.ReadAsArray().astype(float) - - dataSet = gdal.Open(self.temp_dir + "/svfaveg.tif") - svfaveg = dataSet.ReadAsArray().astype(float) - dataSet = gdal.Open(self.temp_dir + "/svfNaveg.tif") - svfNaveg = dataSet.ReadAsArray().astype(float) - dataSet = gdal.Open(self.temp_dir + "/svfSaveg.tif") - svfSaveg = dataSet.ReadAsArray().astype(float) - dataSet = gdal.Open(self.temp_dir + "/svfEaveg.tif") - svfEaveg = dataSet.ReadAsArray().astype(float) - dataSet = gdal.Open(self.temp_dir + "/svfWaveg.tif") - svfWaveg = dataSet.ReadAsArray().astype(float) - else: - svfveg = np.ones((rows, cols)) - svfNveg = np.ones((rows, cols)) - svfSveg = np.ones((rows, cols)) - svfEveg = np.ones((rows, cols)) - svfWveg = np.ones((rows, cols)) - svfaveg = np.ones((rows, cols)) - svfNaveg = np.ones((rows, cols)) - svfSaveg = np.ones((rows, cols)) - svfEaveg = np.ones((rows, cols)) - svfWaveg = np.ones((rows, cols)) - except: - raise QgsProcessingException( - "SVF import error: The zipfile including the SVFs seems corrupt. Retry calcualting the SVFs in the Pre-processor or choose another file." - ) - - svfsizex = svf.shape[0] - svfsizey = svf.shape[1] - - if not (svfsizex == sizex) & (svfsizey == sizey): # & - raise QgsProcessingException( - "Error in svf rasters: All grids must be of same extent and resolution" - ) - - tmp = svf + svfveg - 1.0 - tmp[tmp < 0.0] = 0.0 - # %matlab crazyness around 0 - svfalfa = np.arcsin(np.exp((np.log((1.0 - tmp)) / 2.0))) - - feedback.setProgressText("Sky View Factor rasters loaded") - - # wall height layer - if whlayer is None: - raise QgsProcessingException( - "Error: No valid wall height raster layer is selected" - ) - provider = whlayer.dataProvider() - filepath_wh = str(provider.dataSourceUri()) - self.gdal_wh = gdal.Open(filepath_wh) - wallheight = self.gdal_wh.ReadAsArray().astype(float) - vhsizex = wallheight.shape[0] - vhsizey = wallheight.shape[1] - if not (vhsizex == sizex) & (vhsizey == sizey): - raise QgsProcessingException( - "Error in Wall height raster: All rasters must be of same extent and resolution" - ) - - # wall aspectlayer - if walayer is None: - raise QgsProcessingException( - "Error: No valid wall aspect raster layer is selected" - ) - provider = walayer.dataProvider() - filepath_wa = str(provider.dataSourceUri()) - self.gdal_wa = gdal.Open(filepath_wa) - wallaspect = self.gdal_wa.ReadAsArray().astype(float) - vasizex = wallaspect.shape[0] - vasizey = wallaspect.shape[1] - if not (vasizex == sizex) & (vasizey == sizey): - raise QgsProcessingException( - "Error in Wall aspect raster: All rasters must be of same extent and resolution" - ) - - # Metdata - headernum = 1 - delim = " " - Twater = [] - - try: - self.metdata = np.loadtxt( - inputMet, skiprows=headernum, delimiter=delim - ) - metfileexist = 1 - except: - raise QgsProcessingException( - "Error: Make sure format of meteorological file is correct. You can" - "prepare your data by using 'Prepare Existing Data' in " - "the Pre-processor" - ) - - testwhere = np.where( - (self.metdata[:, 14] < 0.0) | (self.metdata[:, 14] > 1300.0) - ) - if testwhere[0].__len__() > 0: - raise QgsProcessingException( - "Error: Kdown - beyond what is expected at line: " - + str(testwhere[0] + 1) - ) - - if self.metdata.shape[1] == 24: - feedback.setProgressText("Meteorological data successfully loaded") - else: - raise QgsProcessingException( - "Error: Wrong number of columns in meteorological data. You can " - "prepare your data by using 'Prepare Existing Data' in " - "the Pre-processor" - ) - - feedback.setProgressText( - "Calculating sun positions for each time step" - ) - location = {"longitude": lon, "latitude": lat, "altitude": alt} - YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax = ( - Solweig_2015a_metdata_noload(self.metdata, location, utc) - ) - - # Creating vectors from meteorological input - DOY = self.metdata[:, 1] - hours = self.metdata[:, 2] - minu = self.metdata[:, 3] - Ta = self.metdata[:, 11] - RH = self.metdata[:, 10] - radG = self.metdata[:, 14] - radD = self.metdata[:, 21] - radI = self.metdata[:, 22] - P = self.metdata[:, 12] - Ws = self.metdata[:, 9] - - # Check if diffuse and direct radiation exist - if onlyglobal == 0: - if np.min(radD) == -999: - raise QgsProcessingException( - "Diffuse radiation include NoData values", - 'Tick in the box "Estimate diffuse and direct shortwave..." or aqcuire ' - "observed values from external data sources.", - ) - if np.min(radI) == -999: - raise QgsProcessingException( - "Direct radiation include NoData values", - 'Tick in the box "Estimate diffuse and direct shortwave..." or aqcuire ' - "observed values from external data sources.", - ) - - # POIs check - if poilyr is not None: # usePOI: - # header = 'yyyy id it imin dectime altitude azimuth kdir kdiff kglobal kdown kup keast ksouth ' \ - # 'kwest knorth ldown lup least lsouth lwest lnorth Ta Tg RH Esky Tmrt ' \ - # 'I0 CI Shadow SVF_b SVF_bv KsideI PET UTCI' - - header = ( - "yyyy id it imin dectime altitude azimuth kdir kdiff kglobal kdown kup keast ksouth " - "kwest knorth ldown lup least lsouth lwest lnorth Ta Tg RH Esky Tmrt " - "I0 CI Shadow SVF_b SVF_bv KsideI PET UTCI CI_Tg CI_TgG KsideD Lside diffDown Kside" - ) - - # poilyr = self.parameterAsVectorLayer(parameters, self.POI_FILE, context) - # if poilyr is None: - # raise QgsProcessingException("No valid point layer is selected") - - poi_field = self.parameterAsFields( - parameters, self.POI_FIELD, context - ) - # if poi_field[0] is None: - # raise QgsProcessingException("An attribute field with unique values must be selected when using a POI vector file") - vlayer = poilyr - prov = vlayer.dataProvider() - fields = prov.fields() - idx = vlayer.fields().indexFromName(poi_field[0]) - numfeat = vlayer.featureCount() - poiname = [] - poisxy = np.zeros((numfeat, 3)) - 999 - ind = 0 - for f in vlayer.getFeatures(): # looping through each POI - y = f.geometry().centroid().asPoint().y() - x = f.geometry().centroid().asPoint().x() - - poiname.append(f.attributes()[idx]) - poisxy[ind, 0] = ind - poisxy[ind, 1] = np.round((x - minx) * scale) - if miny >= 0: - poisxy[ind, 2] = np.round( - (miny + rows * (1.0 / scale) - y) * scale - ) - else: - poisxy[ind, 2] = np.round( - (miny + rows * (1.0 / scale) - y) * scale - ) - - ind += 1 - - for k in range(0, poisxy.shape[0]): - poi_save = [] # np.zeros((1, 33)) - data_out = outputDir + "/POI_" + str(poiname[k]) + ".txt" - np.savetxt( - data_out, - poi_save, - delimiter=" ", - header=header, - comments="", - ) - - # Num format for POI output - numformat = "%d %d %d %d %.5f " + "%.2f " * 36 - - # Other PET variables - mbody = self.parameterAsDouble(parameters, self.WEIGHT, context) - ht = ( - self.parameterAsDouble(parameters, self.HEIGHT, context) - / 100.0 - ) - clo = self.parameterAsDouble(parameters, self.CLO, context) - age = self.parameterAsDouble(parameters, self.AGE, context) - activity = self.parameterAsDouble(parameters, self.WEIGHT, context) - sex = self.parameterAsInt(parameters, self.SEX, context) + 1 - sensorheight = self.parameterAsDouble( - parameters, self.SENSOR_HEIGHT, context - ) - - solweig_parameters["PET_settings"]["Value"]["Age"] = age - solweig_parameters["PET_settings"]["Value"]["Weight"] = mbody - solweig_parameters["PET_settings"]["Value"]["Height"] = ht - solweig_parameters["PET_settings"]["Value"]["clo"] = clo - solweig_parameters["PET_settings"]["Value"]["Activity"] = activity - if sex == 1: - solweig_parameters["PET_settings"]["Value"]["Sex"] = "Male" - else: - solweig_parameters["PET_settings"]["Value"]["Sex"] = "Female" - - feedback.setProgressText( - "Point of interest (POI) vector data successfully loaded" - ) - - # %Parameterisarion for Lup - if not height: - height = 1.1 - - # %Radiative surface influence, Rule of thumb by Schmid et al. (1990). - first = np.round(height) - if first == 0.0: - first = 1.0 - second = np.round((height * 20.0)) - - if usevegdem == 1: - # Conifer or deciduous - if conifer_bool: - leafon = np.ones((1, DOY.shape[0])) - else: - leafon = np.zeros((1, DOY.shape[0])) - if firstdayleaf > lastdayleaf: - leaf_bool = (DOY > firstdayleaf) | (DOY < lastdayleaf) - else: - leaf_bool = (DOY > firstdayleaf) & (DOY < lastdayleaf) - leafon[0, leaf_bool] = 1 - - # % Vegetation transmittivity of shortwave radiation - psi = leafon * transVeg - psi[leafon == 0] = 0.5 - # amaxvalue - vegmax = vegdsm.max() - amaxvalue = dsm.max() - dsm.min() - amaxvalue = np.maximum(amaxvalue, vegmax) - - # Elevation vegdsms if buildingDEM includes ground heights - vegdsm = vegdsm + dsm - vegdsm[vegdsm == dsm] = 0 - vegdsm2 = vegdsm2 + dsm - vegdsm2[vegdsm2 == dsm] = 0 - - # % Bush separation - bush = np.logical_not((vegdsm2 * vegdsm)) * vegdsm - - svfbuveg = svf - (1.0 - svfveg) * ( - 1.0 - transVeg - ) # % major bug fixed 20141203 - else: - psi = leafon * 0.0 + 1.0 - svfbuveg = svf - bush = np.zeros([rows, cols]) - amaxvalue = 0 - - # %Initialization of maps - Knight = np.zeros((rows, cols)) - Tgmap1 = np.zeros((rows, cols)) - Tgmap1E = np.zeros((rows, cols)) - Tgmap1S = np.zeros((rows, cols)) - Tgmap1W = np.zeros((rows, cols)) - Tgmap1N = np.zeros((rows, cols)) - - # building grid and land cover preparation - # sitein = self.plugin_dir + "/landcoverclasses_2016a.txt" - # f = open(sitein) - # lin = f.readlines() - # lc_class = np.zeros((lin.__len__() - 1, 6)) - # for i in range(1, lin.__len__()): - # lines = lin[i].split() - # for j in np.arange(1, 7): - # lc_class[i - 1, j - 1] = float(lines[j]) - - # lc_df = pd.read_csv(self.plugin_dir + '/landcoverclasses_2023a.txt') - # lc_class = lc_df[['Code', 'Albedo', 'Emissivity', 'Ts_deg', 'Tstart', 'TmaxLST']].to_numpy() - - if demforbuild == 0: - buildings = np.copy(lcgrid) - buildings[buildings == 7] = 1 - buildings[buildings == 6] = 1 - buildings[buildings == 5] = 1 - buildings[buildings == 4] = 1 - buildings[buildings == 3] = 1 - buildings[buildings == 2] = 0 - else: - buildings = dsm - dem - buildings[buildings < 2.0] = 1.0 - buildings[buildings >= 2.0] = 0.0 - - if saveBuild: - saveraster(gdal_dsm, outputDir + "/buildings.tif", buildings) - - # Import shadow matrices (Anisotropic sky) - if folderPathPerez: # UseAniso - anisotropic_sky = 1 - data = np.load(folderPathPerez) - shmat = data["shadowmat"] - vegshmat = data["vegshadowmat"] - vbshvegshmat = data["vbshmat"] - if usevegdem == 1: - diffsh = np.zeros((rows, cols, shmat.shape[2])) - for i in range(0, shmat.shape[2]): - diffsh[:, :, i] = shmat[:, :, i] - ( - 1 - vegshmat[:, :, i] - ) * ( - 1 - transVeg - ) # changes in psi not implemented yet - else: - diffsh = shmat - # vegshmat += 1 - # vbshvegshmat += 1 - - # Estimate number of patches based on shadow matrices - if shmat.shape[2] == 145: - patch_option = 1 # patch_option = 1 # 145 patches - elif shmat.shape[2] == 153: - patch_option = 2 # patch_option = 2 # 153 patches - elif shmat.shape[2] == 306: - patch_option = 3 # patch_option = 3 # 306 patches - elif shmat.shape[2] == 612: - patch_option = 4 # patch_option = 4 # 612 patches - - # asvf to calculate sunlit and shaded patches - asvf = np.arccos(np.sqrt(svf)) - - # Empty array for steradians - steradians = np.zeros((shmat.shape[2])) - - anisotropic_feedback = ( - "Sky divided into " - + str(int(shmat.shape[2])) - + " patches\n \ - Anisotropic sky for diffuse shortwave radiation (Perez et al., 1993) and longwave radiation (Martin & Berdahl, 1984)" - ) - feedback.setProgressText(anisotropic_feedback) - else: - feedback.setProgressText("Isotropic sky") - anisotropic_sky = 0 - diffsh = None - shmat = None - vegshmat = None - vbshvegshmat = None - asvf = None - patch_option = 0 - steradians = 0 - - # % Ts parameterisation maps - if landcover == 1.0: - if folderWallScheme: - unique_landcover = np.unique(lcgrid) - unique_landcover = unique_landcover[unique_landcover < 100] - if ( - np.max(unique_landcover) > 7 - or np.min(unique_landcover) < 1 - ): - raise QgsProcessingException( - "The land cover grid includes integer values higher (or lower) than UMEP-formatted " - "land cover grid (should be integer between 1 and 7). If other LC-classes should be included they also need to be included in landcoverclasses_2016a.txt" - ) - else: - if np.max(lcgrid) > 7 or np.min(lcgrid) < 1: - raise QgsProcessingException( - "The land cover grid includes integer values higher (or lower) than UMEP-formatted " - "land cover grid (should be integer between 1 and 7). If other LC-classes should be included they also need to be included in landcoverclasses_2016a.txt" - ) - if np.where(lcgrid) == 3 or np.where(lcgrid) == 4: - raise QgsProcessingException( - "The land cover grid includes values (decidouos and/or conifer) not appropriate for SOLWEIG-formatted land cover grid (should not include 3 or 4)." - ) - - # Get land cover properties for Tg wave (land cover scheme based on Bogren et al. 2000, explained in Lindberg et al., 2008 and Lindberg, Onomura & Grimmond, 2016) - [ - TgK, - Tstart, - alb_grid, - emis_grid, - TgK_wall, - Tstart_wall, - TmaxLST, - TmaxLST_wall, - ] = Tgmaps_v1(lcgrid.copy(), solweig_parameters) - - else: - TgK = ( - Knight - + solweig_parameters["Ts_deg"]["Value"]["Cobble_stone_2014a"] - ) - Tstart = ( - Knight - - solweig_parameters["Tstart"]["Value"]["Cobble_stone_2014a"] - ) - TmaxLST = solweig_parameters["TmaxLST"]["Value"][ - "Cobble_stone_2014a" - ] - alb_grid = ( - Knight - + solweig_parameters["Albedo"]["Effective"]["Value"][ - "Cobble_stone_2014a" - ] - ) - emis_grid = ( - Knight - + solweig_parameters["Emissivity"]["Value"][ - "Cobble_stone_2014a" - ] - ) - TgK_wall = solweig_parameters["Ts_deg"]["Value"]["Walls"] - Tstart_wall = solweig_parameters["Tstart"]["Value"]["Walls"] - TmaxLST_wall = solweig_parameters["TmaxLST"]["Value"]["Walls"] - - # Import data for wall temperature parameterization - if folderWallScheme: - wallScheme = 1 - wallData = np.load(folderWallScheme) - voxelMaps = wallData["voxelId"] - voxelTable = wallData["voxelTable"] - # Get wall type set in GUI - wall_type = str( - 100 - + int( - self.parameterAsString(parameters, self.WALL_TYPE, context) - ) - ) - - # Calculate wall height for wall scheme, i.e. include corners (thicker walls) - walls_scheme = wa.findwalls_sp( - dsm, 2, np.array([[1, 1, 1], [1, 0, 1], [1, 1, 1]]) - ) - # Calculate wall aspect for wall scheme, i.e. include corners (thicker walls) - dirwalls_scheme = wa.filter1Goodwin_as_aspect_v3( - walls_scheme.copy(), scale, dsm, feedback, 100.0 / 180.0 - ) - - # Used in wall temperature parameterization scheme - first_timestep = ( - pd.to_datetime(YYYY[0][0], format="%Y") - + pd.to_timedelta(DOY[0] - 1, unit="d") - + pd.to_timedelta(hours[0], unit="h") - + pd.to_timedelta(minu[0], unit="m") - ) - second_timestep = ( - pd.to_datetime(YYYY[0][1], format="%Y") - + pd.to_timedelta(DOY[1] - 1, unit="d") - + pd.to_timedelta(hours[1], unit="h") - + pd.to_timedelta(minu[1], unit="m") - ) - - timeStep = (second_timestep - first_timestep).seconds - - # Load voxelTable as Pandas DataFrame - voxelTable, dirwalls_scheme = load_walls( - voxelTable, - solweig_parameters, - wall_type, - dirwalls_scheme, - Ta[0], - timeStep, - albedo_b, - ewall, - alb_grid, - landcover, - lcgrid, - dsm, - ) - # Unique wall types - thermal_effusivity = voxelTable["thermalEffusivity"].unique() - - # Empty wall temperature matrix for parameterization scheme - wallScheme_feedback = ( - "Running with wall parameterization scheme. Walls divided into " - + str(int(voxelTable.shape[0])) - + " unique voxels." - ) - feedback.setProgressText(wallScheme_feedback) - - # Use wall of interest - if woilyr is not None: - ( - dsm_minx, - dsm_x_size, - dsm_x_rotation, - dsm_miny, - dsm_y_rotation, - dsm_y_size, - ) = gdal_dsm.GetGeoTransform() - - woi_field = self.parameterAsStrings( - parameters, self.WOI_FIELD, context - ) - woisxy, woiname = wallOfInterest( - woilyr, - woi_field, - minx, - miny, - scale, - rows, - outputDir, - dsm_minx, - dsm_x_size, - dsm_miny, - dsm_y_size, - ) - - # Create pandas datetime object to be used when createing an xarray DataSet where wall temperatures/radiation is stored and eventually saved as a NetCDf - if wallNetCDF: - met_for_xarray = ( - pd.to_datetime(YYYY[0][:], format="%Y") - + pd.to_timedelta(DOY - 1, unit="d") - + pd.to_timedelta(hours, unit="h") - + pd.to_timedelta(minu, unit="m") - ) - - else: - wallScheme = 0 - voxelMaps = 0 - voxelTable = 0 - timeStep = 0 - thermal_effusivity = 0 - walls_scheme = np.ones((rows, cols)) * 10.0 - dirwalls_scheme = np.ones((rows, cols)) * 10.0 - - # Initialisation of time related variables - if Ta.__len__() == 1: - timestepdec = 0 - else: - timestepdec = dectime[1] - dectime[0] - timeadd = 0.0 - timeaddE = 0.0 - timeaddS = 0.0 - timeaddW = 0.0 - timeaddN = 0.0 - firstdaytime = 1.0 - - WriteMetadataSOLWEIG.writeRunInfo( - outputDir, - filepath_dsm, - gdal_dsm, - usevegdem, - filePath_cdsm, - trunkfile, - filePath_tdsm, - lat, - lon, - utc, - landcover, - filePath_lc, - metfileexist, - inputMet, - self.metdata, - self.plugin_dir, - absK, - absL, - albedo_b, - albedo_g, - ewall, - eground, - onlyglobal, - trunkratio, - transVeg, - rows, - cols, - pos, - elvis, - cyl, - demforbuild, - anisotropic_sky, - wallScheme, - thermal_effusivity, - ) - - feedback.setProgressText( - "Writing settings for this model run to specified output folder (Filename: RunInfoSOLWEIG_YYYY_DOY_HHMM.txt)" - ) - - # Save svf - if anisotropic_sky: - if not poisxy is None: - patch_characteristics = np.zeros( - (shmat.shape[2], poisxy.shape[0]) - ) - for idx in range(poisxy.shape[0]): - for idy in range(shmat.shape[2]): - # Calculations for patches on sky, shmat = 1 = sky is visible - temp_sky = (shmat[:, :, idy] == 1) & ( - vegshmat[:, :, idy] == 1 - ) - # Calculations for patches that are vegetation, vegshmat = 0 = shade from vegetation - temp_vegsh = (vegshmat[:, :, idy] == 0) | ( - vbshvegshmat[:, :, idy] == 0 - ) - # Calculations for patches that are buildings, shmat = 0 = shade from buildings - temp_vbsh = (1 - shmat[:, :, idy]) * vbshvegshmat[ - :, :, idy - ] - temp_sh = temp_vbsh == 1 - if wallScheme: - temp_sh_w = temp_sh * voxelMaps[:, :, idy] - temp_sh_roof = temp_sh * ( - voxelMaps[:, :, idy] == 0 - ) - else: - temp_sh_w = 0 - temp_sh_roof = 0 - # Sky patch - if temp_sky[int(poisxy[idx, 2]), int(poisxy[idx, 1])]: - patch_characteristics[idy, idx] = 1.8 - # Vegetation patch - elif temp_vegsh[ - int(poisxy[idx, 2]), int(poisxy[idx, 1]) - ]: - patch_characteristics[idy, idx] = 2.5 - # Building patch - elif temp_sh[int(poisxy[idx, 2]), int(poisxy[idx, 1])]: - if wallScheme: - if temp_sh_w[ - int(poisxy[idx, 2]), int(poisxy[idx, 1]) - ]: - patch_characteristics[idy, idx] = 4.5 - elif temp_sh_roof[ - int(poisxy[idx, 2]), int(poisxy[idx, 1]) - ]: - patch_characteristics[idy, idx] = 6.0 - else: - patch_characteristics[idy, idx] = 4.5 - # Roof patch - # elif - - # If metfile starts at night - CI = 1.0 - - # Save solweig_parameters in output folder - with open(outputDir + "/solweig_parameters.json", "w") as f: - json.dump(solweig_parameters, f, indent=2) - - # Main function - feedback.setProgressText("Executing main model") - - tmrtplot = np.zeros((rows, cols)) - TgOut1 = np.zeros((rows, cols)) - - # Initiate array for I0 values - if np.unique(DOY).shape[0] > 1: - unique_days = np.unique(DOY) - first_unique_day = DOY[DOY == unique_days[0]] - I0_array = np.zeros((first_unique_day.shape[0])) - else: - first_unique_day = DOY.copy() - I0_array = np.zeros((DOY.shape[0])) - - # Rotate canyon - rotate_deg = 0 - - for i in np.arange(0, Ta.__len__()): - feedback.setProgress( - int(i * (100.0 / Ta.__len__())) - ) # move progressbar forward - if feedback.isCanceled(): - feedback.setProgressText("Calculation cancelled") - break - # Daily water body temperature - if landcover == 1: - if ((dectime[i] - np.floor(dectime[i]))) == 0 or (i == 0): - Twater = np.mean(Ta[jday[0] == np.floor(dectime[i])]) - # Nocturnal cloudfraction from Offerle et al. 2003 - if (dectime[i] - np.floor(dectime[i])) == 0: - daylines = np.where(np.floor(dectime) == dectime[i]) - if daylines.__len__() > 1: - alt = altitude[0][daylines] - alt2 = np.where(alt > 1) - rise = alt2[0][0] - [_, CI, _, _, _] = clearnessindex_2013b( - zen[0, i + rise + 1], - jday[0, i + rise + 1], - Ta[i + rise + 1], - RH[i + rise + 1] / 100.0, - radG[i + rise + 1], - location, - P[i + rise + 1], - ) # i+rise+1 to match matlab code. correct? - if (CI > 1.0) or (CI == np.inf): - CI = 1.0 - else: - CI = 1.0 - - # if altitude[0][i] > 0: - # # # radI[i] = (radG[i] - radD[i])/np.sin(altitude[0][i] * np.pi/180) - # # # onlyglobal = False - # radI[i] = radI[i]/np.sin(altitude[0][i] * np.pi/180) - # else: - # radG[i] = 0. - # radD[i] = 0. - # radI[i] = 0. - - # radI[i] = radI[i]/np.sin(altitude[0][i] * np.pi/180) - - ( - Tmrt, - Kdown, - Kup, - Ldown, - Lup, - Tg, - ea, - esky, - I0, - CI, - shadow, - firstdaytime, - timestepdec, - timeadd, - Tgmap1, - Tgmap1E, - Tgmap1S, - Tgmap1W, - Tgmap1N, - Keast, - Ksouth, - Kwest, - Knorth, - Least, - Lsouth, - Lwest, - Lnorth, - KsideI, - TgOut1, - TgOut, - radIout, - radDout, - Lside, - Lsky_patch_characteristics, - CI_Tg, - CI_TgG, - KsideD, - dRad, - Kside, - steradians, - voxelTable, - ) = so.Solweig_2022a_calc( - i, - dsm, - scale, - rows, - cols, - svf, - svfN, - svfW, - svfE, - svfS, - svfveg, - svfNveg, - svfEveg, - svfSveg, - svfWveg, - svfaveg, - svfEaveg, - svfSaveg, - svfWaveg, - svfNaveg, - vegdsm, - vegdsm2, - albedo_b, - absK, - absL, - ewall, - Fside, - Fup, - Fcyl, - altitude[0][i], - azimuth[0][i] + rotate_deg, - zen[0][i], - jday[0][i], - usevegdem, - onlyglobal, - buildings, - location, - psi[0][i], - landcover, - lcgrid, - dectime[i], - altmax[0][i], - wallaspect, - wallheight, - cyl, - elvis, - Ta[i], - RH[i], - radG[i], - radD[i], - radI[i], - P[i], - amaxvalue, - bush, - Twater, - TgK, - Tstart, - alb_grid, - emis_grid, - TgK_wall, - Tstart_wall, - TmaxLST, - TmaxLST_wall, - first, - second, - svfalfa, - svfbuveg, - firstdaytime, - timeadd, - timestepdec, - Tgmap1, - Tgmap1E, - Tgmap1S, - Tgmap1W, - Tgmap1N, - CI, - TgOut1, - diffsh, - shmat, - vegshmat, - vbshvegshmat, - anisotropic_sky, - asvf, - patch_option, - voxelMaps, - voxelTable, - Ws[i], - wallScheme, - timeStep, - steradians, - walls_scheme, - dirwalls_scheme, - ) - - # Tmrt, Kdown, Kup, Ldown, Lup, Tg, ea, esky, I0, CI, shadow, firstdaytime, timestepdec, timeadd, \ - # Tgmap1, Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, Keast, Ksouth, Kwest, Knorth, Least, \ - # Lsouth, Lwest, Lnorth, KsideI, TgOut1, TgOut, radIout, radDout = so.Solweig_2021a_calc( - # i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, svfveg, - # svfNveg, svfEveg, svfSveg, svfWveg, svfaveg, svfEaveg, svfSaveg, svfWaveg, svfNaveg, - # vegdsm, vegdsm2, albedo_b, absK, absL, ewall, Fside, Fup, Fcyl, altitude[0][i], - # azimuth[0][i], zen[0][i], jday[0][i], usevegdem, onlyglobal, buildings, location, - # psi[0][i], landcover, lcgrid, dectime[i], altmax[0][i], wallaspect, - # wallheight, cyl, elvis, Ta[i], RH[i], radG[i], radD[i], radI[i], P[i], amaxvalue, - # bush, Twater, TgK, Tstart, alb_grid, emis_grid, TgK_wall, Tstart_wall, TmaxLST, - # TmaxLST_wall, first, second, svfalfa, svfbuveg, firstdaytime, timeadd, timestepdec, - # Tgmap1, Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, CI, TgOut1, diffsh, ani) - - # Tmrt, Kdown, Kup, Ldown, Lup, Tg, ea, esky, I0, CI, shadow, firstdaytime, timestepdec, timeadd, \ - # Tgmap1, timeaddE, Tgmap1E, timeaddS, Tgmap1S, timeaddW, Tgmap1W, timeaddN, Tgmap1N, \ - # Keast, Ksouth, Kwest, Knorth, Least, Lsouth, Lwest, Lnorth, KsideI, TgOut1, TgOut, radIout, radDout \ - # = so.Solweig_2019a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, svfveg, - # svfNveg, svfEveg, svfSveg, svfWveg, svfaveg, svfEaveg, svfSaveg, svfWaveg, svfNaveg, - # vegdsm, vegdsm2, albedo_b, absK, absL, ewall, Fside, Fup, Fcyl, altitude[0][i], - # azimuth[0][i], zen[0][i], jday[0][i], usevegdem, onlyglobal, buildings, location, - # psi[0][i], landcover, lcgrid, dectime[i], altmax[0][i], waspect, - # wheight, cyl, elvis, Ta[i], RH[i], radG[i], radD[i], radI[i], P[i], amaxvalue, - # bush, Twater, TgK, Tstart, alb_grid, emis_grid, TgK_wall, Tstart_wall, TmaxLST, - # TmaxLST_wall, first, second, svfalfa, svfbuveg, firstdaytime, timeadd, timeaddE, timeaddS, - # timeaddW, timeaddN, timestepdec, Tgmap1, Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, CI, TgOut1, diffsh, ani) - - # Save I0 for I0 vs. Kdown output plot to check if UTC is off - if i < (first_unique_day.shape[0] - 1): - I0_array[i] = I0 - elif i == (first_unique_day.shape[0] - 1): - # Output I0 vs. Kglobal plot - radG_for_plot = radG[DOY == first_unique_day[0]] - # hours_for_plot = hours[DOY == first_unique_day[0]] - dectime_for_plot = dectime[DOY == first_unique_day[0]] - fig, ax = plt.subplots() - ax.plot(dectime_for_plot, I0_array, label="I0") - ax.plot(dectime_for_plot, radG_for_plot, label="Kglobal") - ax.set_ylabel("Shortwave radiation [$Wm^{-2}$]") - ax.set_xlabel("Decimal time") - ax.set_title("UTC" + str(int(utc))) - ax.legend() - fig.savefig(outputDir + "/metCheck.png", dpi=150) - - tmrtplot = tmrtplot + Tmrt - - if altitude[0][i] > 0: - w = "D" - else: - w = "N" - - # # Write to POIs - # if not poisxy is None: - # for k in range(0, poisxy.shape[0]): - # poi_save = np.zeros((1, 35)) - # poi_save[0, 0] = YYYY[0][i] - # poi_save[0, 1] = jday[0][i] - # poi_save[0, 2] = hours[i] - # poi_save[0, 3] = minu[i] - # poi_save[0, 4] = dectime[i] - # poi_save[0, 5] = altitude[0][i] - # poi_save[0, 6] = azimuth[0][i] - # poi_save[0, 7] = radIout - # poi_save[0, 8] = radDout - # poi_save[0, 9] = radG[i] - # poi_save[0, 10] = Kdown[int(poisxy[k, 2]), int(poisxy[k, 1])] - # poi_save[0, 11] = Kup[int(poisxy[k, 2]), int(poisxy[k, 1])] - # poi_save[0, 12] = Keast[int(poisxy[k, 2]), int(poisxy[k, 1])] - # poi_save[0, 13] = Ksouth[int(poisxy[k, 2]), int(poisxy[k, 1])] - # poi_save[0, 14] = Kwest[int(poisxy[k, 2]), int(poisxy[k, 1])] - # poi_save[0, 15] = Knorth[int(poisxy[k, 2]), int(poisxy[k, 1])] - # poi_save[0, 16] = Ldown[int(poisxy[k, 2]), int(poisxy[k, 1])] - # poi_save[0, 17] = Lup[int(poisxy[k, 2]), int(poisxy[k, 1])] - # poi_save[0, 18] = Least[int(poisxy[k, 2]), int(poisxy[k, 1])] - # poi_save[0, 19] = Lsouth[int(poisxy[k, 2]), int(poisxy[k, 1])] - # poi_save[0, 20] = Lwest[int(poisxy[k, 2]), int(poisxy[k, 1])] - # poi_save[0, 21] = Lnorth[int(poisxy[k, 2]), int(poisxy[k, 1])] - # poi_save[0, 22] = Ta[i] - # poi_save[0, 23] = TgOut[int(poisxy[k, 2]), int(poisxy[k, 1])] - # poi_save[0, 24] = RH[i] - # poi_save[0, 25] = esky - # poi_save[0, 26] = Tmrt[int(poisxy[k, 2]), int(poisxy[k, 1])] - # poi_save[0, 27] = I0 - # poi_save[0, 28] = CI - # poi_save[0, 29] = shadow[int(poisxy[k, 2]), int(poisxy[k, 1])] - # poi_save[0, 30] = svf[int(poisxy[k, 2]), int(poisxy[k, 1])] - # poi_save[0, 31] = svfbuveg[int(poisxy[k, 2]), int(poisxy[k, 1])] - # poi_save[0, 32] = KsideI[int(poisxy[k, 2]), int(poisxy[k, 1])] - # # Recalculating wind speed based on powerlaw - # WsPET = (1.1 / sensorheight) ** 0.2 * Ws[i] - # WsUTCI = (10. / sensorheight) ** 0.2 * Ws[i] - # resultPET = p._PET(Ta[i], RH[i], Tmrt[int(poisxy[k, 2]), int(poisxy[k, 1])], WsPET, - # mbody, age, ht, activity, clo, sex) - # poi_save[0, 33] = resultPET - # resultUTCI = utci.utci_calculator(Ta[i], RH[i], Tmrt[int(poisxy[k, 2]), int(poisxy[k, 1])], - # WsUTCI) - # poi_save[0, 34] = resultUTCI - # data_out = outputDir + '/POI_' + str(poiname[k]) + '.txt' - # # f_handle = file(data_out, 'a') - # f_handle = open(data_out, 'ab') - # np.savetxt(f_handle, poi_save, fmt=numformat) - # f_handle.close() - - # Write to POIs - if not poisxy is None: - for k in range(0, poisxy.shape[0]): - poi_save = np.zeros((1, 41)) - poi_save[0, 0] = YYYY[0][i] - poi_save[0, 1] = jday[0][i] - poi_save[0, 2] = hours[i] - poi_save[0, 3] = minu[i] - poi_save[0, 4] = dectime[i] - poi_save[0, 5] = altitude[0][i] - poi_save[0, 6] = azimuth[0][i] - poi_save[0, 7] = radIout - poi_save[0, 8] = radDout - poi_save[0, 9] = radG[i] - poi_save[0, 10] = Kdown[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 11] = Kup[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 12] = Keast[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 13] = Ksouth[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 14] = Kwest[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 15] = Knorth[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 16] = Ldown[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 17] = Lup[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 18] = Least[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 19] = Lsouth[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 20] = Lwest[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 21] = Lnorth[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 22] = Ta[i] - poi_save[0, 23] = TgOut[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 24] = RH[i] - poi_save[0, 25] = esky - poi_save[0, 26] = Tmrt[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 27] = I0 - poi_save[0, 28] = CI - poi_save[0, 29] = shadow[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 30] = svf[int(poisxy[k, 2]), int(poisxy[k, 1])] - poi_save[0, 31] = svfbuveg[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 32] = KsideI[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - # Recalculating wind speed based on powerlaw - WsPET = (1.1 / sensorheight) ** 0.2 * Ws[i] - WsUTCI = (10.0 / sensorheight) ** 0.2 * Ws[i] - resultPET = p._PET( - Ta[i], - RH[i], - Tmrt[int(poisxy[k, 2]), int(poisxy[k, 1])], - WsPET, - mbody, - age, - ht, - activity, - clo, - sex, - ) - poi_save[0, 33] = resultPET - resultUTCI = utci.utci_calculator( - Ta[i], - RH[i], - Tmrt[int(poisxy[k, 2]), int(poisxy[k, 1])], - WsUTCI, - ) - poi_save[0, 34] = resultUTCI - poi_save[0, 35] = CI_Tg - poi_save[0, 36] = CI_TgG - poi_save[0, 37] = KsideD[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 38] = Lside[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 39] = dRad[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - poi_save[0, 40] = Kside[ - int(poisxy[k, 2]), int(poisxy[k, 1]) - ] - data_out = outputDir + "/POI_" + str(poiname[k]) + ".txt" - # f_handle = file(data_out, 'a') - f_handle = open(data_out, "ab") - np.savetxt(f_handle, poi_save, fmt=numformat) - f_handle.close() - - # If wall temperature parameterization scheme is in use - if folderWallScheme: - # Store wall data for output - if not woisxy is None: - for k in range(0, woisxy.shape[0]): - temp_wall = voxelTable.loc[ - ( - (voxelTable["ypos"] == woisxy[k, 2]) - & (voxelTable["xpos"] == woisxy[k, 1]) - ), - "wallTemperature", - ].to_numpy() - K_in = voxelTable.loc[ - ( - (voxelTable["ypos"] == woisxy[k, 2]) - & (voxelTable["xpos"] == woisxy[k, 1]) - ), - "K_in", - ].to_numpy() - L_in = voxelTable.loc[ - ( - (voxelTable["ypos"] == woisxy[k, 2]) - & (voxelTable["xpos"] == woisxy[k, 1]) - ), - "L_in", - ].to_numpy() - wallShade = voxelTable.loc[ - ( - (voxelTable["ypos"] == woisxy[k, 2]) - & (voxelTable["xpos"] == woisxy[k, 1]) - ), - "wallShade", - ].to_numpy() - temp_all = np.concatenate( - [temp_wall, K_in, L_in, wallShade] - ) - # temp_all = np.concatenate([temp_wall]) - # wall_data = np.zeros((1, 7 + temp_wall.shape[0])) - wall_data = np.zeros((1, 7 + temp_all.shape[0])) - # Part of file name (wallid), i.e. WOI_wallid.txt - data_out = ( - outputDir + "/WOI_" + str(woiname[k]) + ".txt" - ) - if i == 0: - # Output file header - # header = 'yyyy id it imin dectime Ta SVF Ts' - header = ( - "yyyy id it imin dectime Ta SVF" - + " Ts" * temp_wall.shape[0] - + " Kin" * K_in.shape[0] - + " Lin" * L_in.shape[0] - + " shade" * wallShade.shape[0] - ) - # Part of file name (wallid), i.e. WOI_wallid.txt - # woiname = voxelTable.loc[((voxelTable['ypos'] == woisxy[k, 2]) & (voxelTable['xpos'] == woisxy[k, 1])), 'wallId'].to_numpy()[0] - woi_save = [] # - np.savetxt( - data_out, - woi_save, - delimiter=" ", - header=header, - comments="", - ) - # Fill wall_data with variables - wall_data[0, 0] = YYYY[0][i] - wall_data[0, 1] = jday[0][i] - wall_data[0, 2] = hours[i] - wall_data[0, 3] = minu[i] - wall_data[0, 4] = dectime[i] - wall_data[0, 5] = Ta[i] - wall_data[0, 6] = svf[ - int(woisxy[k, 2]), int(woisxy[k, 1]) - ] - wall_data[0, 7:] = temp_all - - # Num format for output file data - woi_numformat = ( - "%d %d %d %d %.5f %.2f %.2f" - + " %.2f" * temp_all.shape[0] - ) - # Open file, add data, save - f_handle = open(data_out, "ab") - np.savetxt(f_handle, wall_data, fmt=woi_numformat) - f_handle.close() - - # Save wall temperature/radiation as NetCDF - if wallNetCDF: - netcdf_output = outputDir + "/walls.nc" - walls_as_netcdf( - voxelTable, - rows, - cols, - met_for_xarray, - i, - dsm, - filepath_dsm, - netcdf_output, - ) - - if hours[i] < 10: - XH = "0" - else: - XH = "" - if minu[i] < 10: - XM = "0" - else: - XM = "" - - if outputTmrt: - saveraster( - gdal_dsm, - outputDir - + "/Tmrt_" - + str(int(YYYY[0, i])) - + "_" - + str(int(DOY[i])) - + "_" - + XH - + str(int(hours[i])) - + XM - + str(int(minu[i])) - + w - + ".tif", - Tmrt, - ) - if outputKup: - saveraster( - gdal_dsm, - outputDir - + "/Kup_" - + str(int(YYYY[0, i])) - + "_" - + str(int(DOY[i])) - + "_" - + XH - + str(int(hours[i])) - + XM - + str(int(minu[i])) - + w - + ".tif", - Kup, - ) - if outputKdown: - saveraster( - gdal_dsm, - outputDir - + "/Kdown_" - + str(int(YYYY[0, i])) - + "_" - + str(int(DOY[i])) - + "_" - + XH - + str(int(hours[i])) - + XM - + str(int(minu[i])) - + w - + ".tif", - Kdown, - ) - if outputLup: - saveraster( - gdal_dsm, - outputDir - + "/Lup_" - + str(int(YYYY[0, i])) - + "_" - + str(int(DOY[i])) - + "_" - + XH - + str(int(hours[i])) - + XM - + str(int(minu[i])) - + w - + ".tif", - Lup, - ) - if outputLdown: - saveraster( - gdal_dsm, - outputDir - + "/Ldown_" - + str(int(YYYY[0, i])) - + "_" - + str(int(DOY[i])) - + "_" - + XH - + str(int(hours[i])) - + XM - + str(int(minu[i])) - + w - + ".tif", - Ldown, - ) - if outputSh: - saveraster( - gdal_dsm, - outputDir - + "/Shadow_" - + str(int(YYYY[0, i])) - + "_" - + str(int(DOY[i])) - + "_" - + XH - + str(int(hours[i])) - + XM - + str(int(minu[i])) - + w - + ".tif", - shadow, - ) - - if outputKdiff: - saveraster( - gdal_dsm, - outputDir - + "/Kdiff_" - + str(int(YYYY[0, i])) - + "_" - + str(int(DOY[i])) - + "_" - + XH - + str(int(hours[i])) - + XM - + str(int(minu[i])) - + w - + ".tif", - dRad, - ) - - # Sky view image of patches - if (anisotropic_sky == 1) & (i == 0) & (not poisxy is None): - for k in range(poisxy.shape[0]): - Lsky_patch_characteristics[:, 2] = patch_characteristics[ - :, k - ] - skyviewimage_out = ( - outputDir + "/POI_" + str(poiname[k]) + ".png" - ) - PolarBarPlot( - Lsky_patch_characteristics, - altitude[0][i], - azimuth[0][i], - "Hemisphere partitioning", - skyviewimage_out, - 0, - 5, - 0, - ) - - # Save files for Tree Planter - if outputTreeplanter: - feedback.setProgressText("Saving files for Tree Planter tool") - # Save DSM - copyfile(filepath_dsm, outputDir + "/DSM.tif") - - # Save CDSM - if usevegdem == 1: - copyfile(filePath_cdsm, outputDir + "/CDSM.tif") - - # Saving settings from SOLWEIG for SOLWEIG1D in TreePlanter - settingsHeader = "UTC, posture, onlyglobal, landcover, anisotropic, cylinder, albedo_walls, albedo_ground, emissivity_walls, emissivity_ground, absK, absL, elevation, patch_option" - settingsFmt = ( - "%i", - "%i", - "%i", - "%i", - "%i", - "%i", - "%1.2f", - "%1.2f", - "%1.2f", - "%1.2f", - "%1.2f", - "%1.2f", - "%1.2f", - "%i", - ) - settingsData = np.array( - [ - [ - utc, - pos, - onlyglobal, - landcover, - anisotropic_sky, - cyl, - albedo_b, - albedo_g, - ewall, - eground, - absK, - absL, - alt, - patch_option, - ] - ] - ) - np.savetxt( - outputDir + "/treeplantersettings.txt", - settingsData, - fmt=settingsFmt, - header=settingsHeader, - delimiter=" ", - ) - - # Copying met file for SpatialTC - copyfile(inputMet, outputDir + "/metforcing.txt") - - tmrtplot = ( - tmrtplot / Ta.__len__() - ) # fix average Tmrt instead of sum, 20191022 - saveraster(gdal_dsm, outputDir + "/Tmrt_average.tif", tmrtplot) - feedback.setProgressText("SOLWEIG: Model calculation finished.") - - return {self.OUTPUT_DIR: outputDir} - - def name(self): - """ - Returns the algorithm name, used for identifying the algorithm. This - string should be fixed for the algorithm, and must not be localised. - The name should be unique within each provider. Names should contain - lowercase alphanumeric characters only and no spaces or other - formatting characters. - """ - return "Outdoor Thermal Comfort: SOLWEIG" - - def displayName(self): - """ - Returns the translated algorithm name, which should be used for any - user-visible display of the algorithm name. - """ - return self.tr("Outdoor Thermal Comfort: SOLWEIG v2025a") - - def group(self): - """ - Returns the name of the group this algorithm belongs to. This string - should be localised. - """ - return self.tr(self.groupId()) - - def groupId(self): - """ - Returns the unique ID of the group this algorithm belongs to. This - string should be fixed for the algorithm, and must not be localised. - The group id should be unique within each provider. Group id should - contain lowercase alphanumeric characters only and no spaces or other - formatting characters. - """ - return "Processor" - - def shortHelpString(self): - return self.tr( - "SOLWEIG (v2025a) is a model which can be used to estimate spatial variations of 3D radiation fluxes and " - "mean radiant temperature (Tmrt) in complex urban settings. The SOLWEIG model follows the same " - "approach commonly adopted to observe Tmrt, with shortwave and longwave radiation fluxes from " - "six directions being individually calculated to derive Tmrt. The model requires a limited number " - "of inputs, such as direct, diffuse and global shortwave radiation, air temperature, relative " - "humidity, urban geometry and geographical information (latitude, longitude and elevation). " - "Additional vegetation and ground cover information can also be used to imporove the estimation of Tmrt.\n" - "\n" - "Tools to generate sky view factors, wall height and aspect etc. is available in the pre-processing past in UMEP\n" - "\n" - "------------\n" - "\n" - "Full manual available via the Help-button." - ) - - def helpUrl(self): - url = "https://umep-docs.readthedocs.io/en/latest/processor/Outdoor%20Thermal%20Comfort%20SOLWEIG.html" - return url - - def tr(self, string): - return QCoreApplication.translate("Processing", string) - - def icon(self): - cmd_folder = Path( - os.path.split(inspect.getfile(inspect.currentframe()))[0] - ).parent - icon = QIcon(str(cmd_folder) + "/icons/icon_solweig.png") - return icon - - def createInstance(self): - return ProcessingSOLWEIGAlgorithm() From e72551dc30fb6413210c45e0ee51165b9b045171 Mon Sep 17 00:00:00 2001 From: "Mael.PHILIPPE" Date: Wed, 27 May 2026 10:46:48 +0200 Subject: [PATCH 10/20] fix/security --- .../Solweig_2025a_calc_forprocessing.py | 5 ++-- .../Solweig_2026a_calc_forprocessing.py | 3 +-- functions/SOLWEIGpython/Solweig_run.py | 26 +++++-------------- functions/URock/Zones.py | 6 ++--- postprocessor/treeplanter_algorithm.py | 12 +-------- 5 files changed, 13 insertions(+), 39 deletions(-) diff --git a/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py b/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py index de979e0..d409821 100644 --- a/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py +++ b/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py @@ -17,15 +17,14 @@ from .TsWaveDelay_2015a import TsWaveDelay_2015a from .Kup_veg_2015a import Kup_veg_2015a -# from .Lside_veg_v2015a import Lside_veg_v2015a -# from .Kside_veg_v2019a import Kside_veg_v2019a + from .Kside_veg_v2022a import Kside_veg_v2022a from ...util.SEBESOLWEIGCommonFiles.Perez_v3 import Perez_v3 from ...util.SEBESOLWEIGCommonFiles.create_patches import create_patches # Anisotropic longwave from .Lcyl_v2022a import Lcyl_v2022a -from .Lside_veg_v2022a import Lside_veg_v2022a +from .Lside_veg import Lside_veg_v2022a from .anisotropic_sky import anisotropic_sky as ani_sky from .patch_radiation import patch_steradians from copy import deepcopy diff --git a/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py index 4844b7d..f5228e4 100644 --- a/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py +++ b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py @@ -19,8 +19,7 @@ from .TsWaveDelay_2015a import TsWaveDelay_2015a from .Kup_veg_2015a import Kup_veg_2015a -# from .Lside_veg_v2015a import Lside_veg_v2015a -# from .Kside_veg_v2019a import Kside_veg_v2019a + from .Kside_veg_v2022a import Kside_veg_v2022a from ...util.SEBESOLWEIGCommonFiles.Perez_v3 import Perez_v3 from ...util.SEBESOLWEIGCommonFiles.create_patches import create_patches diff --git a/functions/SOLWEIGpython/Solweig_run.py b/functions/SOLWEIGpython/Solweig_run.py index 225ab28..7d1c40c 100644 --- a/functions/SOLWEIGpython/Solweig_run.py +++ b/functions/SOLWEIGpython/Solweig_run.py @@ -56,21 +56,6 @@ pass -# import numpy as np -# from .daylen import daylen -# from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import clearnessindex_2013b -# from ...util.SEBESOLWEIGCommonFiles.diffusefraction import diffusefraction -# from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_13 import shadowingfunction_wallheight_13 -# from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_23 import shadowingfunction_wallheight_23 -# from .gvf_2018a import gvf_2018a -# from .cylindric_wedge import cylindric_wedge -# from .TsWaveDelay_2015a import TsWaveDelay_2015a -# from .Kup_veg_2015a import Kup_veg_2015a -# # from .Lside_veg_v2015a import Lside_veg_v2015a -# # from .Kside_veg_v2019a import Kside_veg_v2019a -# from .Kside_veg_v2022a import Kside_veg_v2022a -# from ...util.SEBESOLWEIGCommonFiles.Perez_v3 import Perez_v3 -# from ...util.SEBESOLWEIGCommonFiles.create_patches import create_patches def solweig_run(configPath, feedback): @@ -86,10 +71,9 @@ def solweig_run(configPath, feedback): # Load parameters settings for SOLWEIG with open(configDict["para_json_path"], "r") as jsn: param = json.load(jsn) - # reading variables from config and parameters that is not yet presented - standAlone = int(configDict["standalone"]) + standAlone = int(configDict["standalone"]) cyl = int(configDict["cyl"]) albedo_b = param["Albedo"]["Effective"]["Value"]["Walls"] ewall = param["Emissivity"]["Value"]["Walls"] @@ -645,7 +629,9 @@ def solweig_run(configPath, feedback): # Import shadow matrices (Anisotropic sky) anisotropic_sky = int(configDict["aniso"]) if anisotropic_sky == 1: # UseAniso - data = torch.load(configDict["input_aniso"]).to(device) + data = torch.load( + configDict["input_aniso"], map_location=device, weights_only=True + ) shmat = data["shadowmat"] vegshmat = data["vegshadowmat"] vbshvegshmat = data["vbshmat"] @@ -753,7 +739,9 @@ def solweig_run(configPath, feedback): # Import data for wall temperature parameterization TODO: fix for standalone wallScheme = int(configDict["wallscheme"]) if wallScheme == 1: - wallData = torch.load(configDict["input_wall"]).to(device) + wallData = torch.load( + configDict["input_wall"], map_location=device, weights_only=True + ) voxelMaps = wallData["voxelId"] voxelTable = wallData["voxelTable"] # Get wall type from standalone diff --git a/functions/URock/Zones.py b/functions/URock/Zones.py index 4de856f..cf27092 100644 --- a/functions/URock/Zones.py +++ b/functions/URock/Zones.py @@ -166,8 +166,7 @@ def displacementZones2(cursor, upwindWithPropTable, srid, prefix=PREFIX_NAME): } # Create the zone from the half ellipse and the densified line and then join missing columns - cursor.execute( - safe(";").join([f""" + cursor.execute(safe(";").join([f""" {DataUtil.createIndex(tableName=ZonePoints[z], fieldName=UPWIND_FACADE_FIELD, isSpatial=False)} @@ -194,8 +193,7 @@ def displacementZones2(cursor, upwindWithPropTable, srid, prefix=PREFIX_NAME): FROM {ZonePolygons[z]} AS a LEFT JOIN {upwindWithPropTable} AS b ON a.{UPWIND_FACADE_FIELD} = b.{UPWIND_FACADE_FIELD} WHERE ST_AREA(a.{GEOM_FIELD}) > 0 AND {whereCond[z]}; - """ for z in variablesNames.index]) - ) # nosec B608 # nosec B608 # nosec B608 + """ for z in variablesNames.index])) # nosec B608 # nosec B608 if not DEBUG: # Drop intermediate tables diff --git a/postprocessor/treeplanter_algorithm.py b/postprocessor/treeplanter_algorithm.py index 616e00c..5c9136f 100644 --- a/postprocessor/treeplanter_algorithm.py +++ b/postprocessor/treeplanter_algorithm.py @@ -75,25 +75,19 @@ Solweig_2015a_metdata_noload, ) -# from ..util.SEBESOLWEIGCommonFiles import Solweig_v2015_metdata_noload as metload from ..util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import ( clearnessindex_2013b, ) from ..functions.TreeGenerator import makevegdems as makevegdems -# from ..functions.TreePlanter.SOLWEIG.shadowingfunction_wallheight_23 import shadowingfunction_wallheight_23 from ..util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_23 import ( shadowingfunction_wallheight_23, ) -# from ..functions.TreePlanter.SOLWEIG1D import Solweig1D_2019a_calc as so -from ..functions.wallalgorithms import findwalls -# from ..functions.TreePlanter.SOLWEIG.misc import saveraster from ..util.misc import saveraster -# Import functions and classes for Tree planter from ..functions.TreePlanter.TreePlanter import TreePlanterPrepare from ..functions.TreePlanter.TreePlanter import TreePlanterHillClimber from ..functions.TreePlanter.TreePlanter.TreePlanterClasses import ( @@ -105,13 +99,9 @@ ) from ..functions.TreePlanter.TreePlanter import GreedyAlgorithm -# from ..functions.TreePlanter.SOLWEIG1D.SOLWEIG_1D import tmrt_1d_fun from ..functions.TreePlanter.SOLWEIG1D.SOLWEIG1D_2023a import tmrt_1d_fun -# from ..functions.TreePlanter.treeplanterclasses import Treedata -# from ..functions.TreePlanter.treeplanterclasses import Regional_groups -# from ..functions.TreePlanter.treeplanterclasses import ClippedInputdata -# from ..functions.TreePlanter.treeplanterclasses import Treerasters + class ProcessingTreePlanterAlgorithm(QgsProcessingAlgorithm): From e8147591f68c1b750a3102bdb1d89001c03e7cd1 Mon Sep 17 00:00:00 2001 From: "Mael.PHILIPPE" Date: Wed, 27 May 2026 15:16:08 +0200 Subject: [PATCH 11/20] Merged some fixes from eliott --- functions/SOLWEIGpython/Lside_veg_v2015a.py | 215 ++++++ .../Solweig_2021a_calc_forprocessing.py | 586 --------------- .../Solweig_2022a_calc_forprocessing.py | 704 ------------------ .../Solweig_2026a_calc_forprocessing.py | 61 +- functions/SOLWEIGpython/Solweig_run.py | 10 +- functions/SOLWEIGpython/ground_surface.py | 36 +- postprocessor/treeplanter_algorithm.py | 2 - processor/configsolweig.ini | 2 - processor/solweig_algorithm.py | 4 +- 9 files changed, 279 insertions(+), 1341 deletions(-) create mode 100644 functions/SOLWEIGpython/Lside_veg_v2015a.py delete mode 100644 functions/SOLWEIGpython/Solweig_2021a_calc_forprocessing.py delete mode 100644 functions/SOLWEIGpython/Solweig_2022a_calc_forprocessing.py diff --git a/functions/SOLWEIGpython/Lside_veg_v2015a.py b/functions/SOLWEIGpython/Lside_veg_v2015a.py new file mode 100644 index 0000000..6700501 --- /dev/null +++ b/functions/SOLWEIGpython/Lside_veg_v2015a.py @@ -0,0 +1,215 @@ +from __future__ import absolute_import +import numpy as np +from .Lvikt_veg import Lvikt_veg + + +def Lside_veg_v2015a( + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + azimuth, + altitude, + Ta, + Tw, + SBC, + ewall, + Ldown, + esky, + t, + F_sh, + CI, + LupE, + LupS, + LupW, + LupN, +): + + # This m-file is the current one that estimates L from the four cardinal + # points 20100414 + + # Building height angle from svf + svfalfaE = np.arcsin(np.exp((np.log(1 - svfE)) / 2)) + svfalfaS = np.arcsin(np.exp((np.log(1 - svfS)) / 2)) + svfalfaW = np.arcsin(np.exp((np.log(1 - svfW)) / 2)) + svfalfaN = np.arcsin(np.exp((np.log(1 - svfN)) / 2)) + + vikttot = 4.4897 + aziW = azimuth + t + aziN = azimuth - 90 + t + aziE = azimuth - 180 + t + aziS = azimuth - 270 + t + + F_sh = 2 * F_sh - 1 # (cylindric_wedge scaled 0-1) + + c = 1 - CI + Lsky_allsky = esky * SBC * ((Ta + 273.15) ** 4) * (1 - c) + c * SBC * ( + (Ta + 273.15) ** 4 + ) + + # Least + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfE, svfEveg, svfEaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = np.arctan(svfalfaE) + betaB = np.arctan(np.tan((svfalfaE) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB + # betasun = np.arctan(0.5*np.tan(svfalfaE)*(1+F_sh)) #TODO This should + # be considered in future versions + if (azimuth > (180 - t)) and (azimuth <= (360 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziE * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) + else: + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + Lsky = ((svfE + svfEveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupE * 0.5 + Lrefl = (Ldown + LupE) * (viktrefl) * (1 - ewall) * 0.5 + Least = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl + # viktveg viktwall viktsky + + # Lsouth + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfS, svfSveg, svfSaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = np.arctan(svfalfaS) + betaB = np.arctan(np.tan((svfalfaS) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB + # betasun = np.arctan(0.5*np.tan(svfalfaS)*(1+F_sh)) + if (azimuth <= (90 - t)) or (azimuth > (270 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziS * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) + else: + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + Lsky = ((svfS + svfSveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupS * 0.5 + Lrefl = (Ldown + LupS) * (viktrefl) * (1 - ewall) * 0.5 + Lsouth = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl + # viktveg viktwall viktsky + + # Lwest + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfW, svfWveg, svfWaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = np.arctan(svfalfaW) + betaB = np.arctan(np.tan((svfalfaW) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB + # betasun = np.arctan(0.5*np.tan(svfalfaW)*(1+F_sh)) + if (azimuth > (360 - t)) or (azimuth <= (180 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziW * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) + else: + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + Lsky = ((svfW + svfWveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupW * 0.5 + Lrefl = (Ldown + LupW) * (viktrefl) * (1 - ewall) * 0.5 + Lwest = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl + # viktveg viktwall viktsky + + # Lnorth + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfN, svfNveg, svfNaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = np.arctan(svfalfaN) + betaB = np.arctan(np.tan((svfalfaN) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB + # betasun = np.arctan(0.5*np.tan(svfalfaN)*(1+F_sh)) + if (azimuth > (90 - t)) and (azimuth <= (270 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziN * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) + else: + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + Lsky = ((svfN + svfNveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupN * 0.5 + Lrefl = (Ldown + LupN) * (viktrefl) * (1 - ewall) * 0.5 + Lnorth = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl + # viktveg viktwall viktsky + + return Least, Lsouth, Lwest, Lnorth diff --git a/functions/SOLWEIGpython/Solweig_2021a_calc_forprocessing.py b/functions/SOLWEIGpython/Solweig_2021a_calc_forprocessing.py deleted file mode 100644 index 74fbd47..0000000 --- a/functions/SOLWEIGpython/Solweig_2021a_calc_forprocessing.py +++ /dev/null @@ -1,586 +0,0 @@ -from __future__ import absolute_import - -import numpy as np -from .daylen import daylen -from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import ( - clearnessindex_2013b, -) -from ...util.SEBESOLWEIGCommonFiles.diffusefraction import diffusefraction -from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_13 import ( - shadowingfunction_wallheight_13, -) -from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_23 import ( - shadowingfunction_wallheight_23, -) -from .gvf_2018a import gvf_2018a -from .cylindric_wedge import cylindric_wedge -from .TsWaveDelay_2015a import TsWaveDelay_2015a -from .Kup_veg_2015a import Kup_veg_2015a -from .Lside_veg_v2015a import Lside_veg_v2015a -from .Kside_veg_v2019a import Kside_veg_v2019a -from ...util.SEBESOLWEIGCommonFiles.Perez_v3 import Perez_v3 - - -def Solweig_2021a_calc( - i, - dsm, - scale, - rows, - cols, - svf, - svfN, - svfW, - svfE, - svfS, - svfveg, - svfNveg, - svfEveg, - svfSveg, - svfWveg, - svfaveg, - svfEaveg, - svfSaveg, - svfWaveg, - svfNaveg, - vegdem, - vegdem2, - albedo_b, - absK, - absL, - ewall, - Fside, - Fup, - Fcyl, - altitude, - azimuth, - zen, - jday, - usevegdem, - onlyglobal, - buildings, - location, - psi, - landcover, - lc_grid, - dectime, - altmax, - dirwalls, - walls, - cyl, - elvis, - Ta, - RH, - radG, - radD, - radI, - P, - amaxvalue, - bush, - Twater, - TgK, - Tstart, - alb_grid, - emis_grid, - TgK_wall, - Tstart_wall, - TmaxLST, - TmaxLST_wall, - first, - second, - svfalfa, - svfbuveg, - firstdaytime, - timeadd, - timestepdec, - Tgmap1, - Tgmap1E, - Tgmap1S, - Tgmap1W, - Tgmap1N, - CI, - TgOut1, - diffsh, - ani, -): - - # This is the core function of the SOLWEIG model - # 2016-Aug-28 - # Fredrik Lindberg, fredrikl@gvc.gu.se - # Goteborg Urban Climate Group - # Gothenburg University - # - # Input variables: - # dsm = digital surface model - # scale = height to pixel size (2m pixel gives scale = 0.5) - # header = ESRI Ascii Grid header - # sizey,sizex = no. of pixels in x and y - # svf,svfN,svfW,svfE,svfS = SVFs for building and ground - # svfveg,svfNveg,svfEveg,svfSveg,svfWveg = Veg SVFs blocking sky - # svfaveg,svfEaveg,svfSaveg,svfWaveg,svfNaveg = Veg SVFs blocking buildings - # vegdem = Vegetation canopy DSM - # vegdem2 = Vegetation trunk zone DSM - # albedo_b = buildings - # absK = human absorption coefficient for shortwave radiation - # absL = human absorption coefficient for longwave radiation - # ewall = Emissivity of building walls - # Fside = The angular factors between a person and the surrounding surfaces - # Fup = The angular factors between a person and the surrounding surfaces - # altitude = Sun altitude (degree) - # azimuth = Sun azimuth (degree) - # zen = Sun zenith angle (radians) - # jday = day of year - # usevegdem = use vegetation scheme - # onlyglobal = calculate dir and diff from global - # buildings = Boolena grid to identify building pixels - # location = geographic location - # height = height of measurements point - # psi = 1 - Transmissivity of shortwave through vegetation - # output = output settings - # fileformat = fileformat of output grids - # landcover = use landcover scheme !!!NEW IN 2015a!!! - # sensorheight = Sensorheight of wind sensor - # lc_grid = grid with landcoverclasses - # lc_class = table with landcover properties - # dectime = decimal time - # altmax = maximum sun altitude - # dirwalls = aspect of walls - # walls = one pixel row outside building footprints - # cyl = consider man as cylinder instead of cude - - # # # Core program start # # # - # Instrument offset in degrees - t = 0.0 - - # Stefan Bolzmans Constant - SBC = 5.67051e-8 - - # Find sunrise decimal hour - new from 2014a - _, _, _, SNUP = daylen(jday, location["latitude"]) - - # Vapor pressure - ea = 6.107 * 10 ** ((7.5 * Ta) / (237.3 + Ta)) * (RH / 100.0) - - # Determination of clear - sky emissivity from Prata (1996) - msteg = 46.5 * (ea / (Ta + 273.15)) - esky = ( - 1 - (1 + msteg) * np.exp(-((1.2 + 3.0 * msteg) ** 0.5)) - ) + elvis # -0.04 old error from Jonsson et al.2006 - - if altitude > 0: # # # # # # DAYTIME # # # # # # - # Clearness Index on Earth's surface after Crawford and Dunchon (1999) with a correction - # factor for low sun elevations after Lindberg et al.(2008) - I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( - zen, jday, Ta, RH / 100.0, radG, location, P - ) - if (CI > 1) or (CI == np.inf): - CI = 1 - - # Estimation of radD and radI if not measured after Reindl et al.(1990) - if onlyglobal == 1: - I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( - zen, jday, Ta, RH / 100.0, radG, location, P - ) - if (CI > 1) or (CI == np.inf): - CI = 1 - - radI, radD = diffusefraction(radG, altitude, Kt, Ta, RH) - - # Diffuse Radiation - # Anisotropic Diffuse Radiation after Perez et al. 1993 - if ani == 1: - patchchoice = 1 - zenDeg = zen * (180 / np.pi) - lv = Perez_v3( - zenDeg, azimuth, radD, radI, jday, patchchoice - ) # Relative luminance - aniLum = np.zeros((rows, cols)) - for idx in range(0, 145): - aniLum = ( - aniLum + diffsh[:, :, idx] * lv[0][idx][2] - ) # Total relative luminance from sky into each cell - - dRad = ( - aniLum * radD - ) # Total diffuse radiation from sky into each cell - else: - dRad = radD * svfbuveg - lv = 0 - - # Shadow images - if usevegdem == 1: - vegsh, sh, _, wallsh, wallsun, wallshve, _, facesun = ( - shadowingfunction_wallheight_23( - dsm, - vegdem, - vegdem2, - azimuth, - altitude, - scale, - amaxvalue, - bush, - walls, - dirwalls * np.pi / 180.0, - ) - ) - shadow = sh - (1 - vegsh) * (1 - psi) - else: - sh, wallsh, wallsun, facesh, facesun = ( - shadowingfunction_wallheight_13( - dsm, - azimuth, - altitude, - scale, - walls, - dirwalls * np.pi / 180.0, - ) - ) - shadow = sh - - # # # Surface temperature parameterisation during daytime # # # # - # new using max sun alt.instead of dfm - Tgamp = (TgK * altmax - Tstart) + Tstart - Tgampwall = (TgK_wall * altmax - (Tstart_wall)) + (Tstart_wall) - Tg = ( - Tgamp - * np.sin( - ( - ((dectime - np.floor(dectime)) - SNUP / 24) - / (TmaxLST / 24 - SNUP / 24) - ) - * np.pi - / 2 - ) - + Tstart - ) # 2015 a, based on max sun altitude - Tgwall = Tgampwall * np.sin( - ( - ((dectime - np.floor(dectime)) - SNUP / 24) - / (TmaxLST_wall / 24 - SNUP / 24) - ) - * np.pi - / 2 - ) + ( - Tstart_wall - ) # 2015a, based on max sun altitude - - if Tgwall < 0: # temporary for removing low Tg during morning 20130205 - # Tg = 0 - Tgwall = 0 - - # New estimation of Tg reduction for non - clear situation based on Reindl et al.1990 - radI0, _ = diffusefraction(I0, altitude, 1.0, Ta, RH) - corr = ( - 0.1473 * np.log(90 - (zen / np.pi * 180)) + 0.3454 - ) # 20070329 correction of lat, Lindberg et al. 2008 - CI_Tg = (radI / radI0) + (1 - corr) - if (CI_Tg > 1) or (CI_Tg == np.inf): - CI_Tg = 1 - Tg = Tg * CI_Tg # new estimation - Tgwall = Tgwall * CI_Tg - if landcover == 1: - Tg[Tg < 0] = ( - 0 # temporary for removing low Tg during morning 20130205 - ) - - # # # # Ground View Factors # # # # - ( - gvfLup, - gvfalb, - gvfalbnosh, - gvfLupE, - gvfalbE, - gvfalbnoshE, - gvfLupS, - gvfalbS, - gvfalbnoshS, - gvfLupW, - gvfalbW, - gvfalbnoshW, - gvfLupN, - gvfalbN, - gvfalbnoshN, - gvfSum, - gvfNorm, - ) = gvf_2018a( - wallsun, - walls, - buildings, - scale, - shadow, - first, - second, - dirwalls, - Tg, - Tgwall, - Ta, - emis_grid, - ewall, - alb_grid, - SBC, - albedo_b, - rows, - cols, - Twater, - lc_grid, - landcover, - ) - - # # # # Lup, daytime # # # # - # Surface temperature wave delay - new as from 2014a - Lup, timeaddnotused, Tgmap1 = TsWaveDelay_2015a( - gvfLup, firstdaytime, timeadd, timestepdec, Tgmap1 - ) - LupE, timeaddnotused, Tgmap1E = TsWaveDelay_2015a( - gvfLupE, firstdaytime, timeadd, timestepdec, Tgmap1E - ) - LupS, timeaddnotused, Tgmap1S = TsWaveDelay_2015a( - gvfLupS, firstdaytime, timeadd, timestepdec, Tgmap1S - ) - LupW, timeaddnotused, Tgmap1W = TsWaveDelay_2015a( - gvfLupW, firstdaytime, timeadd, timestepdec, Tgmap1W - ) - LupN, timeaddnotused, Tgmap1N = TsWaveDelay_2015a( - gvfLupN, firstdaytime, timeadd, timestepdec, Tgmap1N - ) - - # # For Tg output in POIs - TgTemp = Tg * shadow + Ta - TgOut, timeadd, TgOut1 = TsWaveDelay_2015a( - TgTemp, firstdaytime, timeadd, timestepdec, TgOut1 - ) # timeadd only here v2021a - - # Building height angle from svf - F_sh = cylindric_wedge( - zen, svfalfa, rows, cols - ) # Fraction shadow on building walls based on sun alt and svf - F_sh[np.isnan(F_sh)] = 0.5 - - # # # # # # # Calculation of shortwave daytime radiative fluxes # # # # # # # - Kdown = ( - radI * shadow * np.sin(altitude * (np.pi / 180)) - + dRad - + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) - ) # *sin(altitude(i) * (pi / 180)) - - # Kdown = radI * shadow * np.sin(altitude * (np.pi / 180)) + radD * svfbuveg + albedo_b * (1 - svfbuveg) * \ - # (radG * (1 - F_sh) + radD * F_sh) # *sin(altitude(i) * (pi / 180)) - - Kup, KupE, KupS, KupW, KupN = Kup_veg_2015a( - radI, - radD, - radG, - altitude, - svfbuveg, - albedo_b, - F_sh, - gvfalb, - gvfalbE, - gvfalbS, - gvfalbW, - gvfalbN, - gvfalbnosh, - gvfalbnoshE, - gvfalbnoshS, - gvfalbnoshW, - gvfalbnoshN, - ) - - Keast, Ksouth, Kwest, Knorth, KsideI, KsideD = Kside_veg_v2019a( - radI, - radD, - radG, - shadow, - svfS, - svfW, - svfN, - svfE, - svfEveg, - svfSveg, - svfWveg, - svfNveg, - azimuth, - altitude, - psi, - t, - albedo_b, - F_sh, - KupE, - KupS, - KupW, - KupN, - cyl, - lv, - ani, - diffsh, - rows, - cols, - ) - - firstdaytime = 0 - - else: # # # # # # # NIGHTTIME # # # # # # # # - - Tgwall = 0 - # CI_Tg = -999 # F_sh = [] - - # Nocturnal K fluxes set to 0 - Knight = np.zeros((rows, cols)) - Kdown = np.zeros((rows, cols)) - Kwest = np.zeros((rows, cols)) - Kup = np.zeros((rows, cols)) - Keast = np.zeros((rows, cols)) - Ksouth = np.zeros((rows, cols)) - Knorth = np.zeros((rows, cols)) - KsideI = np.zeros((rows, cols)) - KsideD = np.zeros((rows, cols)) - F_sh = np.zeros((rows, cols)) - Tg = np.zeros((rows, cols)) - shadow = np.zeros((rows, cols)) - - # # # # Lup # # # # - Lup = SBC * emis_grid * ((Knight + Ta + Tg + 273.15) ** 4) - if landcover == 1: - Lup[lc_grid == 3] = ( - SBC * 0.98 * (Twater + 273.15) ** 4 - ) # nocturnal Water temp - - LupE = Lup - LupS = Lup - LupW = Lup - LupN = Lup - - # # For Tg output in POIs - TgOut = Ta + Tg - - I0 = 0 - timeadd = 0 - firstdaytime = 1 - - # # # # Ldown # # # # - Ldown = ( - (svf + svfveg - 1) * esky * SBC * ((Ta + 273.15) ** 4) - + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) - + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) - + (2 - svf - svfveg) * (1 - ewall) * esky * SBC * ((Ta + 273.15) ** 4) - ) # Jonsson et al.(2006) - # Ldown = Ldown - 25 # Shown by Jonsson et al.(2006) and Duarte et al.(2006) - - if CI < 0.95: # non - clear conditions - c = 1 - CI - Ldown = Ldown * (1 - c) + c * ( - (svf + svfveg - 1) * SBC * ((Ta + 273.15) ** 4) - + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) - + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) - + (2 - svf - svfveg) * (1 - ewall) * SBC * ((Ta + 273.15) ** 4) - ) # NOT REALLY TESTED!!! BUT MORE CORRECT? - - # # # # Lside # # # # - Least, Lsouth, Lwest, Lnorth = Lside_veg_v2015a( - svfS, - svfW, - svfN, - svfE, - svfEveg, - svfSveg, - svfWveg, - svfNveg, - svfEaveg, - svfSaveg, - svfWaveg, - svfNaveg, - azimuth, - altitude, - Ta, - Tgwall, - SBC, - ewall, - Ldown, - esky, - t, - F_sh, - CI, - LupE, - LupS, - LupW, - LupN, - ) - - # # # # Calculation of radiant flux density and Tmrt # # # # - if ( - cyl == 1 and ani == 1 - ): # Human body considered as a cylinder with Perez et al. (1993) - Sstr = absK * ( - (KsideI + KsideD) * Fcyl - + (Kdown + Kup) * Fup - + (Knorth + Keast + Ksouth + Kwest) * Fside - ) + absL * ( - Ldown * Fup - + Lup * Fup - + Lnorth * Fside - + Least * Fside - + Lsouth * Fside - + Lwest * Fside - ) - elif ( - cyl == 1 and ani == 0 - ): # Human body considered as a cylinder with isotropic all-sky diffuse - Sstr = absK * ( - KsideI * Fcyl - + (Kdown + Kup) * Fup - + (Knorth + Keast + Ksouth + Kwest) * Fside - ) + absL * ( - Ldown * Fup - + Lup * Fup - + Lnorth * Fside - + Least * Fside - + Lsouth * Fside - + Lwest * Fside - ) - else: # Human body considered as a standing cube - Sstr = absK * ( - (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside - ) + absL * ( - Ldown * Fup - + Lup * Fup - + Lnorth * Fside - + Least * Fside - + Lsouth * Fside - + Lwest * Fside - ) - - Tmrt = np.sqrt(np.sqrt((Sstr / (absL * SBC)))) - 273.2 - - return ( - Tmrt, - Kdown, - Kup, - Ldown, - Lup, - Tg, - ea, - esky, - I0, - CI, - shadow, - firstdaytime, - timestepdec, - timeadd, - Tgmap1, - Tgmap1E, - Tgmap1S, - Tgmap1W, - Tgmap1N, - Keast, - Ksouth, - Kwest, - Knorth, - Least, - Lsouth, - Lwest, - Lnorth, - KsideI, - TgOut1, - TgOut, - radI, - radD, - ) diff --git a/functions/SOLWEIGpython/Solweig_2022a_calc_forprocessing.py b/functions/SOLWEIGpython/Solweig_2022a_calc_forprocessing.py deleted file mode 100644 index f3e2353..0000000 --- a/functions/SOLWEIGpython/Solweig_2022a_calc_forprocessing.py +++ /dev/null @@ -1,704 +0,0 @@ -from __future__ import absolute_import - -import numpy as np -from .daylen import daylen -from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import ( - clearnessindex_2013b, -) -from ...util.SEBESOLWEIGCommonFiles.diffusefraction import diffusefraction -from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_13 import ( - shadowingfunction_wallheight_13, -) -from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_23 import ( - shadowingfunction_wallheight_23, -) -from .gvf_2018a import gvf_2018a -from .cylindric_wedge import cylindric_wedge -from .TsWaveDelay_2015a import TsWaveDelay_2015a -from .Kup_veg_2015a import Kup_veg_2015a - -# from .Lside_veg_v2015a import Lside_veg_v2015a -# from .Kside_veg_v2019a import Kside_veg_v2019a -from .Kside_veg_v2022a import Kside_veg_v2022a -from ...util.SEBESOLWEIGCommonFiles.Perez_v3 import Perez_v3 -from ...util.SEBESOLWEIGCommonFiles.create_patches import create_patches - -# Anisotropic longwave -from .Lcyl_v2022a import Lcyl_v2022a -from .Lside_veg_v2022a import Lside_veg_v2022a -from copy import deepcopy - - -def Solweig_2022a_calc( - i, - dsm, - scale, - rows, - cols, - svf, - svfN, - svfW, - svfE, - svfS, - svfveg, - svfNveg, - svfEveg, - svfSveg, - svfWveg, - svfaveg, - svfEaveg, - svfSaveg, - svfWaveg, - svfNaveg, - vegdem, - vegdem2, - albedo_b, - absK, - absL, - ewall, - Fside, - Fup, - Fcyl, - altitude, - azimuth, - zen, - jday, - usevegdem, - onlyglobal, - buildings, - location, - psi, - landcover, - lc_grid, - dectime, - altmax, - dirwalls, - walls, - cyl, - elvis, - Ta, - RH, - radG, - radD, - radI, - P, - amaxvalue, - bush, - Twater, - TgK, - Tstart, - alb_grid, - emis_grid, - TgK_wall, - Tstart_wall, - TmaxLST, - TmaxLST_wall, - first, - second, - svfalfa, - svfbuveg, - firstdaytime, - timeadd, - timestepdec, - Tgmap1, - Tgmap1E, - Tgmap1S, - Tgmap1W, - Tgmap1N, - CI, - TgOut1, - diffsh, - shmat, - vegshmat, - vbshvegshmat, - anisotropic_sky, - asvf, - patch_option, -): - - # def Solweig_2021a_calc(i, dsm, scale, rows, cols, svf, svfN, svfW, svfE, svfS, svfveg, svfNveg, svfEveg, svfSveg, - # svfWveg, svfaveg, svfEaveg, svfSaveg, svfWaveg, svfNaveg, vegdem, vegdem2, albedo_b, absK, absL, - # ewall, Fside, Fup, Fcyl, altitude, azimuth, zen, jday, usevegdem, onlyglobal, buildings, location, psi, - # landcover, lc_grid, dectime, altmax, dirwalls, walls, cyl, elvis, Ta, RH, radG, radD, radI, P, - # amaxvalue, bush, Twater, TgK, Tstart, alb_grid, emis_grid, TgK_wall, Tstart_wall, TmaxLST, - # TmaxLST_wall, first, second, svfalfa, svfbuveg, firstdaytime, timeadd, timestepdec, Tgmap1, - # Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, CI, TgOut1, diffsh, ani): - - # This is the core function of the SOLWEIG model - # 2016-Aug-28 - # Fredrik Lindberg, fredrikl@gvc.gu.se - # Goteborg Urban Climate Group - # Gothenburg University - # - # Input variables: - # dsm = digital surface model - # scale = height to pixel size (2m pixel gives scale = 0.5) - # svf,svfN,svfW,svfE,svfS = SVFs for building and ground - # svfveg,svfNveg,svfEveg,svfSveg,svfWveg = Veg SVFs blocking sky - # svfaveg,svfEaveg,svfSaveg,svfWaveg,svfNaveg = Veg SVFs blocking buildings - # vegdem = Vegetation canopy DSM - # vegdem2 = Vegetation trunk zone DSM - # albedo_b = building wall albedo - # absK = human absorption coefficient for shortwave radiation - # absL = human absorption coefficient for longwave radiation - # ewall = Emissivity of building walls - # Fside = The angular factors between a person and the surrounding surfaces - # Fup = The angular factors between a person and the surrounding surfaces - # Fcyl = The angular factors between a culidric person and the surrounding surfaces - # altitude = Sun altitude (degree) - # azimuth = Sun azimuth (degree) - # zen = Sun zenith angle (radians) - # jday = day of year - # usevegdem = use vegetation scheme - # onlyglobal = calculate dir and diff from global shortwave (Reindl et al. 1990) - # buildings = Boolena grid to identify building pixels - # location = geographic location - # height = height of measurements point (center of gravity of human) - # psi = 1 - Transmissivity of shortwave through vegetation - # landcover = use landcover scheme !!!NEW IN 2015a!!! - # lc_grid = grid with landcoverclasses - # lc_class = table with landcover properties - # dectime = decimal time - # altmax = maximum sun altitude - # dirwalls = aspect of walls - # walls = one pixel row outside building footprint. height of building walls - # cyl = consider man as cylinder instead of cude - # elvis = dummy - # Ta = air temp - # RH - # radG = global radiation - # radD = diffuse - # radI = direct - # P = pressure - # amaxvalue = max height of buildings - # bush = grid representing bushes - # Twater = temperature of water (daily) - # TgK, Tstart, TgK_wall, Tstart_wall, TmaxLST,TmaxLST_wall, - # alb_grid, emis_grid = albedo and emmissivity on ground - # first, second = conneted to old Ts model (source area based on Smidt et al.) - # svfalfa = SVF recalculated to angle - # svfbuveg = complete SVF - # firstdaytime, timeadd, timestepdec, Tgmap1, Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, - # CI = Clearness index - # TgOut1 = old Ts model - # diffsh, ani = Used in anisotrpic models (Wallenberg et al. 2019, 2022) - - # # # Core program start # # # - # Instrument offset in degrees - t = 0.0 - - # Stefan Bolzmans Constant - SBC = 5.67051e-8 - - # Find sunrise decimal hour - new from 2014a - _, _, _, SNUP = daylen(jday, location["latitude"]) - - # Vapor pressure - ea = 6.107 * 10 ** ((7.5 * Ta) / (237.3 + Ta)) * (RH / 100.0) - - # Determination of clear - sky emissivity from Prata (1996) - msteg = 46.5 * (ea / (Ta + 273.15)) - esky = ( - 1 - (1 + msteg) * np.exp(-((1.2 + 3.0 * msteg) ** 0.5)) - ) + elvis # -0.04 old error from Jonsson et al.2006 - - if altitude > 0: # # # # # # DAYTIME # # # # # # - # Clearness Index on Earth's surface after Crawford and Dunchon (1999) with a correction - # factor for low sun elevations after Lindberg et al.(2008) - I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( - zen, jday, Ta, RH / 100.0, radG, location, P - ) - if (CI > 1) or (CI == np.inf): - CI = 1 - - # Estimation of radD and radI if not measured after Reindl et al.(1990) - if onlyglobal == 1: - I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( - zen, jday, Ta, RH / 100.0, radG, location, P - ) - if (CI > 1) or (CI == np.inf): - CI = 1 - - radI, radD = diffusefraction(radG, altitude, Kt, Ta, RH) - - # Diffuse Radiation - # Anisotropic Diffuse Radiation after Perez et al. 1993 - if anisotropic_sky == 1: - patchchoice = 1 - zenDeg = zen * (180 / np.pi) - # Relative luminance - lv, pc_, pb_ = Perez_v3( - zenDeg, azimuth, radD, radI, jday, patchchoice, patch_option - ) - # Total relative luminance from sky, i.e. from each patch, into each cell - aniLum = np.zeros((rows, cols)) - for idx in range(lv.shape[0]): - aniLum += diffsh[:, :, idx] * lv[idx, 2] - - dRad = ( - aniLum * radD - ) # Total diffuse radiation from sky into each cell - else: - dRad = radD * svfbuveg - patchchoice = 1 - # zenDeg = zen*(180/np.pi) - lv = None - # lv, pc_, pb_ = Perez_v3(zenDeg, azimuth, radD, radI, jday, patchchoice, patch_option) # Relative luminance - - # Shadow images - if usevegdem == 1: - vegsh, sh, _, wallsh, wallsun, wallshve, _, facesun = ( - shadowingfunction_wallheight_23( - dsm, - vegdem, - vegdem2, - azimuth, - altitude, - scale, - amaxvalue, - bush, - walls, - dirwalls * np.pi / 180.0, - ) - ) - shadow = sh - (1 - vegsh) * (1 - psi) - else: - sh, wallsh, wallsun, facesh, facesun = ( - shadowingfunction_wallheight_13( - dsm, - azimuth, - altitude, - scale, - walls, - dirwalls * np.pi / 180.0, - ) - ) - shadow = sh - - # # # Surface temperature parameterisation during daytime # # # # - # new using max sun alt.instead of dfm - # Tgamp = (TgK * altmax - Tstart) + Tstart # Old - Tgamp = TgK * altmax + Tstart # Fixed 2021 - # Tgampwall = (TgK_wall * altmax - (Tstart_wall)) + (Tstart_wall) # Old - Tgampwall = TgK_wall * altmax + Tstart_wall - Tg = Tgamp * np.sin( - ( - ((dectime - np.floor(dectime)) - SNUP / 24) - / (TmaxLST / 24 - SNUP / 24) - ) - * np.pi - / 2 - ) # 2015 a, based on max sun altitude - Tgwall = Tgampwall * np.sin( - ( - ((dectime - np.floor(dectime)) - SNUP / 24) - / (TmaxLST_wall / 24 - SNUP / 24) - ) - * np.pi - / 2 - ) # 2015a, based on max sun altitude - - if Tgwall < 0: # temporary for removing low Tg during morning 20130205 - # Tg = 0 - Tgwall = 0 - - # New estimation of Tg reduction for non - clear situation based on Reindl et al.1990 - radI0, _ = diffusefraction(I0, altitude, 1.0, Ta, RH) - corr = ( - 0.1473 * np.log(90 - (zen / np.pi * 180)) + 0.3454 - ) # 20070329 correction of lat, Lindberg et al. 2008 - CI_Tg = (radG / radI0) + (1 - corr) - if (CI_Tg > 1) or (CI_Tg == np.inf): - CI_Tg = 1 - - deg2rad = np.pi / 180 - radG0 = radI0 * (np.sin(altitude * deg2rad)) + _ - CI_TgG = (radG / radG0) + (1 - corr) - if (CI_TgG > 1) or (CI_TgG == np.inf): - CI_TgG = 1 - - # Tg = Tg * CI_Tg # new estimation - # Tgwall = Tgwall * CI_Tg - Tg = Tg * CI_TgG # new estimation - Tgwall = Tgwall * CI_TgG - if landcover == 1: - Tg[Tg < 0] = ( - 0 # temporary for removing low Tg during morning 20130205 - ) - - # # # # Ground View Factors # # # # - ( - gvfLup, - gvfalb, - gvfalbnosh, - gvfLupE, - gvfalbE, - gvfalbnoshE, - gvfLupS, - gvfalbS, - gvfalbnoshS, - gvfLupW, - gvfalbW, - gvfalbnoshW, - gvfLupN, - gvfalbN, - gvfalbnoshN, - gvfSum, - gvfNorm, - ) = gvf_2018a( - wallsun, - walls, - buildings, - scale, - shadow, - first, - second, - dirwalls, - Tg, - Tgwall, - Ta, - emis_grid, - ewall, - alb_grid, - SBC, - albedo_b, - rows, - cols, - Twater, - lc_grid, - landcover, - ) - - # # # # Lup, daytime # # # # - # Surface temperature wave delay - new as from 2014a - Lup, timeaddnotused, Tgmap1 = TsWaveDelay_2015a( - gvfLup, firstdaytime, timeadd, timestepdec, Tgmap1 - ) - LupE, timeaddnotused, Tgmap1E = TsWaveDelay_2015a( - gvfLupE, firstdaytime, timeadd, timestepdec, Tgmap1E - ) - LupS, timeaddnotused, Tgmap1S = TsWaveDelay_2015a( - gvfLupS, firstdaytime, timeadd, timestepdec, Tgmap1S - ) - LupW, timeaddnotused, Tgmap1W = TsWaveDelay_2015a( - gvfLupW, firstdaytime, timeadd, timestepdec, Tgmap1W - ) - LupN, timeaddnotused, Tgmap1N = TsWaveDelay_2015a( - gvfLupN, firstdaytime, timeadd, timestepdec, Tgmap1N - ) - - # # For Tg output in POIs - TgTemp = Tg * shadow + Ta - TgOut, timeadd, TgOut1 = TsWaveDelay_2015a( - TgTemp, firstdaytime, timeadd, timestepdec, TgOut1 - ) # timeadd only here v2021a - - # Building height angle from svf - F_sh = cylindric_wedge( - zen, svfalfa, rows, cols - ) # Fraction shadow on building walls based on sun alt and svf - F_sh[np.isnan(F_sh)] = 0.5 - - # # # # # # # Calculation of shortwave daytime radiative fluxes # # # # # # # - Kdown = ( - radI * shadow * np.sin(altitude * (np.pi / 180)) - + dRad - + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) - ) # *sin(altitude(i) * (pi / 180)) - - # Kdown = radI * shadow * np.sin(altitude * (np.pi / 180)) + radD * svfbuveg + albedo_b * (1 - svfbuveg) * \ - # (radG * (1 - F_sh) + radD * F_sh) # *sin(altitude(i) * (pi / 180)) - - Kup, KupE, KupS, KupW, KupN = Kup_veg_2015a( - radI, - radD, - radG, - altitude, - svfbuveg, - albedo_b, - F_sh, - gvfalb, - gvfalbE, - gvfalbS, - gvfalbW, - gvfalbN, - gvfalbnosh, - gvfalbnoshE, - gvfalbnoshS, - gvfalbnoshW, - gvfalbnoshN, - ) - - # Keast, Ksouth, Kwest, Knorth, KsideI, KsideD = Kside_veg_v2019a(radI, radD, radG, shadow, svfS, svfW, svfN, svfE, - # svfEveg, svfSveg, svfWveg, svfNveg, azimuth, altitude, psi, t, albedo_b, F_sh, KupE, KupS, KupW, - # KupN, cyl, lv, ani, diffsh, rows, cols) - - Keast, Ksouth, Kwest, Knorth, KsideI, KsideD, Kside = Kside_veg_v2022a( - radI, - radD, - radG, - shadow, - svfS, - svfW, - svfN, - svfE, - svfEveg, - svfSveg, - svfWveg, - svfNveg, - azimuth, - altitude, - psi, - t, - albedo_b, - F_sh, - KupE, - KupS, - KupW, - KupN, - cyl, - lv, - anisotropic_sky, - diffsh, - rows, - cols, - asvf, - shmat, - vegshmat, - vbshvegshmat, - ) - - firstdaytime = 0 - - else: # # # # # # # NIGHTTIME # # # # # # # # - - Tgwall = 0 - # CI_Tg = -999 # F_sh = [] - - # Nocturnal K fluxes set to 0 - Knight = np.zeros((rows, cols)) - Kdown = np.zeros((rows, cols)) - Kwest = np.zeros((rows, cols)) - Kup = np.zeros((rows, cols)) - Keast = np.zeros((rows, cols)) - Ksouth = np.zeros((rows, cols)) - Knorth = np.zeros((rows, cols)) - KsideI = np.zeros((rows, cols)) - KsideD = np.zeros((rows, cols)) - F_sh = np.zeros((rows, cols)) - Tg = np.zeros((rows, cols)) - shadow = np.zeros((rows, cols)) - CI_Tg = deepcopy(CI) - CI_TgG = deepcopy(CI) - - dRad = np.zeros((rows, cols)) - - Kside = np.zeros((rows, cols)) - - # # # # Lup # # # # - Lup = SBC * emis_grid * ((Knight + Ta + Tg + 273.15) ** 4) - if landcover == 1: - Lup[lc_grid == 3] = ( - SBC * 0.98 * (Twater + 273.15) ** 4 - ) # nocturnal Water temp - - LupE = Lup - LupS = Lup - LupW = Lup - LupN = Lup - - # # For Tg output in POIs - TgOut = Ta + Tg - - I0 = 0 - timeadd = 0 - firstdaytime = 1 - - # # # # Ldown # # # # - # Anisotropic sky longwave radiation - if anisotropic_sky == 1: - if "lv" not in locals(): - # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) - skyvaultalt, skyvaultazi, _, _, _, _, _ = create_patches( - patch_option - ) - - patch_emissivities = np.zeros(skyvaultalt.shape[0]) - - x = np.transpose(np.atleast_2d(skyvaultalt)) - y = np.transpose(np.atleast_2d(skyvaultazi)) - z = np.transpose(np.atleast_2d(patch_emissivities)) - - L_patches = np.append(np.append(x, y, axis=1), z, axis=1) - - else: - L_patches = deepcopy(lv) - - if altitude < 0: - CI = deepcopy(CI) - - if CI < 0.95: - esky_c = CI * esky + (1 - CI) * 1.0 - esky = esky_c - - Ldown, Lside, Least_, Lwest_, Lnorth_, Lsouth_ = Lcyl_v2022a( - esky, - L_patches, - Ta, - Tgwall, - ewall, - Lup, - shmat, - vegshmat, - vbshvegshmat, - altitude, - azimuth, - rows, - cols, - asvf, - ) - - else: - Ldown = ( - (svf + svfveg - 1) * esky * SBC * ((Ta + 273.15) ** 4) - + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) - + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) - + (2 - svf - svfveg) - * (1 - ewall) - * esky - * SBC - * ((Ta + 273.15) ** 4) - ) # Jonsson et al.(2006) - # Ldown = Ldown - 25 # Shown by Jonsson et al.(2006) and Duarte et al.(2006) - - Lside = np.zeros((rows, cols)) - L_patches = None - - if CI < 0.95: # non - clear conditions - c = 1 - CI - Ldown = Ldown * (1 - c) + c * ( - (svf + svfveg - 1) * SBC * ((Ta + 273.15) ** 4) - + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) - + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) - + (2 - svf - svfveg) * (1 - ewall) * SBC * ((Ta + 273.15) ** 4) - ) # NOT REALLY TESTED!!! BUT MORE CORRECT? - - # # # # Lside # # # # - Least, Lsouth, Lwest, Lnorth = Lside_veg_v2022a( - svfS, - svfW, - svfN, - svfE, - svfEveg, - svfSveg, - svfWveg, - svfNveg, - svfEaveg, - svfSaveg, - svfWaveg, - svfNaveg, - azimuth, - altitude, - Ta, - Tgwall, - SBC, - ewall, - Ldown, - esky, - t, - F_sh, - CI, - LupE, - LupS, - LupW, - LupN, - anisotropic_sky, - ) - - # Box and anisotropic longwave - if cyl == 0 and anisotropic_sky == 1: - Least += Least_ - Lwest += Lwest_ - Lnorth += Lnorth_ - Lsouth += Lsouth_ - - # # # # Calculation of radiant flux density and Tmrt # # # # - # Human body considered as a cylinder with isotropic all-sky diffuse - if cyl == 1 and anisotropic_sky == 0: - Sstr = absK * ( - KsideI * Fcyl - + (Kdown + Kup) * Fup - + (Knorth + Keast + Ksouth + Kwest) * Fside - ) + absL * ( - (Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside - ) - # Human body considered as a cylinder with Perez et al. (1993) (anisotropic sky diffuse) - # and Martin and Berdahl (1984) (anisotropic sky longwave) - elif cyl == 1 and anisotropic_sky == 1: - Sstr = absK * ( - Kside * Fcyl - + (Kdown + Kup) * Fup - + (Knorth + Keast + Ksouth + Kwest) * Fside - ) + absL * ( - (Ldown + Lup) * Fup - + Lside * Fcyl - + (Lnorth + Least + Lsouth + Lwest) * Fside - ) - # Knorth = nan Ksouth = nan Kwest = nan Keast = nan - else: # Human body considered as a standing cube - Sstr = absK * ( - (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside - ) + absL * ( - (Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside - ) - - Tmrt = np.sqrt(np.sqrt((Sstr / (absL * SBC)))) - 273.2 - - # Add longwave to cardinal directions for output in POI - if (cyl == 1) and (anisotropic_sky == 1): - Least += Least_ - Lwest += Lwest_ - Lnorth += Lnorth_ - Lsouth += Lsouth_ - - return ( - Tmrt, - Kdown, - Kup, - Ldown, - Lup, - Tg, - ea, - esky, - I0, - CI, - shadow, - firstdaytime, - timestepdec, - timeadd, - Tgmap1, - Tgmap1E, - Tgmap1S, - Tgmap1W, - Tgmap1N, - Keast, - Ksouth, - Kwest, - Knorth, - Least, - Lsouth, - Lwest, - Lnorth, - KsideI, - TgOut1, - TgOut, - radI, - radD, - Lside, - L_patches, - CI_Tg, - CI_TgG, - KsideD, - dRad, - Kside, - ) diff --git a/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py index f5228e4..c34e000 100644 --- a/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py +++ b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py @@ -133,6 +133,7 @@ def Solweig_2026a_calc( walls_scheme, dirwalls_scheme, groundScheme, + outgoingLW, Tg, Rn, Rn_past, @@ -401,6 +402,34 @@ def Solweig_2026a_calc( shadow_past, ) + else: + # using max sun alt instead of dfm + Tgamp = TgK * altmax + Tstart # Fixed 2021 + Tgdiff = Tgamp * torch.sin( + ( + ((dectime - torch.floor(dectime)) - SNUP / 24) + / (TmaxLST / 24 - SNUP / 24) + ) + * torch.pi + / 2 + ) # 2015 a, based on max sun altitude + + Tgdiff = Tgdiff * CI_TgG # new estimation + + # For Tg output in POIs + TgTemp = Tgdiff * shadow + Ta + _, timeadd, Tg = TsWaveDelay_2015a( + TgTemp, firstdaytime, timeadd, timestepdec, Tg + ) # timeadd only here v2021a + + if landcover == 1: + Tg[Tg < 0] = ( + 0 # temporary for removing low Tg during morning 20130205 + ) + + # Calculate the outgoing longwave radiation + if outgoingLW == 1: + # # # # Lup, daytime # # # # ( Lup, @@ -439,30 +468,6 @@ def Solweig_2026a_calc( ) else: - # using max sun alt instead of dfm - Tgamp = TgK * altmax + Tstart # Fixed 2021 - Tgdiff = Tgamp * torch.sin( - ( - ((dectime - torch.floor(dectime)) - SNUP / 24) - / (TmaxLST / 24 - SNUP / 24) - ) - * torch.pi - / 2 - ) # 2015 a, based on max sun altitude - - Tgdiff = Tgdiff * CI_TgG # new estimation - - # For Tg output in POIs - TgTemp = Tgdiff * shadow + Ta - _, timeadd, Tg = TsWaveDelay_2015a( - TgTemp, firstdaytime, timeadd, timestepdec, Tg - ) # timeadd only here v2021a - - if landcover == 1: - Tg[Tg < 0] = ( - 0 # temporary for removing low Tg during morning 20130205 - ) - ### Ground View Factors ( gvfLup, @@ -648,7 +653,12 @@ def Solweig_2026a_calc( shadow, shadow_past, ) + else: + # In the old scheme the ground surface temperature is equal to the air temperature during nighttime + Tg = torch.ones((rows, cols), device=device) * Ta + # Calculate the outgoing longwave radiation + if outgoingLW == 1: # # # # Lup, daytime # # # # ( Lup, @@ -687,9 +697,6 @@ def Solweig_2026a_calc( ) else: - # In the old scheme the ground surface temperature is equal to the air temperature during nighttime - Tg = torch.ones((rows, cols), device=device) * Ta - # # # # Lup, nighttime # # # # Lup = SBC * emis_grid * ((Knight + Tg + 273.15) ** 4) LupE = Lup diff --git a/functions/SOLWEIGpython/Solweig_run.py b/functions/SOLWEIGpython/Solweig_run.py index 7d1c40c..5318a55 100644 --- a/functions/SOLWEIGpython/Solweig_run.py +++ b/functions/SOLWEIGpython/Solweig_run.py @@ -56,8 +56,6 @@ pass - - def solweig_run(configPath, feedback): """ Input: @@ -697,8 +695,8 @@ def solweig_run(configPath, feedback): TmaxLST_wall = param["TmaxLST"]["Value"]["Walls"] # Parameterization for the 2026 ground scheme - groundScheme = int(configDict["groundmodel"]) - if groundScheme == 1: + groundSurface = int(configDict["groundmodel"]) + if groundSurface == 1: # Initiate the maps if the surface temperature is available if configDict["input_surf"] != "": surfData = pd.read_csv(configDict["input_surf"]) @@ -736,6 +734,9 @@ def solweig_run(configPath, feedback): else: pass + # Replace the ground view factors with integration of solid angles + outgoingLW = int(configDict["outgoingLW"]) + # Import data for wall temperature parameterization TODO: fix for standalone wallScheme = int(configDict["wallscheme"]) if wallScheme == 1: @@ -1077,6 +1078,7 @@ def solweig_run(configPath, feedback): walls_scheme, dirwalls_scheme, groundScheme, + outgoingLW, Tg, Rn, Rn_past, diff --git a/functions/SOLWEIGpython/ground_surface.py b/functions/SOLWEIGpython/ground_surface.py index 903259f..120fae1 100644 --- a/functions/SOLWEIGpython/ground_surface.py +++ b/functions/SOLWEIGpython/ground_surface.py @@ -102,24 +102,36 @@ def initiate_groundScheme( offset_Tg = solweig_parameters["Tg_ini coefficients"]["Values"][ solweig_parameters["Names"]["Value"][str((int(i.item())))] ][0] - ratio_Tg = solweig_parameters["Tg_ini coefficients"]["Values"][ - solweig_parameters["Names"]["Value"][str((int(i.item())))] - ][1] - phi_Tg = solweig_parameters["Tg_ini coefficients"]["Values"][ + + slope_Tg = solweig_parameters["Tg_ini coefficients"]["Values"][ solweig_parameters["Names"]["Value"][str((int(i.item())))] - ][2] + ][0] + + ratio_Tg = float( + solweig_parameters["Tg_ini coefficients"]["Values"][ + solweig_parameters["Names"]["Value"][str((int(i.item())))] + ][1] + ) + phi_Tg = 1.6 + + # Correct the offset value given the latitude + offset_Tg += slope_Tg * location["latitude"] # Mean daily soil temperature parameters ampl_Tm = solweig_parameters["Tm_ini coefficients"]["Values"][ solweig_parameters["Names"]["Value"][str((int(i.item())))] ][0] - phi_Tm = solweig_parameters["Tm_ini coefficients"]["Values"][ + slope_Tm = solweig_parameters["Tm_inicoefficients"]["Values"][ solweig_parameters["Names"]["Value"][str((int(i.item())))] ][1] + phi_Tm = 1.7 offset_Tm = solweig_parameters["Tm_ini coefficients"]["Values"][ solweig_parameters["Names"]["Value"][str((int(i.item())))] ][2] + # Correct the offset value given the latitude + offset_Tm += slope_Tm * location["latitude"] + if i == 0 or i == 1: # For paved and asphalt landcover Tg[Tg == i] = ( @@ -127,8 +139,7 @@ def initiate_groundScheme( + offset_Tg * ( 1 - + 1 - / ratio_Tg + + ratio_Tg * torch.sin(2 * torch.pi / 365.25 * day + phi_Tg) * torch.sign( torch.tensor(location["latitude"], device=device) @@ -152,8 +163,7 @@ def initiate_groundScheme( + offset_Tg * ( 1 - + 1 - / ratio_Tg + + ratio_Tg * torch.sin(2 * torch.pi / 365.25 * day + phi_Tg) * torch.sign( torch.tensor(location["latitude"], device=device) @@ -167,8 +177,7 @@ def initiate_groundScheme( # For grass surfaces Tg[Tg == i] = Ta[0] + offset_Tg * ( 1 - + 1 - / ratio_Tg + + ratio_Tg * torch.sin(2 * torch.pi / 365.25 * day + phi_Tg) * torch.sign(torch.tensor(location["latitude"], device=device)) ) @@ -187,8 +196,7 @@ def initiate_groundScheme( + offset_Tg * ( 1 - + 1 - / ratio_Tg + + ratio_Tg * torch.sin(2 * torch.pi / 365.25 * day + phi_Tg) * torch.sign( torch.tensor(location["latitude"], device=device) diff --git a/postprocessor/treeplanter_algorithm.py b/postprocessor/treeplanter_algorithm.py index 5c9136f..f72f103 100644 --- a/postprocessor/treeplanter_algorithm.py +++ b/postprocessor/treeplanter_algorithm.py @@ -102,8 +102,6 @@ from ..functions.TreePlanter.SOLWEIG1D.SOLWEIG1D_2023a import tmrt_1d_fun - - class ProcessingTreePlanterAlgorithm(QgsProcessingAlgorithm): """ This algorithm is a processing version of Tree planter diff --git a/processor/configsolweig.ini b/processor/configsolweig.ini index ce029c7..96cd205 100644 --- a/processor/configsolweig.ini +++ b/processor/configsolweig.ini @@ -36,7 +36,6 @@ poi_field= input_wall = C:\Users\xlinfr\Desktop\SOLWEIGdata\blabla.npz # Input file for surface temperature data input_surf=C:\Users\xlinfr\Desktop\SOLWEIGdata\surftemp.txt -input_surf=C:\Users\xlinfr\Desktop\SOLWEIGdata\surftemp.txt #Point of Interest file for walls woi_file= woi_field= @@ -78,7 +77,6 @@ wallscheme=0 # If building materials is not included in lc, then this is used for all buildings (Wood, Brick or Concrete) walltype=Brick # use OHM for ground surface temperature modeling -groundmodel=1 # output settings outputtmrt=1 outputkup=1 diff --git a/processor/solweig_algorithm.py b/processor/solweig_algorithm.py index 4165a19..aef489b 100644 --- a/processor/solweig_algorithm.py +++ b/processor/solweig_algorithm.py @@ -255,7 +255,7 @@ def initAlgorithm(self, config): self.INPUT_LC, self.tr("UMEP land cover grid"), "", - optional=False, + optional=True, ) ) self.addParameter( @@ -271,7 +271,7 @@ def initAlgorithm(self, config): self.INPUT_DEM, self.tr("Digital Elevation Model (DEM)"), "", - optional=False, + optional=True, ) ) self.addParameter( From d4f05d87203440f6b70f39c3d1681c99d18556af Mon Sep 17 00:00:00 2001 From: "Mael.PHILIPPE" Date: Fri, 5 Jun 2026 14:05:49 +0200 Subject: [PATCH 12/20] added gpu calcuation, and torch is not mandatory to run umep-processing unless you try to launch on the gpu --- __init__.py | 18 +- .../SEBE_2015a_calc_forprocessing.py | 111 +- functions/SEBEfiles/importdata.py | 57 +- functions/SEBEfiles/sunmapcreator_2015a.py | 99 +- functions/SOLWEIGpython/CirclePlotBar.py | 1 - functions/SOLWEIGpython/Kside_veg_v2022a.py | 431 ++--- .../SOLWEIGpython/Kside_veg_v2022a_torch.py | 569 ++++++ functions/SOLWEIGpython/Kup_veg_2015a.py | 11 +- .../SOLWEIGpython/Kup_veg_2015a_torch.py | 51 + functions/SOLWEIGpython/Lside_veg.py | 175 +- functions/SOLWEIGpython/Lside_veg_torch.py | 509 ++++++ functions/SOLWEIGpython/Lside_veg_v2015a.py | 325 ++-- functions/SOLWEIGpython/Lvikt_veg.py | 16 - functions/SOLWEIGpython/Lvikt_veg_torch.py | 81 + .../Solweig_2025a_calc_forprocessing.py | 51 +- .../Solweig_2026a_calc_forprocessing.py | 741 +++----- .../Solweig_2026a_calc_forprocessing_torch.py | 970 ++++++++++ functions/SOLWEIGpython/Solweig_run.py | 564 +++--- functions/SOLWEIGpython/Solweig_run_torch.py | 1556 +++++++++++++++++ functions/SOLWEIGpython/Tgmaps_v1.py | 25 +- functions/SOLWEIGpython/Tgmaps_v1_torch.py | 50 + functions/SOLWEIGpython/anisotropic_sky.py | 1 - functions/SOLWEIGpython/cylindric_wedge.py | 79 +- .../SOLWEIGpython/cylindric_wedge_torch.py | 112 ++ functions/SOLWEIGpython/daylen.py | 20 +- functions/SOLWEIGpython/daylen_torch.py | 32 + functions/SOLWEIGpython/ground_surface.py | 383 ++-- .../SOLWEIGpython/ground_surface_torch.py | 785 +++++++++ functions/SOLWEIGpython/patch_radiation.py | 4 +- functions/SOLWEIGpython/wallOfInterest.py | 1 - .../SOLWEIGpython/wall_surface_temperature.py | 1 - functions/svf_for_voxels.py | 390 ++--- functions/svf_for_voxels_torch.py | 578 ++++++ functions/svf_functions.py | 369 ++-- functions/svf_functions_torch.py | 671 +++++++ functions/wallalgorithms.py | 440 ++--- functions/wallalgorithms_torch.py | 383 ++++ lc_update.py | 17 + preprocessor/skyviewfactor_algorithm.py | 453 +++-- preprocessor/wall_heightaspect_algorithm.py | 101 +- processor/configsolweig.ini | 2 +- processor/sebe_algorithm.py | 48 +- processor/solweig_algorithm.py | 82 +- util/SEBESOLWEIGCommonFiles/Perez_v3.py | 268 ++- util/SEBESOLWEIGCommonFiles/Perez_v3_torch.py | 351 ++++ .../Solweig_v2015_metdata_noload.py | 66 +- .../Solweig_v2015_metdata_noload_torch.py | 126 ++ .../clearnessindex_2013b.py | 46 +- .../clearnessindex_2013b_torch.py | 110 ++ util/SEBESOLWEIGCommonFiles/create_patches.py | 102 +- .../create_patches_torch.py | 113 ++ .../SEBESOLWEIGCommonFiles/diffusefraction.py | 17 +- .../diffusefraction_torch.py | 67 + .../shadowingfunction_wallheight_13.py | 302 ++-- .../shadowingfunction_wallheight_13_torch.py | 166 ++ .../shadowingfunction_wallheight_23.py | 311 ++-- .../shadowingfunction_wallheight_23_torch.py | 249 +++ util/SEBESOLWEIGCommonFiles/sun_distance.py | 14 +- .../sun_distance_torch.py | 24 + util/SEBESOLWEIGCommonFiles/sun_position.py | 411 ++--- .../sun_position_torch.py | 1306 ++++++++++++++ util/imageMorphometricParms_v2.py | 2 - util/landCoverFractions_v2.py | 12 - util/ncWMSConnector.py | 2 +- util/shadowingfunctions.py | 875 +++++---- util/shadowingfunctions_torch.py | 463 +++++ util/ssParms.py | 3 +- util/torch_fallback.py | 25 + util/umep_solweig_export_component.py | 16 +- 69 files changed, 12844 insertions(+), 3966 deletions(-) create mode 100644 functions/SOLWEIGpython/Kside_veg_v2022a_torch.py create mode 100644 functions/SOLWEIGpython/Kup_veg_2015a_torch.py create mode 100644 functions/SOLWEIGpython/Lside_veg_torch.py create mode 100644 functions/SOLWEIGpython/Lvikt_veg_torch.py create mode 100644 functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing_torch.py create mode 100644 functions/SOLWEIGpython/Solweig_run_torch.py create mode 100644 functions/SOLWEIGpython/Tgmaps_v1_torch.py create mode 100644 functions/SOLWEIGpython/cylindric_wedge_torch.py create mode 100644 functions/SOLWEIGpython/daylen_torch.py create mode 100644 functions/SOLWEIGpython/ground_surface_torch.py create mode 100644 functions/svf_for_voxels_torch.py create mode 100644 functions/svf_functions_torch.py create mode 100644 functions/wallalgorithms_torch.py create mode 100644 lc_update.py create mode 100644 util/SEBESOLWEIGCommonFiles/Perez_v3_torch.py create mode 100644 util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload_torch.py create mode 100644 util/SEBESOLWEIGCommonFiles/clearnessindex_2013b_torch.py create mode 100644 util/SEBESOLWEIGCommonFiles/create_patches_torch.py create mode 100644 util/SEBESOLWEIGCommonFiles/diffusefraction_torch.py create mode 100644 util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13_torch.py create mode 100644 util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23_torch.py create mode 100644 util/SEBESOLWEIGCommonFiles/sun_distance_torch.py create mode 100644 util/SEBESOLWEIGCommonFiles/sun_position_torch.py create mode 100644 util/shadowingfunctions_torch.py create mode 100644 util/torch_fallback.py diff --git a/__init__.py b/__init__.py index 1d80fad..91562c8 100644 --- a/__init__.py +++ b/__init__.py @@ -12,15 +12,17 @@ ***************************************************************************/ /*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * ***************************************************************************/ This script initializes the plugin, making it known to QGIS. """ +# 1. Initialize fallback immediately to shield all downstream imports +from .util import torch_fallback import site import sys @@ -41,7 +43,7 @@ def classFactory(iface): # pylint: disable=invalid-name :param iface: A QGIS interface instance. :type iface: QgsInterface """ - # from .processing_umep import ProcessingUMEPPlugin - return ProcessingUMEPPlugin() + # Crucial: pass the iface variable QGIS gives you right into the plugin + return ProcessingUMEPPlugin() \ No newline at end of file diff --git a/functions/SEBEfiles/SEBE_2015a_calc_forprocessing.py b/functions/SEBEfiles/SEBE_2015a_calc_forprocessing.py index af5eb7c..c20f468 100644 --- a/functions/SEBEfiles/SEBE_2015a_calc_forprocessing.py +++ b/functions/SEBEfiles/SEBE_2015a_calc_forprocessing.py @@ -1,6 +1,5 @@ from builtins import range import numpy as np -import torch from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_13 import ( shadowingfunction_wallheight_13, ) @@ -29,19 +28,18 @@ def SEBE_2015a_calc( usevegdem, feedback, wallmaxheight, - device, ): # Parameters - deg2rad = torch.pi / 180 - Knight = torch.zeros((sizex, sizey), device=device) - Energyyearroof = torch.clone(Knight) + deg2rad = np.pi / 180 + Knight = np.zeros((sizex, sizey)) + Energyyearroof = np.copy(Knight) if usevegdem == 1: # amaxvalue vegmax = vegdem.max() amaxvalue = a.max() - a.min() - amaxvalue = torch.maximum(amaxvalue, vegmax) + amaxvalue = np.maximum(amaxvalue, vegmax) # Elevation vegdsms if buildingDEM includes ground heights vegdem = vegdem + a @@ -50,39 +48,34 @@ def SEBE_2015a_calc( vegdem2[vegdem2 == a] = 0 # % Bush separation - bush = torch.logical_not((vegdem2 * vegdem)) * vegdem + bush = np.logical_not((vegdem2 * vegdem)) * vegdem else: psi = 1 # Creating wallmatrix (1 meter interval) - wallcol, wallrow = torch.where( - torch.transpose(walls) > 0 - ) # row and col for each wall pixel - wallstot = torch.floor(walls * (1 / voxelheight)) * voxelheight - # wallsections = torch.floor(torch.max(walls) * (1 / voxelheight)) # finding tallest wall - wallsections = torch.floor(wallmaxheight * (1 / voxelheight)) - # feedback.setProgressText('torch.max(walls):' + str(torch.max(walls))) + # row and col for each wall pixel + wallcol, wallrow = np.where(np.transpose(walls) > 0) + wallstot = np.floor(walls * (1 / voxelheight)) * voxelheight + # wallsections = np.floor(np.max(walls) * (1 / voxelheight)) # finding + # tallest wall + wallsections = np.floor(wallmaxheight * (1 / voxelheight)) + # feedback.setProgressText('np.max(walls):' + str(np.max(walls))) # feedback.setProgressText('1 / voxelheight:' + str(1 / voxelheight)) # feedback.setProgressText('voxel:' + str(voxelheight)) # feedback.setProgressText('wallsections:' + str(wallsections)) - # feedback.setProgressText('torch.shape(wallrow)[0]:' + str(torch.shape(wallrow)[0])) - wallmatrix = torch.zeros( - (torch.shape(wallrow)[0], int(wallsections)), device=device - ) - Energyyearwall = torch.clone(wallmatrix) + # feedback.setProgressText('np.shape(wallrow)[0]:' + str(np.shape(wallrow)[0])) + wallmatrix = np.zeros((np.shape(wallrow)[0], int(wallsections))) + Energyyearwall = np.copy(wallmatrix) - # Main loop - Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) - skyvaultaltint = torch.tensor( - [6, 18, 30, 42, 54, 66, 78, 90], device=device - ) - aziinterval = torch.tensor([30, 30, 24, 24, 18, 12, 6, 1], device=device) + # Main loop - Creating skyvault of patches of constant radians (Tregeneza + # and Sharples, 1993) + skyvaultaltint = np.array([6, 18, 30, 42, 54, 66, 78, 90]) + aziinterval = np.array([30, 30, 24, 24, 18, 12, 6, 1]) if usevegdem == 1: - wallshve = torch.zeros(torch.shape(a), device=device) - vegrow, vegcol = torch.where( - vegdem > 0 - ) # row and col for each veg pixel - vegdata = torch.zeros((torch.shape(vegrow)[0], 3), device=device) + wallshve = np.zeros(np.shape(a)) + vegrow, vegcol = np.where(vegdem > 0) # row and col for each veg pixel + vegdata = np.zeros((np.shape(vegrow)[0], 3)) for i in range(0, vegrow.shape[0] - 1): vegdata[i, 0] = vegrow[i] + 1 vegdata[i, 1] = vegcol[i] + 1 @@ -102,23 +95,22 @@ def SEBE_2015a_calc( #################### SOLAR RADIATION POSITIONS ################### # Solar Incidence angle (Roofs) - suniroof = torch.sin(slope) * torch.cos( + suniroof = np.sin(slope) * np.cos( radmatI[index, 0] * deg2rad - ) * torch.cos((radmatI[index, 1] * deg2rad) - aspect) + torch.cos( + ) * np.cos((radmatI[index, 1] * deg2rad) - aspect) + np.cos( slope - ) * torch.sin( + ) * np.sin( (radmatI[index, 0] * deg2rad) ) suniroof[suniroof < 0] = 0 # Solar Incidence angle (Walls) - suniwall = torch.abs( - torch.sin(torch.pi / 2) - * torch.cos(radmatI[index, 0] * deg2rad) - * torch.cos((radmatI[index, 1] * deg2rad) - dirwalls * deg2rad) - + torch.cos(torch.pi / 2) - * torch.sin((radmatI[index, 0] * deg2rad)) + suniwall = np.abs( + np.sin(np.pi / 2) + * np.cos(radmatI[index, 0] * deg2rad) + * np.cos((radmatI[index, 1] * deg2rad) - dirwalls * deg2rad) + + np.cos(np.pi / 2) * np.sin((radmatI[index, 0] * deg2rad)) ) # Shadow image @@ -137,7 +129,7 @@ def SEBE_2015a_calc( dirwalls * deg2rad, ) ) - shadow = torch.clone(sh - (1.0 - vegsh) * (1.0 - psi)) + shadow = np.copy(sh - (1.0 - vegsh) * (1.0 - psi)) else: sh, wallsh, wallsun, facesh, facesun = ( shadowingfunction_wallheight_13( @@ -149,48 +141,47 @@ def SEBE_2015a_calc( dirwalls * deg2rad, ) ) - shadow = torch.clone(sh) + shadow = np.copy(sh) # roof irradiance calculation # direct radiation if radmatI[index, 2] > 0: I = shadow * radmatI[index, 2] * suniroof else: - I = torch.clone(Knight) + I = np.copy(Knight) # roof diffuse and reflected radiation D = radmatD[index, 2] * shadow R = radmatR[index, 2] * (shadow * -1 + 1) - Energyyearroof = torch.clone(Energyyearroof + D + R + I) + Energyyearroof = np.copy(Energyyearroof + D + R + I) # WALL IRRADIANCE # direct radiation if radmatI[index, 2] > 0: Iw = radmatI[index, 2] * suniwall # wall else: - Iw = torch.clone(Knight) + Iw = np.copy(Knight) # wall diffuse and reflected radiation Dw = radmatD[index, 2] * facesun Rw = radmatR[index, 2] * facesun # for each wall level (voxelheight interval) - wallsun = torch.floor(wallsun * (1 / voxelheight)) * voxelheight - wallsh = torch.floor(wallsh * (1 / voxelheight)) * voxelheight + wallsun = np.floor(wallsun * (1 / voxelheight)) * voxelheight + wallsh = np.floor(wallsh * (1 / voxelheight)) * voxelheight if usevegdem == 1: - wallshve = ( - torch.floor(wallshve * (1 / voxelheight)) * voxelheight - ) + wallshve = np.floor(wallshve * (1 / voxelheight)) * voxelheight wallmatrix = wallmatrix * 0 - for p in range(torch.shape(wallmatrix)[0]): + for p in range(np.shape(wallmatrix)[0]): if wallsun[wallrow[p], wallcol[p]] > 0: # Sections in sun + # All sections in sun if ( wallsun[int(wallrow[p]), int(wallcol[p])] == wallstot[int(wallrow[p]), int(wallcol[p])] - ): # All sections in sun + ): wallmatrix[ p, 0 : int( @@ -221,9 +212,8 @@ def SEBE_2015a_calc( + Rw[wallrow[p], wallcol[p]] ) - if ( - usevegdem == 1 and wallshve[wallrow[p], wallcol[p]] > 0 - ): # sections in vegetation shade + # sections in vegetation shade + if usevegdem == 1 and wallshve[wallrow[p], wallcol[p]] > 0: wallmatrix[ p, 0 : int( @@ -245,7 +235,7 @@ def SEBE_2015a_calc( 0 : int(wallsh[wallrow[p], wallcol[p]] / voxelheight), ] = Rw[wallrow[p], wallcol[p]] - Energyyearwall = Energyyearwall + torch.clone(wallmatrix) + Energyyearwall = Energyyearwall + np.copy(wallmatrix) index = index + 1 @@ -253,16 +243,15 @@ def SEBE_2015a_calc( # fix_print_with_import wallmatrixbol = (Energyyearwall > 0).astype(float) Energyyearwall = ( - Energyyearwall + (torch.sum(radmatR[:, 2]) * albedo) / 2 + Energyyearwall + (np.sum(radmatR[:, 2]) * albedo) / 2 ) * wallmatrixbol Energyyearroof /= 1000 Energyyearwall /= 1000 - Energyyearwall = torch.transpose( - torch.vstack( - (wallrow + 1, wallcol + 1, torch.transpose(Energyyearwall)) - ) - ) # adding 1 to wallrow and wallcol so that the tests pass + # adding 1 to wallrow and wallcol so that the tests pass + Energyyearwall = np.transpose( + np.vstack((wallrow + 1, wallcol + 1, np.transpose(Energyyearwall))) + ) seberesult = { "Energyyearroof": Energyyearroof, @@ -270,4 +259,4 @@ def SEBE_2015a_calc( "vegdata": vegdata, } - return seberesult + return seberesult \ No newline at end of file diff --git a/functions/SEBEfiles/importdata.py b/functions/SEBEfiles/importdata.py index 2156cf0..9fa3bc0 100644 --- a/functions/SEBEfiles/importdata.py +++ b/functions/SEBEfiles/importdata.py @@ -47,10 +47,9 @@ import os from PIL import Image import numpy as np -import torch -def importdata(*args, device): +def importdata(*args): """ Imports data. @@ -136,10 +135,9 @@ def importdata(*args, device): delimiter = None headerRows = 0 img = Image.open(fileName) - output["cdata"] = torch.tensor(img, device=device) - output["colormap"] = ( - img.mode - ) # TODO: check if this method is euaivalent + output["cdata"] = np.array(img) + # TODO: check if this method is euaivalent + output["colormap"] = img.mode output["alpha"] = img.split()[-1] elif ext is ".mat": import scipy.io as sio @@ -197,9 +195,9 @@ def print_usage(): raise IOError("Insufficient/bad arguments for importdata()") -def importdata_ascii(fileName, delimiter, headerRows, device): +def importdata_ascii(fileName, delimiter, headerRows): output = dict() - output["data"] = torch.tensor([], device=device) + output["data"] = np.array([]) output["textdata"] = list() # Read file into string and count the number of header rows @@ -220,19 +218,21 @@ def importdata_ascii(fileName, delimiter, headerRows, device): if delimiter in line: headerRows += 1 else: - # Data part has begun and therefore no more header rows can be found + # Data part has begun and therefore no more header rows can be + # found break # Put the header rows in output.textdata. if headerRows > 0: for i, el in enumerate(fileContentRows[0:headerRows]): - output["textdata"].append( - str(el) - ) # struct in ML is converted to dict in py + # struct in ML is converted to dict in py + output["textdata"].append(str(el)) - # If space is the delimiter, then remove spaces in the beginning of each data row. + # If space is the delimiter, then remove spaces in the beginning of each + # data row. if delimiter is " ": - # strtrim does not only remove the leading spaces but also the tailing spaces, but that doesn't really matter. + # strtrim does not only remove the leading spaces but also the tailing + # spaces, but that doesn't really matter. fileContentRows = fileContentRows[:headerRows] + [ line.strip() for line in fileContentRows[headerRows:] ] @@ -247,40 +247,35 @@ def importdata_ascii(fileName, delimiter, headerRows, device): # If there are different number of columns, use the greatest value. dataColumns = 0 for line in fileContentRows: - num_elements = len( - [el for el in line.split(delimiter) if el] - ) # if element is not empty + # if element is not empty + num_elements = len([el for el in line.split(delimiter) if el]) dataColumns = max(dataColumns, num_elements) # print "headerRows py:", headerRows - # Go through the data and put it in either output.data or output.textdata depending on if it is numeric or not. + # Go through the data and put it in either output.data or output.textdata + # depending on if it is numeric or not. output["data"] = ( - torch.from_numpy( - np.empty((len(fileContentRows) - headerRows, dataColumns)), - device=device, - ) - * torch.nan + np.empty((len(fileContentRows) - headerRows, dataColumns)) * np.nan ) for i, line in enumerate(fileContentRows[headerRows:]): - # Only use the row if it contains anything other than white-space characters. + # Only use the row if it contains anything other than white-space + # characters. if line.replace(" ", ""): rowData = line.split(delimiter) for j, el in enumerate(rowData): try: output["data"][i, j] = float(el) except ValueError: - output["textdata"].append( - str(el) - ) # using tuple (i,j) as key to dict textdata + # using tuple (i,j) as key to dict textdata + output["textdata"].append(str(el)) # Check wether rowheaders or colheaders should be used # fix_print_with_import print("text data") # fix_print_with_import print(output["textdata"]) - if ( - headerRows == dataColumns and len(output["textdata"]) == 1 - ): # getting the col size, assuming + # getting the col size, assuming + if headerRows == dataColumns and len(output["textdata"]) == 1: # the dict is equivalent of struct in Matlab output["rowheaders"] = output["textdata"] elif len(output["textdata"]) == dataColumns: # TODO fix this @@ -288,4 +283,4 @@ def importdata_ascii(fileName, delimiter, headerRows, device): # making changes to data to fit the Matlab function headerRows = float(headerRows) - return output, delimiter, headerRows + return output, delimiter, headerRows \ No newline at end of file diff --git a/functions/SEBEfiles/sunmapcreator_2015a.py b/functions/SEBEfiles/sunmapcreator_2015a.py index b112875..610b577 100644 --- a/functions/SEBEfiles/sunmapcreator_2015a.py +++ b/functions/SEBEfiles/sunmapcreator_2015a.py @@ -1,7 +1,7 @@ from __future__ import division from __future__ import absolute_import from builtins import range -import torch +import numpy as np from ...util.SEBESOLWEIGCommonFiles.diffusefraction import diffusefraction from ...util.SEBESOLWEIGCommonFiles.Perez_v3 import Perez_v3 from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import ( @@ -11,16 +11,7 @@ def sunmapcreator_2015a( - met, - altitude, - azimuth, - onlyglobal, - output, - jday, - albedo, - location, - zen, - device, + met, altitude, azimuth, onlyglobal, output, jday, albedo, location, zen ): """ % This function creates a sun map based on hourly values of solar radiation. @@ -34,9 +25,15 @@ def sunmapcreator_2015a( :param albedo: :return: """ + np.seterr(over="raise") + np.seterr(invalid="raise") - # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) + # Creating skyvault of patches of constant radians (Tregeneza and + # Sharples, 1993) patch_option = 1 # 145 patches + # patch_option = 2 # 153 patches + # patch_option = 3 # 306 patches + # patch_option = 4 # 612 patches ( skyvaultalt, skyvaultazi, @@ -45,45 +42,31 @@ def sunmapcreator_2015a( aziinterval, skyvaultaziint, azistart, - ) = create_patches(patch_option, device) - - iangle2 = torch.tensor([]) + ) = create_patches(patch_option) + iangle2 = np.array([]) + Gyear = 0 + Dyear = 0 + Gmonth = np.zeros([1, 12]) + Dmonth = Gmonth for j in range(len(aziinterval)): - iangle2 = torch.append( - iangle2, - skyvaultaltint[j] * torch.ones([1, aziinterval[j]], device=device), + iangle2 = np.append( + iangle2, skyvaultaltint[j] * np.ones([1, aziinterval[j]]) ) - radmatI = torch.transpose( - torch.vstack( - ( - iangle2, - skyvaultazi, - torch.zeros((13, len(iangle2)), device=device), - ) - ) + radmatI = np.transpose( + np.vstack((iangle2, skyvaultazi, np.zeros((13, len(iangle2))))) ) - radmatD = torch.transpose( - torch.vstack( - ( - iangle2, - skyvaultazi, - torch.zeros((13, len(iangle2)), device=device), - ) - ) + radmatD = np.transpose( + np.vstack((iangle2, skyvaultazi, np.zeros((13, len(iangle2))))) ) - radmatR = torch.transpose( - torch.vstack( - ( - iangle2, - skyvaultazi, - torch.zeros((13, len(iangle2)), device=device), - ) - ) + radmatR = np.transpose( + np.vstack((iangle2, skyvaultazi, np.zeros((13, len(iangle2))))) ) iazimuth = skyvaultazi + # Ta = met[:, 11] + # RH = met[:, 10] # Main loop for i in range(len(met[:, 0])): @@ -91,13 +74,14 @@ def sunmapcreator_2015a( azi = azimuth[0, i] # disp(alt) if alt > 2: - # Estimation of radD and radI if not measured after Reindl et al. (1990) + # Estimation of radD and radI if not measured after Reindl et al. + # (1990) if onlyglobal: if ( met[i, 11] <= -999.00 or met[i, 10] <= -999.00 - or torch.isnan(met[i, 11]) - or torch.isnan(met[i, 10]) + or np.isnan(met[i, 11]) + or np.isnan(met[i, 10]) ): met[i, 11] = 15.0 met[i, 10] = 75.0 @@ -120,7 +104,8 @@ def sunmapcreator_2015a( G = met[i, 14] - # Anisotropic diffuse distribution (Perez et al., 1993/Robinson & Stone, 2004) + # Anisotropic diffuse distribution (Perez et al., 1993/Robinson & + # Stone, 2004) lv, _, _ = Perez_v3( 90 - altitude[0, i], azimuth[0, i], @@ -131,15 +116,18 @@ def sunmapcreator_2015a( patch_option, ) - distalt = torch.abs(iangle2 - alt) - altlevel = distalt == (torch.min(torch.abs(distalt))) - distazi = torch.abs(iazimuth - azi) - azipos = distazi[altlevel] == (torch.min(distazi[altlevel])) - azipos2 = torch.where(altlevel)[0][0] + torch.where(azipos)[0][0] - + distalt = np.abs(iangle2 - alt) + altlevel = distalt == (np.min(np.abs(distalt))) + distazi = np.abs(iazimuth - azi) + azipos = distazi[altlevel] == (np.min(distazi[altlevel])) + azipos2 = np.where(altlevel)[0][0] + np.where(azipos)[0][0] + # azipos2 = np.where(altlevel)[0] + np.where(azipos)[0] + # azipos2 = find(altlevel, 1)-1 + find(azipos, 1) radmatI[azipos2, 2] = radmatI[azipos2, 2] + I radmatD[:, 2] = radmatD[:, 2] + D * lv[:, 2] radmatR[:, 2] = radmatR[:, 2] + G * (1 / 145) * albedo + # Gyear=Gyear+(G*sin(altitude(i)*(pi/180))); + # Dyear=Dyear+D; if output["energymonth"] == 1: radmatI[azipos2, met[i, 1] + 2] = ( @@ -153,11 +141,10 @@ def sunmapcreator_2015a( ) # Adjusting the numbers if multiple years is used - - if torch.shape(met)[0] > 8760: - multiyear = torch.shape(met)[0] / 8760 + if np.shape(met)[0] > 8760: + multiyear = np.shape(met)[0] / 8760 radmatI[:, 2:15] = radmatI[:, 2:15] / multiyear radmatD[:, 2:15] = radmatD[:, 2:15] / multiyear radmatR[:, 2:15] = radmatR[:, 2:15] / multiyear - return radmatI, radmatD, radmatR + return radmatI, radmatD, radmatR \ No newline at end of file diff --git a/functions/SOLWEIGpython/CirclePlotBar.py b/functions/SOLWEIGpython/CirclePlotBar.py index 4f17f30..b23695d 100644 --- a/functions/SOLWEIGpython/CirclePlotBar.py +++ b/functions/SOLWEIGpython/CirclePlotBar.py @@ -4,7 +4,6 @@ import matplotlib.pyplot as plt from matplotlib.patches import Circle import matplotlib.colors as colors -import matplotlib # def PolarBarPlot(lv, radD, outfolder, YYYY, DOY, XH, hours, XM, minu, iStep, idxStep): diff --git a/functions/SOLWEIGpython/Kside_veg_v2022a.py b/functions/SOLWEIGpython/Kside_veg_v2022a.py index 11e379c..7c733ac 100644 --- a/functions/SOLWEIGpython/Kside_veg_v2022a.py +++ b/functions/SOLWEIGpython/Kside_veg_v2022a.py @@ -2,7 +2,6 @@ import numpy as np from .Kvikt_veg import Kvikt_veg from . import sunlit_shaded_patches -import torch def Kside_veg_v2022a( @@ -39,108 +38,107 @@ def Kside_veg_v2022a( vegshmat, vbshvegshmat, ): - device = ( - altitude.device - if isinstance(altitude, torch.Tensor) - else ( - azimuth.device - if isinstance(azimuth, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "cpu") - ) - ) + # New reflection equation 2012-05-25 vikttot = 4.4897 aziE = azimuth + t aziS = azimuth - 90 + t aziW = azimuth - 180 + t aziN = azimuth - 270 + t - deg2rad = torch.pi / 180.0 - deg2rad = torch.tensor(deg2rad) - - KsideD = torch.zeros((rows, cols), device=device) - Kref_sun = torch.zeros((rows, cols), device=device) - Kref_sh = torch.zeros((rows, cols), device=device) - Kref_veg = torch.zeros((rows, cols), device=device) - Kside = torch.zeros((rows, cols), device=device) - - Kref_veg_n = torch.zeros((rows, cols), device=device) - Kref_veg_s = torch.zeros((rows, cols), device=device) - Kref_veg_e = torch.zeros((rows, cols), device=device) - Kref_veg_w = torch.zeros((rows, cols), device=device) - - Kref_sh_n = torch.zeros((rows, cols), device=device) - Kref_sh_s = torch.zeros((rows, cols), device=device) - Kref_sh_e = torch.zeros((rows, cols), device=device) - Kref_sh_w = torch.zeros((rows, cols), device=device) - - Kref_sun_n = torch.zeros((rows, cols), device=device) - Kref_sun_s = torch.zeros((rows, cols), device=device) - Kref_sun_e = torch.zeros((rows, cols), device=device) - Kref_sun_w = torch.zeros((rows, cols), device=device) - - KeastRef = torch.zeros((rows, cols), device=device) - KwestRef = torch.zeros((rows, cols), device=device) - KnorthRef = torch.zeros((rows, cols), device=device) - KsouthRef = torch.zeros((rows, cols), device=device) - diffRadE = torch.zeros((rows, cols), device=device) - diffRadS = torch.zeros((rows, cols), device=device) - diffRadW = torch.zeros((rows, cols), device=device) - diffRadN = torch.zeros((rows, cols), device=device) - - if cyl == 1: - KsideI = shadow * radI * torch.cos(altitude * deg2rad) - KeastI = torch.zeros((rows, cols), device=device) - KsouthI = torch.zeros((rows, cols), device=device) - KwestI = torch.zeros((rows, cols), device=device) - KnorthI = torch.zeros((rows, cols), device=device) - else: - KeastI = torch.where( - (azimuth > (360 - t)) | (azimuth <= (180 - t)), - radI - * shadow - * torch.cos(altitude * deg2rad) - * torch.sin(aziE * deg2rad), - torch.zeros((rows, cols), device=device), - ) - KsouthI = torch.where( - (azimuth > (90 - t)) & (azimuth <= (270 - t)), - radI - * shadow - * torch.cos(altitude * deg2rad) - * torch.sin(aziS * deg2rad), - torch.zeros((rows, cols), device=device), - ) - KwestI = torch.where( - (azimuth > (180 - t)) & (azimuth <= (360 - t)), - radI - * shadow - * torch.cos(altitude * deg2rad) - * torch.sin(aziW * deg2rad), - torch.zeros((rows, cols), device=device), - ) - KnorthI = torch.where( - (azimuth <= (90 - t)) | (azimuth > (270 - t)), - radI - * shadow - * torch.cos(altitude * deg2rad) - * torch.sin(aziN * deg2rad), - torch.zeros((rows, cols), device=device), - ) + deg2rad = np.pi / 180 + KsideD = np.zeros((rows, cols)) + Kref_sun = np.zeros((rows, cols)) + Kref_sh = np.zeros((rows, cols)) + Kref_veg = np.zeros((rows, cols)) + Kside = np.zeros((rows, cols)) + + Kref_veg_n = np.zeros((rows, cols)) + Kref_veg_s = np.zeros((rows, cols)) + Kref_veg_e = np.zeros((rows, cols)) + Kref_veg_w = np.zeros((rows, cols)) + + Kref_sh_n = np.zeros((rows, cols)) + Kref_sh_s = np.zeros((rows, cols)) + Kref_sh_e = np.zeros((rows, cols)) + Kref_sh_w = np.zeros((rows, cols)) + + Kref_sun_n = np.zeros((rows, cols)) + Kref_sun_s = np.zeros((rows, cols)) + Kref_sun_e = np.zeros((rows, cols)) + Kref_sun_w = np.zeros((rows, cols)) + + KeastRef = np.zeros((rows, cols)) + KwestRef = np.zeros((rows, cols)) + KnorthRef = np.zeros((rows, cols)) + KsouthRef = np.zeros((rows, cols)) + diffRadE = np.zeros((rows, cols)) + diffRadS = np.zeros((rows, cols)) + diffRadW = np.zeros((rows, cols)) + diffRadN = np.zeros((rows, cols)) + + ### Direct radiation ### + if cyl == 1: # Kside with cylinder ### + KsideI = shadow * radI * np.cos(altitude * deg2rad) + KeastI = 0 + KsouthI = 0 + KwestI = 0 + KnorthI = 0 + else: # Kside with weights ### + if azimuth > (360 - t) or azimuth <= (180 - t): + KeastI = ( + radI + * shadow + * np.cos(altitude * deg2rad) + * np.sin(aziE * deg2rad) + ) + else: + KeastI = 0 + if azimuth > (90 - t) and azimuth <= (270 - t): + KsouthI = ( + radI + * shadow + * np.cos(altitude * deg2rad) + * np.sin(aziS * deg2rad) + ) + else: + KsouthI = 0 + if azimuth > (180 - t) and azimuth <= (360 - t): + KwestI = ( + radI + * shadow + * np.cos(altitude * deg2rad) + * np.sin(aziW * deg2rad) + ) + else: + KwestI = 0 + if azimuth <= (90 - t) or azimuth > (270 - t): + KnorthI = ( + radI + * shadow + * np.cos(altitude * deg2rad) + * np.sin(aziN * deg2rad) + ) + else: + KnorthI = 0 + KsideI = shadow * 0 - viktveg, viktwall = Kvikt_veg(svfE, svfEveg, vikttot) - svfviktbuvegE = viktwall + (viktveg * (1 - psi)) + ### Diffuse and reflected radiation ### + [viktveg, viktwall] = Kvikt_veg(svfE, svfEveg, vikttot) + svfviktbuvegE = viktwall + (viktveg) * (1 - psi) - viktveg, viktwall = Kvikt_veg(svfS, svfSveg, vikttot) - svfviktbuvegS = viktwall + (viktveg * (1 - psi)) + [viktveg, viktwall] = Kvikt_veg(svfS, svfSveg, vikttot) + svfviktbuvegS = viktwall + (viktveg) * (1 - psi) - viktveg, viktwall = Kvikt_veg(svfW, svfWveg, vikttot) - svfviktbuvegW = viktwall + (viktveg * (1 - psi)) + [viktveg, viktwall] = Kvikt_veg(svfW, svfWveg, vikttot) + svfviktbuvegW = viktwall + (viktveg) * (1 - psi) - viktveg, viktwall = Kvikt_veg(svfN, svfNveg, vikttot) - svfviktbuvegN = viktwall + (viktveg * (1 - psi)) + [viktveg, viktwall] = Kvikt_veg(svfN, svfNveg, vikttot) + svfviktbuvegN = viktwall + (viktveg) * (1 - psi) + ### Anisotropic Diffuse Radiation after Perez et al. 1993 ### if anisotropic_diffuse == 1: + anisotropic_sky = True patch_altitude = lv[:, 0] @@ -148,59 +146,69 @@ def Kside_veg_v2022a( if anisotropic_sky: patch_luminance = lv[:, 2] else: - patch_luminance = ( - torch.ones((patch_altitude.shape[0]), device=device) - / patch_altitude.shape[0] - ) + patch_luminance = np.zeros((patch_altitude.shape[0])) + patch_luminance[:] = 1.0 / patch_luminance.shape[0] + + # Unique altitudes for patches + skyalt, skyalt_c = np.unique(patch_altitude, return_counts=True) + + radTot = np.zeros(1) - skyalt, skyalt_c = torch.unique(patch_altitude, return_counts=True) - radTot = torch.zeros(1, device=device) - steradian = torch.zeros((patch_altitude.shape[0]), device=device) + # Calculation of steradian for each patch + steradian = np.zeros((patch_altitude.shape[0])) for i in range(patch_altitude.shape[0]): + # If there are more than one patch in a band if skyalt_c[skyalt == patch_altitude[i]] > 1: steradian[i] = ( (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad ) * ( - torch.sin( - (patch_altitude[i] + patch_altitude[0]) * deg2rad - ) - - torch.sin( - (patch_altitude[i] - patch_altitude[0]) * deg2rad - ) + np.sin((patch_altitude[i] + patch_altitude[0]) * deg2rad) + - np.sin((patch_altitude[i] - patch_altitude[0]) * deg2rad) ) + # If there is only one patch in band, i.e. 90 degrees else: steradian[i] = ( (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad ) * ( - torch.sin((patch_altitude[i]) * deg2rad) - - torch.sin( + np.sin((patch_altitude[i]) * deg2rad) + - np.sin( (patch_altitude[i - 1] + patch_altitude[0]) * deg2rad ) ) + # Radiance fraction normalization radTot += ( patch_luminance[i] * steradian[i] - * torch.sin(patch_altitude[i] * deg2rad) + * np.sin(patch_altitude[i] * deg2rad) ) + # Radiance fraction normalization lumChi = (patch_luminance * radD) / radTot if cyl == 1: for idx in range(patch_azimuth.shape[0]): - anglIncC = torch.cos( - patch_altitude[idx] * deg2rad - ) * torch.cos(torch.tensor(0.0)) + # Angle of incidence, np.cos(0) because cylinder - always + # perpendicular + anglIncC = np.cos(patch_altitude[idx] * deg2rad) * np.cos( + 0 + ) # * np.sin(np.pi / 2) \ + # + np.sin(patch_altitude[idx] * deg2rad) * np.cos(np.pi / 2) + # Diffuse vertical radiation KsideD += ( diffsh[:, :, idx] * lumChi[idx] * anglIncC * steradian[idx] ) + # Shortwave reflected on sunlit surfaces + # sunlit_surface = ((albedo * radG) / np.pi) sunlit_surface = ( - albedo * (radI * torch.cos(altitude * deg2rad)) - + (radD * 0.5) - ) / torch.pi - shaded_surface = (albedo * radD * 0.5) / torch.pi + albedo * (radI * np.cos(altitude * deg2rad)) + (radD * 0.5) + ) / np.pi + # Shortwave reflected on shaded surfaces and vegetation + shaded_surface = (albedo * radD * 0.5) / np.pi + # Shortwave radiation reflected on vegetation - based on + # diffuse shortwave radiation temp_vegsh = (vegshmat[:, :, idx] == 0) | ( vbshvegshmat[:, :, idx] == 0 ) @@ -208,32 +216,36 @@ def Kside_veg_v2022a( shaded_surface * temp_vegsh * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) ) + # Shortwave radiation reflected on buildings (shaded and + # sunlit) - based on global and diffuse shortwave radiation temp_vbsh = (1 - shmat[:, :, idx]) * vbshvegshmat[:, :, idx] - temp_sh = temp_vbsh == 1 - - sunlit_patches, shaded_patches = shaded_or_sunlit( - altitude, - azimuth, - patch_altitude[idx], - patch_azimuth[idx], - asvf, + temp_sh = temp_vbsh == 1 # & (vbshvegshmat[:,:,idx] == 1) + + sunlit_patches, shaded_patches = ( + sunlit_shaded_patches.shaded_or_sunlit( + altitude, + azimuth, + patch_altitude[idx], + patch_azimuth[idx], + asvf, + ) ) Kref_sun += ( sunlit_surface * sunlit_patches * temp_sh * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) ) Kref_sh += ( shaded_surface * shaded_patches * temp_sh * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) ) Kside = KsideI + KsideD + Kref_sun + Kref_sh + Kref_veg @@ -243,58 +255,75 @@ def Kside_veg_v2022a( Knorth = KupN * 0.5 Ksouth = KupS * 0.5 - else: + # Keast = (albedo * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) + KupE) * 0.5 + # Ksouth = (albedo * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) + KupS) * 0.5 + # Kwest = (albedo * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + KupW) * 0.5 + # Knorth = (albedo * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + KupN) * 0.5 + else: # Box + diffRadE = np.zeros((rows, cols)) + diffRadS = np.zeros((rows, cols)) + diffRadW = np.zeros((rows, cols)) + diffRadN = np.zeros((rows, cols)) + for idx in range(patch_azimuth.shape[0]): if (patch_azimuth[idx] > 360) or (patch_azimuth[idx] <= 180): - anglIncE = torch.cos( - patch_altitude[idx] * deg2rad - ) * torch.cos((90 - patch_azimuth[idx] + t) * deg2rad) + anglIncE = np.cos(patch_altitude[idx] * deg2rad) * np.cos( + (90 - patch_azimuth[idx] + t) * deg2rad + ) # * np.sin(np.pi / 2) \ + # + np.sin(patch_altitude[idx] * deg2rad) * np.cos(np.pi / 2) diffRadE += ( diffsh[:, :, idx] * lumChi[idx] * anglIncE * steradian[idx] - ) + ) # * 0.5 if (patch_azimuth[idx] > 90) and (patch_azimuth[idx] <= 270): - anglIncS = torch.cos( - patch_altitude[idx] * deg2rad - ) * torch.cos((180 - patch_azimuth[idx] + t) * deg2rad) + anglIncS = np.cos(patch_altitude[idx] * deg2rad) * np.cos( + (180 - patch_azimuth[idx] + t) * deg2rad + ) # * np.sin(np.pi / 2) \ + # + np.sin(patch_altitude[idx] * deg2rad) * np.cos(np.pi / 2) diffRadS += ( diffsh[:, :, idx] * lumChi[idx] * anglIncS * steradian[idx] - ) + ) # * 0.5 if (patch_azimuth[idx] > 180) and (patch_azimuth[idx] <= 360): - anglIncW = torch.cos( - patch_altitude[idx] * deg2rad - ) * torch.cos((270 - patch_azimuth[idx] + t) * deg2rad) + anglIncW = np.cos(patch_altitude[idx] * deg2rad) * np.cos( + (270 - patch_azimuth[idx] + t) * deg2rad + ) # * np.sin(np.pi / 2) \ + # + np.sin(patch_altitude[idx] * deg2rad) * np.cos(np.pi / 2) diffRadW += ( diffsh[:, :, idx] * lumChi[idx] * anglIncW * steradian[idx] - ) + ) # * 0.5 if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] <= 90): - anglIncN = torch.cos( - patch_altitude[idx] * deg2rad - ) * torch.cos((0 - patch_azimuth[idx] + t) * deg2rad) + anglIncN = np.cos(patch_altitude[idx] * deg2rad) * np.cos( + (0 - patch_azimuth[idx] + t) * deg2rad + ) # * np.sin(np.pi / 2) \ + # + np.sin(patch_altitude[idx] * deg2rad) * np.cos(np.pi / 2) diffRadN += ( diffsh[:, :, idx] * lumChi[idx] * anglIncN * steradian[idx] - ) + ) # * 0.5 + # Shortwave reflected on sunlit surfaces + # sunlit_surface = ((albedo * radG) / np.pi) sunlit_surface = ( - albedo * (radI * torch.cos(altitude * deg2rad)) - + (radD * 0.5) - ) / torch.pi - shaded_surface = (albedo * radD * 0.5) / torch.pi + albedo * (radI * np.cos(altitude * deg2rad)) + (radD * 0.5) + ) / np.pi + # Shortwave reflected on shaded surfaces and vegetation + shaded_surface = (albedo * radD * 0.5) / np.pi + # Shortwave radiation reflected on vegetation - based on + # diffuse shortwave radiation temp_vegsh = (vegshmat[:, :, idx] == 0) | ( vbshvegshmat[:, :, idx] == 0 ) @@ -302,67 +331,71 @@ def Kside_veg_v2022a( shaded_surface * temp_vegsh * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) ) if (patch_azimuth[idx] > 360) or (patch_azimuth[idx] < 180): Kref_veg_e += ( shaded_surface * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) * temp_vegsh - * torch.cos((90 - patch_azimuth[idx] + t) * deg2rad) + * np.cos((90 - patch_azimuth[idx] + t) * deg2rad) ) if (patch_azimuth[idx] > 90) and (patch_azimuth[idx] < 270): Kref_veg_s += ( shaded_surface * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) * temp_vegsh - * torch.cos((180 - patch_azimuth[idx] + t) * deg2rad) + * np.cos((180 - patch_azimuth[idx] + t) * deg2rad) ) if (patch_azimuth[idx] > 180) and (patch_azimuth[idx] < 360): Kref_veg_w += ( shaded_surface * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) * temp_vegsh - * torch.cos((270 - patch_azimuth[idx] + t) * deg2rad) + * np.cos((270 - patch_azimuth[idx] + t) * deg2rad) ) if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] < 90): Kref_veg_n += ( shaded_surface * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) * temp_vegsh - * torch.cos((0 - patch_azimuth[idx] + t) * deg2rad) + * np.cos((0 - patch_azimuth[idx] + t) * deg2rad) ) + # Shortwave radiation reflected on buildings (shaded and + # sunlit) - based on global and diffuse shortwave radiation temp_vbsh = (1 - shmat[:, :, idx]) * vbshvegshmat[:, :, idx] - temp_sh = temp_vbsh == 1 - azimuth_difference = torch.abs(azimuth - patch_azimuth[idx]) + temp_sh = temp_vbsh == 1 # & (vbshvegshmat[:,:,idx] == 1) + azimuth_difference = np.abs(azimuth - patch_azimuth[idx]) if (azimuth_difference > 90) and (azimuth_difference < 270): - sunlit_patches, shaded_patches = shaded_or_sunlit( - altitude, - azimuth, - patch_altitude[idx], - patch_azimuth[idx], - asvf, + sunlit_patches, shaded_patches = ( + sunlit_shaded_patches.shaded_or_sunlit( + altitude, + azimuth, + patch_altitude[idx], + patch_azimuth[idx], + asvf, + ) ) Kref_sun += ( sunlit_surface * sunlit_patches * temp_sh * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) ) Kref_sh += ( shaded_surface * shaded_patches * temp_sh * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) ) if (patch_azimuth[idx] > 360) or ( @@ -372,21 +405,17 @@ def Kside_veg_v2022a( sunlit_surface * sunlit_patches * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos( - (90 - patch_azimuth[idx] + t) * deg2rad - ) + * np.cos((90 - patch_azimuth[idx] + t) * deg2rad) ) Kref_sh_e += ( shaded_surface * shaded_patches * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos( - (90 - patch_azimuth[idx] + t) * deg2rad - ) + * np.cos((90 - patch_azimuth[idx] + t) * deg2rad) ) if (patch_azimuth[idx] > 90) and ( patch_azimuth[idx] < 270 @@ -395,21 +424,17 @@ def Kside_veg_v2022a( sunlit_surface * sunlit_patches * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos( - (180 - patch_azimuth[idx] + t) * deg2rad - ) + * np.cos((180 - patch_azimuth[idx] + t) * deg2rad) ) Kref_sh_s += ( shaded_surface * shaded_patches * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos( - (180 - patch_azimuth[idx] + t) * deg2rad - ) + * np.cos((180 - patch_azimuth[idx] + t) * deg2rad) ) if (patch_azimuth[idx] > 180) and ( patch_azimuth[idx] < 360 @@ -418,45 +443,41 @@ def Kside_veg_v2022a( sunlit_surface * sunlit_patches * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos( - (270 - patch_azimuth[idx] + t) * deg2rad - ) + * np.cos((270 - patch_azimuth[idx] + t) * deg2rad) ) Kref_sh_w += ( shaded_surface * shaded_patches * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos( - (270 - patch_azimuth[idx] + t) * deg2rad - ) + * np.cos((270 - patch_azimuth[idx] + t) * deg2rad) ) if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] < 90): Kref_sun_n += ( sunlit_surface * sunlit_patches * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos((0 - patch_azimuth[idx] + t) * deg2rad) + * np.cos((0 - patch_azimuth[idx] + t) * deg2rad) ) Kref_sh_n += ( shaded_surface * shaded_patches * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos((0 - patch_azimuth[idx] + t) * deg2rad) + * np.cos((0 - patch_azimuth[idx] + t) * deg2rad) ) else: Kref_sh += ( shaded_surface * temp_sh * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) ) if (patch_azimuth[idx] > 360) or ( @@ -465,11 +486,9 @@ def Kside_veg_v2022a( Kref_sh_e += ( shaded_surface * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos( - (90 - patch_azimuth[idx] + t) * deg2rad - ) + * np.cos((90 - patch_azimuth[idx] + t) * deg2rad) ) if (patch_azimuth[idx] > 90) and ( patch_azimuth[idx] < 270 @@ -477,11 +496,9 @@ def Kside_veg_v2022a( Kref_sh_s += ( shaded_surface * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos( - (180 - patch_azimuth[idx] + t) * deg2rad - ) + * np.cos((180 - patch_azimuth[idx] + t) * deg2rad) ) if (patch_azimuth[idx] > 180) and ( patch_azimuth[idx] < 360 @@ -489,19 +506,17 @@ def Kside_veg_v2022a( Kref_sh_w += ( shaded_surface * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos( - (270 - patch_azimuth[idx] + t) * deg2rad - ) + * np.cos((270 - patch_azimuth[idx] + t) * deg2rad) ) if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] < 90): Kref_sh_n += ( shaded_surface * steradian[idx] - * torch.cos(patch_altitude[idx] * deg2rad) + * np.cos(patch_altitude[idx] * deg2rad) * temp_sh - * torch.cos((0 - patch_azimuth[idx] + t) * deg2rad) + * np.cos((0 - patch_azimuth[idx] + t) * deg2rad) ) Keast = ( @@ -567,3 +582,5 @@ def Kside_veg_v2022a( Knorth = KnorthI + KnorthDG return Keast, Ksouth, Kwest, Knorth, KsideI, KsideD, Kside + # return Keast, Ksouth, Kwest, Knorth, KsideI, KsideD, Kref_sh, Kref_sun, + # Kref_veg, Kside diff --git a/functions/SOLWEIGpython/Kside_veg_v2022a_torch.py b/functions/SOLWEIGpython/Kside_veg_v2022a_torch.py new file mode 100644 index 0000000..9d37c4d --- /dev/null +++ b/functions/SOLWEIGpython/Kside_veg_v2022a_torch.py @@ -0,0 +1,569 @@ +from __future__ import absolute_import +from .Kvikt_veg import Kvikt_veg +try: + import torch +except: + pass + +def Kside_veg_v2022a( + radI, + radD, + radG, + shadow, + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + azimuth, + altitude, + psi, + t, + albedo, + F_sh, + KupE, + KupS, + KupW, + KupN, + cyl, + lv, + anisotropic_diffuse, + diffsh, + rows, + cols, + asvf, + shmat, + vegshmat, + vbshvegshmat, +): + device = ( + altitude.device + if isinstance(altitude, torch.Tensor) + else ( + azimuth.device + if isinstance(azimuth, torch.Tensor) + else torch.device("cuda" if torch.cuda.is_available() else "cpu") + ) + ) + + vikttot = 4.4897 + aziE = azimuth + t + aziS = azimuth - 90 + t + aziW = azimuth - 180 + t + aziN = azimuth - 270 + t + deg2rad = torch.pi / 180.0 + deg2rad = torch.tensor(deg2rad) + + KsideD = torch.zeros((rows, cols), device=device) + Kref_sun = torch.zeros((rows, cols), device=device) + Kref_sh = torch.zeros((rows, cols), device=device) + Kref_veg = torch.zeros((rows, cols), device=device) + Kside = torch.zeros((rows, cols), device=device) + + Kref_veg_n = torch.zeros((rows, cols), device=device) + Kref_veg_s = torch.zeros((rows, cols), device=device) + Kref_veg_e = torch.zeros((rows, cols), device=device) + Kref_veg_w = torch.zeros((rows, cols), device=device) + + Kref_sh_n = torch.zeros((rows, cols), device=device) + Kref_sh_s = torch.zeros((rows, cols), device=device) + Kref_sh_e = torch.zeros((rows, cols), device=device) + Kref_sh_w = torch.zeros((rows, cols), device=device) + + Kref_sun_n = torch.zeros((rows, cols), device=device) + Kref_sun_s = torch.zeros((rows, cols), device=device) + Kref_sun_e = torch.zeros((rows, cols), device=device) + Kref_sun_w = torch.zeros((rows, cols), device=device) + + KeastRef = torch.zeros((rows, cols), device=device) + KwestRef = torch.zeros((rows, cols), device=device) + KnorthRef = torch.zeros((rows, cols), device=device) + KsouthRef = torch.zeros((rows, cols), device=device) + diffRadE = torch.zeros((rows, cols), device=device) + diffRadS = torch.zeros((rows, cols), device=device) + diffRadW = torch.zeros((rows, cols), device=device) + diffRadN = torch.zeros((rows, cols), device=device) + + if cyl == 1: + KsideI = shadow * radI * torch.cos(altitude * deg2rad) + KeastI = torch.zeros((rows, cols), device=device) + KsouthI = torch.zeros((rows, cols), device=device) + KwestI = torch.zeros((rows, cols), device=device) + KnorthI = torch.zeros((rows, cols), device=device) + else: + KeastI = torch.where( + (azimuth > (360 - t)) | (azimuth <= (180 - t)), + radI + * shadow + * torch.cos(altitude * deg2rad) + * torch.sin(aziE * deg2rad), + torch.zeros((rows, cols), device=device), + ) + KsouthI = torch.where( + (azimuth > (90 - t)) & (azimuth <= (270 - t)), + radI + * shadow + * torch.cos(altitude * deg2rad) + * torch.sin(aziS * deg2rad), + torch.zeros((rows, cols), device=device), + ) + KwestI = torch.where( + (azimuth > (180 - t)) & (azimuth <= (360 - t)), + radI + * shadow + * torch.cos(altitude * deg2rad) + * torch.sin(aziW * deg2rad), + torch.zeros((rows, cols), device=device), + ) + KnorthI = torch.where( + (azimuth <= (90 - t)) | (azimuth > (270 - t)), + radI + * shadow + * torch.cos(altitude * deg2rad) + * torch.sin(aziN * deg2rad), + torch.zeros((rows, cols), device=device), + ) + KsideI = shadow * 0 + + viktveg, viktwall = Kvikt_veg(svfE, svfEveg, vikttot) + svfviktbuvegE = viktwall + (viktveg * (1 - psi)) + + viktveg, viktwall = Kvikt_veg(svfS, svfSveg, vikttot) + svfviktbuvegS = viktwall + (viktveg * (1 - psi)) + + viktveg, viktwall = Kvikt_veg(svfW, svfWveg, vikttot) + svfviktbuvegW = viktwall + (viktveg * (1 - psi)) + + viktveg, viktwall = Kvikt_veg(svfN, svfNveg, vikttot) + svfviktbuvegN = viktwall + (viktveg * (1 - psi)) + + if anisotropic_diffuse == 1: + anisotropic_sky = True + + patch_altitude = lv[:, 0] + patch_azimuth = lv[:, 1] + if anisotropic_sky: + patch_luminance = lv[:, 2] + else: + patch_luminance = ( + torch.ones((patch_altitude.shape[0]), device=device) + / patch_altitude.shape[0] + ) + + skyalt, skyalt_c = torch.unique(patch_altitude, return_counts=True) + radTot = torch.zeros(1, device=device) + steradian = torch.zeros((patch_altitude.shape[0]), device=device) + for i in range(patch_altitude.shape[0]): + if skyalt_c[skyalt == patch_altitude[i]] > 1: + steradian[i] = ( + (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad + ) * ( + torch.sin( + (patch_altitude[i] + patch_altitude[0]) * deg2rad + ) + - torch.sin( + (patch_altitude[i] - patch_altitude[0]) * deg2rad + ) + ) + else: + steradian[i] = ( + (360 / skyalt_c[skyalt == patch_altitude[i]]) * deg2rad + ) * ( + torch.sin((patch_altitude[i]) * deg2rad) + - torch.sin( + (patch_altitude[i - 1] + patch_altitude[0]) * deg2rad + ) + ) + + radTot += ( + patch_luminance[i] + * steradian[i] + * torch.sin(patch_altitude[i] * deg2rad) + ) + + lumChi = (patch_luminance * radD) / radTot + + if cyl == 1: + for idx in range(patch_azimuth.shape[0]): + anglIncC = torch.cos( + patch_altitude[idx] * deg2rad + ) * torch.cos(torch.tensor(0.0)) + KsideD += ( + diffsh[:, :, idx] * lumChi[idx] * anglIncC * steradian[idx] + ) + + sunlit_surface = ( + albedo * (radI * torch.cos(altitude * deg2rad)) + + (radD * 0.5) + ) / torch.pi + shaded_surface = (albedo * radD * 0.5) / torch.pi + + temp_vegsh = (vegshmat[:, :, idx] == 0) | ( + vbshvegshmat[:, :, idx] == 0 + ) + Kref_veg += ( + shaded_surface + * temp_vegsh + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + ) + + temp_vbsh = (1 - shmat[:, :, idx]) * vbshvegshmat[:, :, idx] + temp_sh = temp_vbsh == 1 + + sunlit_patches, shaded_patches = shaded_or_sunlit( + altitude, + azimuth, + patch_altitude[idx], + patch_azimuth[idx], + asvf, + ) + Kref_sun += ( + sunlit_surface + * sunlit_patches + * temp_sh + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + ) + Kref_sh += ( + shaded_surface + * shaded_patches + * temp_sh + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + ) + + Kside = KsideI + KsideD + Kref_sun + Kref_sh + Kref_veg + + Keast = KupE * 0.5 + Kwest = KupW * 0.5 + Knorth = KupN * 0.5 + Ksouth = KupS * 0.5 + + else: + for idx in range(patch_azimuth.shape[0]): + if (patch_azimuth[idx] > 360) or (patch_azimuth[idx] <= 180): + anglIncE = torch.cos( + patch_altitude[idx] * deg2rad + ) * torch.cos((90 - patch_azimuth[idx] + t) * deg2rad) + diffRadE += ( + diffsh[:, :, idx] + * lumChi[idx] + * anglIncE + * steradian[idx] + ) + + if (patch_azimuth[idx] > 90) and (patch_azimuth[idx] <= 270): + anglIncS = torch.cos( + patch_altitude[idx] * deg2rad + ) * torch.cos((180 - patch_azimuth[idx] + t) * deg2rad) + diffRadS += ( + diffsh[:, :, idx] + * lumChi[idx] + * anglIncS + * steradian[idx] + ) + + if (patch_azimuth[idx] > 180) and (patch_azimuth[idx] <= 360): + anglIncW = torch.cos( + patch_altitude[idx] * deg2rad + ) * torch.cos((270 - patch_azimuth[idx] + t) * deg2rad) + diffRadW += ( + diffsh[:, :, idx] + * lumChi[idx] + * anglIncW + * steradian[idx] + ) + + if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] <= 90): + anglIncN = torch.cos( + patch_altitude[idx] * deg2rad + ) * torch.cos((0 - patch_azimuth[idx] + t) * deg2rad) + diffRadN += ( + diffsh[:, :, idx] + * lumChi[idx] + * anglIncN + * steradian[idx] + ) + + sunlit_surface = ( + albedo * (radI * torch.cos(altitude * deg2rad)) + + (radD * 0.5) + ) / torch.pi + shaded_surface = (albedo * radD * 0.5) / torch.pi + + temp_vegsh = (vegshmat[:, :, idx] == 0) | ( + vbshvegshmat[:, :, idx] == 0 + ) + Kref_veg += ( + shaded_surface + * temp_vegsh + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + ) + + if (patch_azimuth[idx] > 360) or (patch_azimuth[idx] < 180): + Kref_veg_e += ( + shaded_surface + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + * temp_vegsh + * torch.cos((90 - patch_azimuth[idx] + t) * deg2rad) + ) + if (patch_azimuth[idx] > 90) and (patch_azimuth[idx] < 270): + Kref_veg_s += ( + shaded_surface + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + * temp_vegsh + * torch.cos((180 - patch_azimuth[idx] + t) * deg2rad) + ) + if (patch_azimuth[idx] > 180) and (patch_azimuth[idx] < 360): + Kref_veg_w += ( + shaded_surface + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + * temp_vegsh + * torch.cos((270 - patch_azimuth[idx] + t) * deg2rad) + ) + if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] < 90): + Kref_veg_n += ( + shaded_surface + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + * temp_vegsh + * torch.cos((0 - patch_azimuth[idx] + t) * deg2rad) + ) + + temp_vbsh = (1 - shmat[:, :, idx]) * vbshvegshmat[:, :, idx] + temp_sh = temp_vbsh == 1 + azimuth_difference = torch.abs(azimuth - patch_azimuth[idx]) + + if (azimuth_difference > 90) and (azimuth_difference < 270): + sunlit_patches, shaded_patches = shaded_or_sunlit( + altitude, + azimuth, + patch_altitude[idx], + patch_azimuth[idx], + asvf, + ) + Kref_sun += ( + sunlit_surface + * sunlit_patches + * temp_sh + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + ) + Kref_sh += ( + shaded_surface + * shaded_patches + * temp_sh + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + ) + + if (patch_azimuth[idx] > 360) or ( + patch_azimuth[idx] < 180 + ): + Kref_sun_e += ( + sunlit_surface + * sunlit_patches + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * torch.cos( + (90 - patch_azimuth[idx] + t) * deg2rad + ) + ) + Kref_sh_e += ( + shaded_surface + * shaded_patches + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * torch.cos( + (90 - patch_azimuth[idx] + t) * deg2rad + ) + ) + if (patch_azimuth[idx] > 90) and ( + patch_azimuth[idx] < 270 + ): + Kref_sun_s += ( + sunlit_surface + * sunlit_patches + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * torch.cos( + (180 - patch_azimuth[idx] + t) * deg2rad + ) + ) + Kref_sh_s += ( + shaded_surface + * shaded_patches + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * torch.cos( + (180 - patch_azimuth[idx] + t) * deg2rad + ) + ) + if (patch_azimuth[idx] > 180) and ( + patch_azimuth[idx] < 360 + ): + Kref_sun_w += ( + sunlit_surface + * sunlit_patches + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * torch.cos( + (270 - patch_azimuth[idx] + t) * deg2rad + ) + ) + Kref_sh_w += ( + shaded_surface + * shaded_patches + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * torch.cos( + (270 - patch_azimuth[idx] + t) * deg2rad + ) + ) + if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] < 90): + Kref_sun_n += ( + sunlit_surface + * sunlit_patches + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * torch.cos((0 - patch_azimuth[idx] + t) * deg2rad) + ) + Kref_sh_n += ( + shaded_surface + * shaded_patches + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * torch.cos((0 - patch_azimuth[idx] + t) * deg2rad) + ) + else: + Kref_sh += ( + shaded_surface + * temp_sh + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + ) + + if (patch_azimuth[idx] > 360) or ( + patch_azimuth[idx] < 180 + ): + Kref_sh_e += ( + shaded_surface + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * torch.cos( + (90 - patch_azimuth[idx] + t) * deg2rad + ) + ) + if (patch_azimuth[idx] > 90) and ( + patch_azimuth[idx] < 270 + ): + Kref_sh_s += ( + shaded_surface + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * torch.cos( + (180 - patch_azimuth[idx] + t) * deg2rad + ) + ) + if (patch_azimuth[idx] > 180) and ( + patch_azimuth[idx] < 360 + ): + Kref_sh_w += ( + shaded_surface + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * torch.cos( + (270 - patch_azimuth[idx] + t) * deg2rad + ) + ) + if (patch_azimuth[idx] > 270) or (patch_azimuth[idx] < 90): + Kref_sh_n += ( + shaded_surface + * steradian[idx] + * torch.cos(patch_altitude[idx] * deg2rad) + * temp_sh + * torch.cos((0 - patch_azimuth[idx] + t) * deg2rad) + ) + + Keast = ( + KeastI + + diffRadE + + Kref_sun_e + + Kref_sh_e + + Kref_veg_e + + KupE * 0.5 + ) + Kwest = ( + KwestI + + diffRadW + + Kref_sun_w + + Kref_sh_w + + Kref_veg_w + + KupW * 0.5 + ) + Knorth = ( + KnorthI + + diffRadN + + Kref_sun_n + + Kref_sh_n + + Kref_veg_n + + KupN * 0.5 + ) + Ksouth = ( + KsouthI + + diffRadS + + Kref_sun_s + + Kref_sh_s + + Kref_veg_s + + KupS * 0.5 + ) + + else: + KeastDG = ( + radD * (1 - svfviktbuvegE) + + albedo * (svfviktbuvegE * (radG * (1 - F_sh) + radD * F_sh)) + + KupE + ) * 0.5 + Keast = KeastI + KeastDG + + KsouthDG = ( + radD * (1 - svfviktbuvegS) + + albedo * (svfviktbuvegS * (radG * (1 - F_sh) + radD * F_sh)) + + KupS + ) * 0.5 + Ksouth = KsouthI + KsouthDG + + KwestDG = ( + radD * (1 - svfviktbuvegW) + + albedo * (svfviktbuvegW * (radG * (1 - F_sh) + radD * F_sh)) + + KupW + ) * 0.5 + Kwest = KwestI + KwestDG + + KnorthDG = ( + radD * (1 - svfviktbuvegN) + + albedo * (svfviktbuvegN * (radG * (1 - F_sh) + radD * F_sh)) + + KupN + ) * 0.5 + Knorth = KnorthI + KnorthDG + + return Keast, Ksouth, Kwest, Knorth, KsideI, KsideD, Kside diff --git a/functions/SOLWEIGpython/Kup_veg_2015a.py b/functions/SOLWEIGpython/Kup_veg_2015a.py index 2b1b9b8..d2c7f69 100644 --- a/functions/SOLWEIGpython/Kup_veg_2015a.py +++ b/functions/SOLWEIGpython/Kup_veg_2015a.py @@ -1,5 +1,4 @@ import numpy as np -import torch def Kup_veg_2015a( @@ -22,27 +21,27 @@ def Kup_veg_2015a( gvfalbnoshN, ): - Kup = (gvfalb * radI * torch.sin(altitude * (torch.pi / 180.0))) + ( + Kup = (gvfalb * radI * np.sin(altitude * (np.pi / 180.0))) + ( radD * svfbuveg + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) ) * gvfalbnosh - KupE = (gvfalbE * radI * torch.sin(altitude * (torch.pi / 180.0))) + ( + KupE = (gvfalbE * radI * np.sin(altitude * (np.pi / 180.0))) + ( radD * svfbuveg + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) ) * gvfalbnoshE - KupS = (gvfalbS * radI * torch.sin(altitude * (torch.pi / 180.0))) + ( + KupS = (gvfalbS * radI * np.sin(altitude * (np.pi / 180.0))) + ( radD * svfbuveg + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) ) * gvfalbnoshS - KupW = (gvfalbW * radI * torch.sin(altitude * (torch.pi / 180.0))) + ( + KupW = (gvfalbW * radI * np.sin(altitude * (np.pi / 180.0))) + ( radD * svfbuveg + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) ) * gvfalbnoshW - KupN = (gvfalbN * radI * torch.sin(altitude * (torch.pi / 180.0))) + ( + KupN = (gvfalbN * radI * np.sin(altitude * (np.pi / 180.0))) + ( radD * svfbuveg + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) ) * gvfalbnoshN diff --git a/functions/SOLWEIGpython/Kup_veg_2015a_torch.py b/functions/SOLWEIGpython/Kup_veg_2015a_torch.py new file mode 100644 index 0000000..dff01c6 --- /dev/null +++ b/functions/SOLWEIGpython/Kup_veg_2015a_torch.py @@ -0,0 +1,51 @@ +try: + import torch +except: + pass + +def Kup_veg_2015a( + radI, + radD, + radG, + altitude, + svfbuveg, + albedo_b, + F_sh, + gvfalb, + gvfalbE, + gvfalbS, + gvfalbW, + gvfalbN, + gvfalbnosh, + gvfalbnoshE, + gvfalbnoshS, + gvfalbnoshW, + gvfalbnoshN, +): + + Kup = (gvfalb * radI * torch.sin(altitude * (torch.pi / 180.0))) + ( + radD * svfbuveg + + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) + ) * gvfalbnosh + + KupE = (gvfalbE * radI * torch.sin(altitude * (torch.pi / 180.0))) + ( + radD * svfbuveg + + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) + ) * gvfalbnoshE + + KupS = (gvfalbS * radI * torch.sin(altitude * (torch.pi / 180.0))) + ( + radD * svfbuveg + + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) + ) * gvfalbnoshS + + KupW = (gvfalbW * radI * torch.sin(altitude * (torch.pi / 180.0))) + ( + radD * svfbuveg + + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) + ) * gvfalbnoshW + + KupN = (gvfalbN * radI * torch.sin(altitude * (torch.pi / 180.0))) + ( + radD * svfbuveg + + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) + ) * gvfalbnoshN + + return Kup, KupE, KupS, KupW, KupN diff --git a/functions/SOLWEIGpython/Lside_veg.py b/functions/SOLWEIGpython/Lside_veg.py index b54d1a3..da67dc7 100644 --- a/functions/SOLWEIGpython/Lside_veg.py +++ b/functions/SOLWEIGpython/Lside_veg.py @@ -1,7 +1,6 @@ from __future__ import absolute_import import numpy as np from .Lvikt_veg import Lvikt_veg -import torch def Lside_veg_v2022a( @@ -36,35 +35,12 @@ def Lside_veg_v2022a( ): # This m-file is the current one that estimates L from the four cardinal points 20100414 - device = ( - Ldown.device - if isinstance(Ldown, torch.Tensor) - else ( - Ta.device - if isinstance(Ta, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "cpu") - ) - ) - - # Convert all SVF inputs to the same device - svfS = torch.as_tensor(svfS, device=device) - svfW = torch.as_tensor(svfW, device=device) - svfN = torch.as_tensor(svfN, device=device) - svfE = torch.as_tensor(svfE, device=device) - svfEveg = torch.as_tensor(svfEveg, device=device) - svfSveg = torch.as_tensor(svfSveg, device=device) - svfWveg = torch.as_tensor(svfWveg, device=device) - svfNveg = torch.as_tensor(svfNveg, device=device) - svfEaveg = torch.as_tensor(svfEaveg, device=device) - svfSaveg = torch.as_tensor(svfSaveg, device=device) - svfWaveg = torch.as_tensor(svfWaveg, device=device) - svfNaveg = torch.as_tensor(svfNaveg, device=device) # Building height angle from svf - svfalfaE = torch.arcsin(torch.exp((torch.log(1 - svfE)) / 2)) - svfalfaS = torch.arcsin(torch.exp((torch.log(1 - svfS)) / 2)) - svfalfaW = torch.arcsin(torch.exp((torch.log(1 - svfW)) / 2)) - svfalfaN = torch.arcsin(torch.exp((torch.log(1 - svfN)) / 2)) + svfalfaE = np.arcsin(np.exp((np.log(1 - svfE)) / 2)) + svfalfaS = np.arcsin(np.exp((np.log(1 - svfS)) / 2)) + svfalfaW = np.arcsin(np.exp((np.log(1 - svfW)) / 2)) + svfalfaN = np.arcsin(np.exp((np.log(1 - svfN)) / 2)) vikttot = 4.4897 aziW = azimuth + t @@ -85,21 +61,18 @@ def Lside_veg_v2022a( ) if altitude > 0: # daytime - alfaB = torch.arctan(svfalfaE) - betaB = torch.arctan(torch.tan((svfalfaE) * F_sh)) + alfaB = np.arctan(svfalfaE) + betaB = np.arctan(np.tan((svfalfaE) * F_sh)) betasun = ((alfaB - betaB) / 2) + betaB - # betasun = torch.arctan(0.5*torch.tan(svfalfaE)*(1+F_sh)) #TODO This should be considered in future versions + # betasun = np.arctan(0.5*np.tan(svfalfaE)*(1+F_sh)) #TODO This should be considered in future versions if (azimuth > (180 - t)) and (azimuth <= (360 - t)): Lwallsun = ( SBC * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziE * (torch.pi / 180))) - ** 4 - ) + * ((Ta + 273.15 + Tw * np.sin(aziE * (np.pi / 180))) ** 4) * viktwall * (1 - F_sh) - * torch.cos(betasun) + * np.cos(betasun) * 0.5 ) Lwallsh = ( @@ -131,21 +104,18 @@ def Lside_veg_v2022a( ) if altitude > 0: # daytime - alfaB = torch.arctan(svfalfaS) - betaB = torch.arctan(torch.tan((svfalfaS) * F_sh)) + alfaB = np.arctan(svfalfaS) + betaB = np.arctan(np.tan((svfalfaS) * F_sh)) betasun = ((alfaB - betaB) / 2) + betaB - # betasun = torch.arctan(0.5*torch.tan(svfalfaS)*(1+F_sh)) + # betasun = np.arctan(0.5*np.tan(svfalfaS)*(1+F_sh)) if (azimuth <= (90 - t)) or (azimuth > (270 - t)): Lwallsun = ( SBC * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziS * (torch.pi / 180))) - ** 4 - ) + * ((Ta + 273.15 + Tw * np.sin(aziS * (np.pi / 180))) ** 4) * viktwall * (1 - F_sh) - * torch.cos(betasun) + * np.cos(betasun) * 0.5 ) Lwallsh = ( @@ -177,21 +147,18 @@ def Lside_veg_v2022a( ) if altitude > 0: # daytime - alfaB = torch.arctan(svfalfaW) - betaB = torch.arctan(torch.tan((svfalfaW) * F_sh)) + alfaB = np.arctan(svfalfaW) + betaB = np.arctan(np.tan((svfalfaW) * F_sh)) betasun = ((alfaB - betaB) / 2) + betaB - # betasun = torch.arctan(0.5*torch.tan(svfalfaW)*(1+F_sh)) + # betasun = np.arctan(0.5*np.tan(svfalfaW)*(1+F_sh)) if (azimuth > (360 - t)) or (azimuth <= (180 - t)): Lwallsun = ( SBC * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziW * (torch.pi / 180))) - ** 4 - ) + * ((Ta + 273.15 + Tw * np.sin(aziW * (np.pi / 180))) ** 4) * viktwall * (1 - F_sh) - * torch.cos(betasun) + * np.cos(betasun) * 0.5 ) Lwallsh = ( @@ -223,21 +190,18 @@ def Lside_veg_v2022a( ) if altitude > 0: # daytime - alfaB = torch.arctan(svfalfaN) - betaB = torch.arctan(torch.tan((svfalfaN) * F_sh)) + alfaB = np.arctan(svfalfaN) + betaB = np.arctan(np.tan((svfalfaN) * F_sh)) betasun = ((alfaB - betaB) / 2) + betaB - # betasun = torch.arctan(0.5*torch.tan(svfalfaN)*(1+F_sh)) + # betasun = np.arctan(0.5*np.tan(svfalfaN)*(1+F_sh)) if (azimuth > (90 - t)) and (azimuth <= (270 - t)): Lwallsun = ( SBC * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziN * (torch.pi / 180))) - ** 4 - ) + * ((Ta + 273.15 + Tw * np.sin(aziN * (np.pi / 180))) ** 4) * viktwall * (1 - F_sh) - * torch.cos(betasun) + * np.cos(betasun) * 0.5 ) Lwallsh = ( @@ -294,35 +258,12 @@ def Lside_veg_v2026( ): # This m-file is the current one that estimates L from the four cardinal points 20100414 - device = ( - Ldown.device - if isinstance(Ldown, torch.Tensor) - else ( - Ta.device - if isinstance(Ta, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "cpu") - ) - ) - - # Convert all SVF inputs to the same device - svfS = torch.as_tensor(svfS, device=device) - svfW = torch.as_tensor(svfW, device=device) - svfN = torch.as_tensor(svfN, device=device) - svfE = torch.as_tensor(svfE, device=device) - svfEveg = torch.as_tensor(svfEveg, device=device) - svfSveg = torch.as_tensor(svfSveg, device=device) - svfWveg = torch.as_tensor(svfWveg, device=device) - svfNveg = torch.as_tensor(svfNveg, device=device) - svfEaveg = torch.as_tensor(svfEaveg, device=device) - svfSaveg = torch.as_tensor(svfSaveg, device=device) - svfWaveg = torch.as_tensor(svfWaveg, device=device) - svfNaveg = torch.as_tensor(svfNaveg, device=device) # Building height angle from svf - svfalfaE = torch.arcsin(torch.exp((torch.log(1 - svfE)) / 2)) - svfalfaS = torch.arcsin(torch.exp((torch.log(1 - svfS)) / 2)) - svfalfaW = torch.arcsin(torch.exp((torch.log(1 - svfW)) / 2)) - svfalfaN = torch.arcsin(torch.exp((torch.log(1 - svfN)) / 2)) + svfalfaE = np.arcsin(np.exp((np.log(1 - svfE)) / 2)) + svfalfaS = np.arcsin(np.exp((np.log(1 - svfS)) / 2)) + svfalfaW = np.arcsin(np.exp((np.log(1 - svfW)) / 2)) + svfalfaN = np.arcsin(np.exp((np.log(1 - svfN)) / 2)) vikttot = 4.4897 aziW = azimuth + t @@ -343,29 +284,26 @@ def Lside_veg_v2026( ) if anisotropic_longwave == 1: - Least = torch.zeros_like(Ldown, device=device) - Lnorth = torch.zeros_like(Ldown, device=device) - Lwest = torch.zeros_like(Ldown, device=device) - Lsouth = torch.zeros_like(Ldown, device=device) + Least = np.zeros_like(Ldown) + Lnorth = np.zeros_like(Ldown) + Lwest = np.zeros_like(Ldown) + Lsouth = np.zeros_like(Ldown) return Least, Lsouth, Lwest, Lnorth else: if altitude > 0: # daytime - alfaB = torch.arctan(svfalfaE) - betaB = torch.arctan(torch.tan((svfalfaE) * F_sh)) + alfaB = np.arctan(svfalfaE) + betaB = np.arctan(np.tan((svfalfaE) * F_sh)) betasun = ((alfaB - betaB) / 2) + betaB - # betasun = torch.arctan(0.5*torch.tan(svfalfaE)*(1+F_sh)) #TODO This should be considered in future versions + # betasun = np.arctan(0.5*np.tan(svfalfaE)*(1+F_sh)) #TODO This should be considered in future versions if (azimuth > (180 - t)) and (azimuth <= (360 - t)): Lwallsun = ( SBC * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziE * (torch.pi / 180))) - ** 4 - ) + * ((Ta + 273.15 + Tw * np.sin(aziE * (np.pi / 180))) ** 4) * viktwall * (1 - F_sh) - * torch.cos(betasun) + * np.cos(betasun) * 0.5 ) Lwallsh = ( @@ -391,21 +329,18 @@ def Lside_veg_v2026( ) if altitude > 0: # daytime - alfaB = torch.arctan(svfalfaS) - betaB = torch.arctan(torch.tan((svfalfaS) * F_sh)) + alfaB = np.arctan(svfalfaS) + betaB = np.arctan(np.tan((svfalfaS) * F_sh)) betasun = ((alfaB - betaB) / 2) + betaB - # betasun = torch.arctan(0.5*torch.tan(svfalfaS)*(1+F_sh)) + # betasun = np.arctan(0.5*np.tan(svfalfaS)*(1+F_sh)) if (azimuth <= (90 - t)) or (azimuth > (270 - t)): Lwallsun = ( SBC * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziS * (torch.pi / 180))) - ** 4 - ) + * ((Ta + 273.15 + Tw * np.sin(aziS * (np.pi / 180))) ** 4) * viktwall * (1 - F_sh) - * torch.cos(betasun) + * np.cos(betasun) * 0.5 ) Lwallsh = ( @@ -431,21 +366,18 @@ def Lside_veg_v2026( ) if altitude > 0: # daytime - alfaB = torch.arctan(svfalfaW) - betaB = torch.arctan(torch.tan((svfalfaW) * F_sh)) + alfaB = np.arctan(svfalfaW) + betaB = np.arctan(np.tan((svfalfaW) * F_sh)) betasun = ((alfaB - betaB) / 2) + betaB - # betasun = torch.arctan(0.5*torch.tan(svfalfaW)*(1+F_sh)) + # betasun = np.arctan(0.5*np.tan(svfalfaW)*(1+F_sh)) if (azimuth > (360 - t)) or (azimuth <= (180 - t)): Lwallsun = ( SBC * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziW * (torch.pi / 180))) - ** 4 - ) + * ((Ta + 273.15 + Tw * np.sin(aziW * (np.pi / 180))) ** 4) * viktwall * (1 - F_sh) - * torch.cos(betasun) + * np.cos(betasun) * 0.5 ) Lwallsh = ( @@ -471,21 +403,18 @@ def Lside_veg_v2026( ) if altitude > 0: # daytime - alfaB = torch.arctan(svfalfaN) - betaB = torch.arctan(torch.tan((svfalfaN) * F_sh)) + alfaB = np.arctan(svfalfaN) + betaB = np.arctan(np.tan((svfalfaN) * F_sh)) betasun = ((alfaB - betaB) / 2) + betaB - # betasun = torch.arctan(0.5*torch.tan(svfalfaN)*(1+F_sh)) + # betasun = np.arctan(0.5*np.tan(svfalfaN)*(1+F_sh)) if (azimuth > (90 - t)) and (azimuth <= (270 - t)): Lwallsun = ( SBC * ewall - * ( - (Ta + 273.15 + Tw * torch.sin(aziN * (torch.pi / 180))) - ** 4 - ) + * ((Ta + 273.15 + Tw * np.sin(aziN * (np.pi / 180))) ** 4) * viktwall * (1 - F_sh) - * torch.cos(betasun) + * np.cos(betasun) * 0.5 ) Lwallsh = ( diff --git a/functions/SOLWEIGpython/Lside_veg_torch.py b/functions/SOLWEIGpython/Lside_veg_torch.py new file mode 100644 index 0000000..1aa3540 --- /dev/null +++ b/functions/SOLWEIGpython/Lside_veg_torch.py @@ -0,0 +1,509 @@ +from __future__ import absolute_import +from .Lvikt_veg_torch import Lvikt_veg +try: + import torch +except: + pass + +def Lside_veg_v2022a( + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + azimuth, + altitude, + Ta, + Tw, + SBC, + ewall, + Ldown, + esky, + t, + F_sh, + CI, + LupE, + LupS, + LupW, + LupN, + anisotropic_longwave, +): + + # This m-file is the current one that estimates L from the four cardinal points 20100414 + device = ( + Ldown.device + if isinstance(Ldown, torch.Tensor) + else ( + Ta.device + if isinstance(Ta, torch.Tensor) + else torch.device("cuda" if torch.cuda.is_available() else "cpu") + ) + ) + + # Convert all SVF inputs to the same device + svfS = torch.as_tensor(svfS, device=device) + svfW = torch.as_tensor(svfW, device=device) + svfN = torch.as_tensor(svfN, device=device) + svfE = torch.as_tensor(svfE, device=device) + svfEveg = torch.as_tensor(svfEveg, device=device) + svfSveg = torch.as_tensor(svfSveg, device=device) + svfWveg = torch.as_tensor(svfWveg, device=device) + svfNveg = torch.as_tensor(svfNveg, device=device) + svfEaveg = torch.as_tensor(svfEaveg, device=device) + svfSaveg = torch.as_tensor(svfSaveg, device=device) + svfWaveg = torch.as_tensor(svfWaveg, device=device) + svfNaveg = torch.as_tensor(svfNaveg, device=device) + + # Building height angle from svf + svfalfaE = torch.arcsin(torch.exp((torch.log(1 - svfE)) / 2)) + svfalfaS = torch.arcsin(torch.exp((torch.log(1 - svfS)) / 2)) + svfalfaW = torch.arcsin(torch.exp((torch.log(1 - svfW)) / 2)) + svfalfaN = torch.arcsin(torch.exp((torch.log(1 - svfN)) / 2)) + + vikttot = 4.4897 + aziW = azimuth + t + aziN = azimuth - 90 + t + aziE = azimuth - 180 + t + aziS = azimuth - 270 + t + + F_sh = 2 * F_sh - 1 # (cylindric_wedge scaled 0-1) + + c = 1 - CI + Lsky_allsky = esky * SBC * ((Ta + 273.15) ** 4) * (1 - c) + c * SBC * ( + (Ta + 273.15) ** 4 + ) + + ## Least + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfE, svfEveg, svfEaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = torch.arctan(svfalfaE) + betaB = torch.arctan(torch.tan((svfalfaE) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB + # betasun = torch.arctan(0.5*torch.tan(svfalfaE)*(1+F_sh)) #TODO This should be considered in future versions + if (azimuth > (180 - t)) and (azimuth <= (360 - t)): + Lwallsun = ( + SBC + * ewall + * ( + (Ta + 273.15 + Tw * torch.sin(aziE * (torch.pi / 180))) + ** 4 + ) + * viktwall + * (1 - F_sh) + * torch.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) + else: + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + # Longwave from ground (see Lcyl_v2022a for remaining fluxes) + if anisotropic_longwave == 1: + Lground = LupE * 0.5 + Least = Lground + else: + Lsky = ((svfE + svfEveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupE * 0.5 + Lrefl = (Ldown + LupE) * (viktrefl) * (1 - ewall) * 0.5 + Least = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky + + ## Lsouth + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfS, svfSveg, svfSaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = torch.arctan(svfalfaS) + betaB = torch.arctan(torch.tan((svfalfaS) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB + # betasun = torch.arctan(0.5*torch.tan(svfalfaS)*(1+F_sh)) + if (azimuth <= (90 - t)) or (azimuth > (270 - t)): + Lwallsun = ( + SBC + * ewall + * ( + (Ta + 273.15 + Tw * torch.sin(aziS * (torch.pi / 180))) + ** 4 + ) + * viktwall + * (1 - F_sh) + * torch.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) + else: + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + # Longwave from ground (see Lcyl_v2022a for remaining fluxes) + if anisotropic_longwave == 1: + Lground = LupS * 0.5 + Lsouth = Lground + else: + Lsky = ((svfS + svfSveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupS * 0.5 + Lrefl = (Ldown + LupS) * (viktrefl) * (1 - ewall) * 0.5 + Lsouth = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky + + ## Lwest + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfW, svfWveg, svfWaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = torch.arctan(svfalfaW) + betaB = torch.arctan(torch.tan((svfalfaW) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB + # betasun = torch.arctan(0.5*torch.tan(svfalfaW)*(1+F_sh)) + if (azimuth > (360 - t)) or (azimuth <= (180 - t)): + Lwallsun = ( + SBC + * ewall + * ( + (Ta + 273.15 + Tw * torch.sin(aziW * (torch.pi / 180))) + ** 4 + ) + * viktwall + * (1 - F_sh) + * torch.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) + else: + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + # Longwave from ground (see Lcyl_v2022a for remaining fluxes) + if anisotropic_longwave == 1: + Lground = LupW * 0.5 + Lwest = Lground + else: + Lsky = ((svfW + svfWveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupW * 0.5 + Lrefl = (Ldown + LupW) * (viktrefl) * (1 - ewall) * 0.5 + Lwest = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky + + ## Lnorth + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfN, svfNveg, svfNaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = torch.arctan(svfalfaN) + betaB = torch.arctan(torch.tan((svfalfaN) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB + # betasun = torch.arctan(0.5*torch.tan(svfalfaN)*(1+F_sh)) + if (azimuth > (90 - t)) and (azimuth <= (270 - t)): + Lwallsun = ( + SBC + * ewall + * ( + (Ta + 273.15 + Tw * torch.sin(aziN * (torch.pi / 180))) + ** 4 + ) + * viktwall + * (1 - F_sh) + * torch.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) + else: + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + # Longwave from ground (see Lcyl_v2022a for remaining fluxes) + if anisotropic_longwave == 1: + Lground = LupN * 0.5 + Lnorth = Lground + else: + Lsky = ((svfN + svfNveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupN * 0.5 + Lrefl = (Ldown + LupN) * (viktrefl) * (1 - ewall) * 0.5 + Lnorth = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky + + return Least, Lsouth, Lwest, Lnorth + + +def Lside_veg_v2026( + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + azimuth, + altitude, + Ta, + Tw, + SBC, + ewall, + Ldown, + esky, + t, + F_sh, + CI, + anisotropic_longwave, +): + + # This m-file is the current one that estimates L from the four cardinal points 20100414 + device = ( + Ldown.device + if isinstance(Ldown, torch.Tensor) + else ( + Ta.device + if isinstance(Ta, torch.Tensor) + else torch.device("cuda" if torch.cuda.is_available() else "cpu") + ) + ) + + # Convert all SVF inputs to the same device + svfS = torch.as_tensor(svfS, device=device) + svfW = torch.as_tensor(svfW, device=device) + svfN = torch.as_tensor(svfN, device=device) + svfE = torch.as_tensor(svfE, device=device) + svfEveg = torch.as_tensor(svfEveg, device=device) + svfSveg = torch.as_tensor(svfSveg, device=device) + svfWveg = torch.as_tensor(svfWveg, device=device) + svfNveg = torch.as_tensor(svfNveg, device=device) + svfEaveg = torch.as_tensor(svfEaveg, device=device) + svfSaveg = torch.as_tensor(svfSaveg, device=device) + svfWaveg = torch.as_tensor(svfWaveg, device=device) + svfNaveg = torch.as_tensor(svfNaveg, device=device) + + # Building height angle from svf + svfalfaE = torch.arcsin(torch.exp((torch.log(1 - svfE)) / 2)) + svfalfaS = torch.arcsin(torch.exp((torch.log(1 - svfS)) / 2)) + svfalfaW = torch.arcsin(torch.exp((torch.log(1 - svfW)) / 2)) + svfalfaN = torch.arcsin(torch.exp((torch.log(1 - svfN)) / 2)) + + vikttot = 4.4897 + aziW = azimuth + t + aziN = azimuth - 90 + t + aziE = azimuth - 180 + t + aziS = azimuth - 270 + t + + F_sh = 2 * F_sh - 1 # (cylindric_wedge scaled 0-1) + + c = 1 - CI + Lsky_allsky = esky * SBC * ((Ta + 273.15) ** 4) * (1 - c) + c * SBC * ( + (Ta + 273.15) ** 4 + ) + + ## Least + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfE, svfEveg, svfEaveg, vikttot + ) + + if anisotropic_longwave == 1: + Least = torch.zeros_like(Ldown, device=device) + Lnorth = torch.zeros_like(Ldown, device=device) + Lwest = torch.zeros_like(Ldown, device=device) + Lsouth = torch.zeros_like(Ldown, device=device) + + return Least, Lsouth, Lwest, Lnorth + else: + if altitude > 0: # daytime + alfaB = torch.arctan(svfalfaE) + betaB = torch.arctan(torch.tan((svfalfaE) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB + # betasun = torch.arctan(0.5*torch.tan(svfalfaE)*(1+F_sh)) #TODO This should be considered in future versions + if (azimuth > (180 - t)) and (azimuth <= (360 - t)): + Lwallsun = ( + SBC + * ewall + * ( + (Ta + 273.15 + Tw * torch.sin(aziE * (torch.pi / 180))) + ** 4 + ) + * viktwall + * (1 - F_sh) + * torch.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) + else: + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + Lsky = ((svfE + svfEveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lrefl = Ldown * (viktrefl) * (1 - ewall) * 0.5 + Least = Lsky + Lwallsun + Lwallsh + Lveg + Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky + + ## Lsouth + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfS, svfSveg, svfSaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = torch.arctan(svfalfaS) + betaB = torch.arctan(torch.tan((svfalfaS) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB + # betasun = torch.arctan(0.5*torch.tan(svfalfaS)*(1+F_sh)) + if (azimuth <= (90 - t)) or (azimuth > (270 - t)): + Lwallsun = ( + SBC + * ewall + * ( + (Ta + 273.15 + Tw * torch.sin(aziS * (torch.pi / 180))) + ** 4 + ) + * viktwall + * (1 - F_sh) + * torch.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) + else: + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + Lsky = ((svfS + svfSveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lrefl = Ldown * (viktrefl) * (1 - ewall) * 0.5 + Lsouth = Lsky + Lwallsun + Lwallsh + Lveg + Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky + + ## Lwest + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfW, svfWveg, svfWaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = torch.arctan(svfalfaW) + betaB = torch.arctan(torch.tan((svfalfaW) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB + # betasun = torch.arctan(0.5*torch.tan(svfalfaW)*(1+F_sh)) + if (azimuth > (360 - t)) or (azimuth <= (180 - t)): + Lwallsun = ( + SBC + * ewall + * ( + (Ta + 273.15 + Tw * torch.sin(aziW * (torch.pi / 180))) + ** 4 + ) + * viktwall + * (1 - F_sh) + * torch.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) + else: + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + Lsky = ((svfW + svfWveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lrefl = Ldown * (viktrefl) * (1 - ewall) * 0.5 + Lwest = Lsky + Lwallsun + Lwallsh + Lveg + Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky + + ## Lnorth + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfN, svfNveg, svfNaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = torch.arctan(svfalfaN) + betaB = torch.arctan(torch.tan((svfalfaN) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB + # betasun = torch.arctan(0.5*torch.tan(svfalfaN)*(1+F_sh)) + if (azimuth > (90 - t)) and (azimuth <= (270 - t)): + Lwallsun = ( + SBC + * ewall + * ( + (Ta + 273.15 + Tw * torch.sin(aziN * (torch.pi / 180))) + ** 4 + ) + * viktwall + * (1 - F_sh) + * torch.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) + else: + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + Lsky = ((svfN + svfNveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lrefl = Ldown * (viktrefl) * (1 - ewall) * 0.5 + Lnorth = Lsky + Lwallsun + Lwallsh + Lveg + Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky + + return Least, Lsouth, Lwest, Lnorth diff --git a/functions/SOLWEIGpython/Lside_veg_v2015a.py b/functions/SOLWEIGpython/Lside_veg_v2015a.py index 6700501..3aecfb3 100644 --- a/functions/SOLWEIGpython/Lside_veg_v2015a.py +++ b/functions/SOLWEIGpython/Lside_veg_v2015a.py @@ -2,214 +2,133 @@ import numpy as np from .Lvikt_veg import Lvikt_veg - -def Lside_veg_v2015a( - svfS, - svfW, - svfN, - svfE, - svfEveg, - svfSveg, - svfWveg, - svfNveg, - svfEaveg, - svfSaveg, - svfWaveg, - svfNaveg, - azimuth, - altitude, - Ta, - Tw, - SBC, - ewall, - Ldown, - esky, - t, - F_sh, - CI, - LupE, - LupS, - LupW, - LupN, -): - - # This m-file is the current one that estimates L from the four cardinal - # points 20100414 - - # Building height angle from svf - svfalfaE = np.arcsin(np.exp((np.log(1 - svfE)) / 2)) - svfalfaS = np.arcsin(np.exp((np.log(1 - svfS)) / 2)) - svfalfaW = np.arcsin(np.exp((np.log(1 - svfW)) / 2)) - svfalfaN = np.arcsin(np.exp((np.log(1 - svfN)) / 2)) - - vikttot = 4.4897 - aziW = azimuth + t - aziN = azimuth - 90 + t - aziE = azimuth - 180 + t - aziS = azimuth - 270 + t - - F_sh = 2 * F_sh - 1 # (cylindric_wedge scaled 0-1) - - c = 1 - CI - Lsky_allsky = esky * SBC * ((Ta + 273.15) ** 4) * (1 - c) + c * SBC * ( - (Ta + 273.15) ** 4 - ) - - # Least - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( - svfE, svfEveg, svfEaveg, vikttot - ) - +def Lside_veg_v2015a(svfS,svfW,svfN,svfE,svfEveg,svfSveg,svfWveg,svfNveg,svfEaveg,svfSaveg,svfWaveg,svfNaveg,azimuth,altitude,Ta,Tw,SBC,ewall,Ldown,esky,t,F_sh,CI,LupE,LupS,LupW,LupN): + + # This m-file is the current one that estimates L from the four cardinal points 20100414 + + #Building height angle from svf + svfalfaE=np.arcsin(np.exp((np.log(1-svfE))/2)) + svfalfaS=np.arcsin(np.exp((np.log(1-svfS))/2)) + svfalfaW=np.arcsin(np.exp((np.log(1-svfW))/2)) + svfalfaN=np.arcsin(np.exp((np.log(1-svfN))/2)) + + vikttot=4.4897 + aziW=azimuth+t + aziN=azimuth-90+t + aziE=azimuth-180+t + aziS=azimuth-270+t + + F_sh = 2*F_sh-1 #(cylindric_wedge scaled 0-1) + + c=1-CI + Lsky_allsky = esky*SBC*((Ta+273.15)**4)*(1-c)+c*SBC*((Ta+273.15)**4) + + ## Least + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg(svfE, svfEveg, svfEaveg, vikttot) + if altitude > 0: # daytime - alfaB = np.arctan(svfalfaE) - betaB = np.arctan(np.tan((svfalfaE) * F_sh)) - betasun = ((alfaB - betaB) / 2) + betaB - # betasun = np.arctan(0.5*np.tan(svfalfaE)*(1+F_sh)) #TODO This should - # be considered in future versions - if (azimuth > (180 - t)) and (azimuth <= (360 - t)): - Lwallsun = ( - SBC - * ewall - * ((Ta + 273.15 + Tw * np.sin(aziE * (np.pi / 180))) ** 4) - * viktwall - * (1 - F_sh) - * np.cos(betasun) - * 0.5 - ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) + alfaB=np.arctan(svfalfaE) + betaB=np.arctan(np.tan((svfalfaE)*F_sh)) + betasun=((alfaB-betaB)/2)+betaB + # betasun = np.arctan(0.5*np.tan(svfalfaE)*(1+F_sh)) #TODO This should be considered in future versions + if (azimuth > (180-t)) and (azimuth <= (360-t)): + Lwallsun=SBC*ewall*((Ta+273.15+Tw*np.sin(aziE*(np.pi/180)))**4)*\ + viktwall*(1-F_sh)*np.cos(betasun)*0.5 + Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*F_sh*0.5 else: - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - else: # nighttime - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - - Lsky = ((svfE + svfEveg - 1) * Lsky_allsky) * viktsky * 0.5 - Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 - Lground = LupE * 0.5 - Lrefl = (Ldown + LupE) * (viktrefl) * (1 - ewall) * 0.5 - Least = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl - - # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl - # viktveg viktwall viktsky - - # Lsouth - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( - svfS, svfSveg, svfSaveg, vikttot - ) - - if altitude > 0: # daytime - alfaB = np.arctan(svfalfaS) - betaB = np.arctan(np.tan((svfalfaS) * F_sh)) - betasun = ((alfaB - betaB) / 2) + betaB + Lwallsun=0 + Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 + else: #nighttime + Lwallsun=0 + Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 + + Lsky=((svfE+svfEveg-1)*Lsky_allsky)*viktsky*0.5 + Lveg=SBC*ewall*((Ta+273.15)**4)*viktveg*0.5 + Lground=LupE*0.5 + Lrefl=(Ldown+LupE)*(viktrefl)*(1-ewall)*0.5 + Least=Lsky+Lwallsun+Lwallsh+Lveg+Lground+Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky + + ## Lsouth + [viktveg,viktwall,viktsky,viktrefl]=Lvikt_veg(svfS,svfSveg,svfSaveg,vikttot) + + if altitude>0: # daytime + alfaB=np.arctan(svfalfaS) + betaB=np.arctan(np.tan((svfalfaS)*F_sh)) + betasun=((alfaB-betaB)/2)+betaB # betasun = np.arctan(0.5*np.tan(svfalfaS)*(1+F_sh)) - if (azimuth <= (90 - t)) or (azimuth > (270 - t)): - Lwallsun = ( - SBC - * ewall - * ((Ta + 273.15 + Tw * np.sin(aziS * (np.pi / 180))) ** 4) - * viktwall - * (1 - F_sh) - * np.cos(betasun) - * 0.5 - ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) + if (azimuth <= (90-t)) or (azimuth > (270-t)): + Lwallsun=SBC*ewall*((Ta+273.15+Tw*np.sin(aziS*(np.pi/180)))**4)*\ + viktwall*(1-F_sh)*np.cos(betasun)*0.5 + Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*F_sh*0.5 else: - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - else: # nighttime - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - - Lsky = ((svfS + svfSveg - 1) * Lsky_allsky) * viktsky * 0.5 - Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 - Lground = LupS * 0.5 - Lrefl = (Ldown + LupS) * (viktrefl) * (1 - ewall) * 0.5 - Lsouth = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl - - # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl - # viktveg viktwall viktsky - - # Lwest - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( - svfW, svfWveg, svfWaveg, vikttot - ) - - if altitude > 0: # daytime - alfaB = np.arctan(svfalfaW) - betaB = np.arctan(np.tan((svfalfaW) * F_sh)) - betasun = ((alfaB - betaB) / 2) + betaB + Lwallsun=0 + Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 + else: #nighttime + Lwallsun=0 + Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 + + Lsky=((svfS+svfSveg-1)*Lsky_allsky)*viktsky*0.5 + Lveg=SBC*ewall*((Ta+273.15)**4)*viktveg*0.5 + Lground=LupS*0.5 + Lrefl=(Ldown+LupS)*(viktrefl)*(1-ewall)*0.5 + Lsouth=Lsky+Lwallsun+Lwallsh+Lveg+Lground+Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky + + ## Lwest + [viktveg,viktwall,viktsky,viktrefl]=Lvikt_veg(svfW,svfWveg,svfWaveg,vikttot) + + if altitude>0: # daytime + alfaB=np.arctan(svfalfaW) + betaB=np.arctan(np.tan((svfalfaW)*F_sh)) + betasun=((alfaB-betaB)/2)+betaB # betasun = np.arctan(0.5*np.tan(svfalfaW)*(1+F_sh)) - if (azimuth > (360 - t)) or (azimuth <= (180 - t)): - Lwallsun = ( - SBC - * ewall - * ((Ta + 273.15 + Tw * np.sin(aziW * (np.pi / 180))) ** 4) - * viktwall - * (1 - F_sh) - * np.cos(betasun) - * 0.5 - ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) + if (azimuth > (360-t)) or (azimuth <= (180-t)): + Lwallsun=SBC*ewall*((Ta+273.15+Tw*np.sin(aziW*(np.pi/180)))**4)*\ + viktwall*(1-F_sh)*np.cos(betasun)*0.5 + Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*F_sh*0.5 else: - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - else: # nighttime - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - - Lsky = ((svfW + svfWveg - 1) * Lsky_allsky) * viktsky * 0.5 - Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 - Lground = LupW * 0.5 - Lrefl = (Ldown + LupW) * (viktrefl) * (1 - ewall) * 0.5 - Lwest = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl - - # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl - # viktveg viktwall viktsky - - # Lnorth - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( - svfN, svfNveg, svfNaveg, vikttot - ) - - if altitude > 0: # daytime - alfaB = np.arctan(svfalfaN) - betaB = np.arctan(np.tan((svfalfaN) * F_sh)) - betasun = ((alfaB - betaB) / 2) + betaB + Lwallsun=0 + Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 + else: #nighttime + Lwallsun=0 + Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 + + Lsky=((svfW+svfWveg-1)*Lsky_allsky)*viktsky*0.5 + Lveg=SBC*ewall*((Ta+273.15)**4)*viktveg*0.5 + Lground=LupW*0.5 + Lrefl=(Ldown+LupW)*(viktrefl)*(1-ewall)*0.5 + Lwest=Lsky+Lwallsun+Lwallsh+Lveg+Lground+Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky + + ## Lnorth + [viktveg,viktwall,viktsky,viktrefl]=Lvikt_veg(svfN,svfNveg,svfNaveg,vikttot) + + if altitude>0: # daytime + alfaB=np.arctan(svfalfaN) + betaB=np.arctan(np.tan((svfalfaN)*F_sh)) + betasun=((alfaB-betaB)/2)+betaB # betasun = np.arctan(0.5*np.tan(svfalfaN)*(1+F_sh)) - if (azimuth > (90 - t)) and (azimuth <= (270 - t)): - Lwallsun = ( - SBC - * ewall - * ((Ta + 273.15 + Tw * np.sin(aziN * (np.pi / 180))) ** 4) - * viktwall - * (1 - F_sh) - * np.cos(betasun) - * 0.5 - ) - Lwallsh = ( - SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 - ) + if (azimuth > (90-t)) and (azimuth <= (270-t)): + Lwallsun=SBC*ewall*((Ta+273.15+Tw*np.sin(aziN*(np.pi/180)))**4)*\ + viktwall*(1-F_sh)*np.cos(betasun)*0.5 + Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*F_sh*0.5 else: - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - else: # nighttime - Lwallsun = 0 - Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 - - Lsky = ((svfN + svfNveg - 1) * Lsky_allsky) * viktsky * 0.5 - Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 - Lground = LupN * 0.5 - Lrefl = (Ldown + LupN) * (viktrefl) * (1 - ewall) * 0.5 - Lnorth = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl - - # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl - # viktveg viktwall viktsky - - return Least, Lsouth, Lwest, Lnorth + Lwallsun=0 + Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 + else: #nighttime + Lwallsun=0 + Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 + + Lsky=((svfN+svfNveg-1)*Lsky_allsky)*viktsky*0.5 + Lveg=SBC*ewall*((Ta+273.15)**4)*viktveg*0.5 + Lground=LupN*0.5 + Lrefl=(Ldown+LupN)*(viktrefl)*(1-ewall)*0.5 + Lnorth=Lsky+Lwallsun+Lwallsh+Lveg+Lground+Lrefl + + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky + + return Least,Lsouth,Lwest,Lnorth \ No newline at end of file diff --git a/functions/SOLWEIGpython/Lvikt_veg.py b/functions/SOLWEIGpython/Lvikt_veg.py index 5e7087a..8ca101c 100644 --- a/functions/SOLWEIGpython/Lvikt_veg.py +++ b/functions/SOLWEIGpython/Lvikt_veg.py @@ -1,20 +1,4 @@ -import torch - - def Lvikt_veg(svf, svfveg, svfaveg, vikttot): - device = None - if isinstance(svf, torch.Tensor): - device = svf.device - elif isinstance(svfveg, torch.Tensor): - device = svfveg.device - elif isinstance(svfaveg, torch.Tensor): - device = svfaveg.device - else: - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") - - svf = torch.as_tensor(svf, device=device) - svfveg = torch.as_tensor(svfveg, device=device) - svfaveg = torch.as_tensor(svfaveg, device=device) # Least viktonlywall = ( diff --git a/functions/SOLWEIGpython/Lvikt_veg_torch.py b/functions/SOLWEIGpython/Lvikt_veg_torch.py new file mode 100644 index 0000000..8ad4e02 --- /dev/null +++ b/functions/SOLWEIGpython/Lvikt_veg_torch.py @@ -0,0 +1,81 @@ +try: + import torch +except: + pass + +def Lvikt_veg(svf, svfveg, svfaveg, vikttot): + device = None + if isinstance(svf, torch.Tensor): + device = svf.device + elif isinstance(svfveg, torch.Tensor): + device = svfveg.device + elif isinstance(svfaveg, torch.Tensor): + device = svfaveg.device + else: + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + + svf = torch.as_tensor(svf, device=device) + svfveg = torch.as_tensor(svfveg, device=device) + svfaveg = torch.as_tensor(svfaveg, device=device) + + # Least + viktonlywall = ( + vikttot + - ( + 63.227 * svf**6 + - 161.51 * svf**5 + + 156.91 * svf**4 + - 70.424 * svf**3 + + 16.773 * svf**2 + - 0.4863 * svf + ) + ) / vikttot + + viktaveg = ( + vikttot + - ( + 63.227 * svfaveg**6 + - 161.51 * svfaveg**5 + + 156.91 * svfaveg**4 + - 70.424 * svfaveg**3 + + 16.773 * svfaveg**2 + - 0.4863 * svfaveg + ) + ) / vikttot + + viktwall = viktonlywall - viktaveg + + svfvegbu = svfveg + svf - 1 # Vegetation plus buildings + viktsky = ( + 63.227 * svfvegbu**6 + - 161.51 * svfvegbu**5 + + 156.91 * svfvegbu**4 + - 70.424 * svfvegbu**3 + + 16.773 * svfvegbu**2 + - 0.4863 * svfvegbu + ) / vikttot + viktrefl = ( + vikttot + - ( + 63.227 * svfvegbu**6 + - 161.51 * svfvegbu**5 + + 156.91 * svfvegbu**4 + - 70.424 * svfvegbu**3 + + 16.773 * svfvegbu**2 + - 0.4863 * svfvegbu + ) + ) / vikttot + viktveg = ( + vikttot + - ( + 63.227 * svfvegbu**6 + - 161.51 * svfvegbu**5 + + 156.91 * svfvegbu**4 + - 70.424 * svfvegbu**3 + + 16.773 * svfvegbu**2 + - 0.4863 * svfvegbu + ) + ) / vikttot + viktveg = viktveg - viktwall + + return viktveg, viktwall, viktsky, viktrefl diff --git a/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py b/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py index d409821..c89f014 100644 --- a/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py +++ b/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py @@ -17,18 +17,17 @@ from .TsWaveDelay_2015a import TsWaveDelay_2015a from .Kup_veg_2015a import Kup_veg_2015a - +# from .Lside_veg_v2015a import Lside_veg_v2015a +# from .Kside_veg_v2019a import Kside_veg_v2019a from .Kside_veg_v2022a import Kside_veg_v2022a from ...util.SEBESOLWEIGCommonFiles.Perez_v3 import Perez_v3 from ...util.SEBESOLWEIGCommonFiles.create_patches import create_patches # Anisotropic longwave -from .Lcyl_v2022a import Lcyl_v2022a from .Lside_veg import Lside_veg_v2022a from .anisotropic_sky import anisotropic_sky as ani_sky from .patch_radiation import patch_steradians from copy import deepcopy -import time # Wall surface temperature scheme from .wall_surface_temperature import wall_surface_temperature @@ -135,7 +134,7 @@ def Solweig_2025a_calc( # landcover, lc_grid, dectime, altmax, dirwalls, walls, cyl, elvis, Ta, RH, radG, radD, radI, P, # amaxvalue, bush, Twater, TgK, Tstart, alb_grid, emis_grid, TgK_wall, Tstart_wall, TmaxLST, # TmaxLST_wall, first, second, svfalfa, svfbuveg, firstdaytime, timeadd, timestepdec, Tgmap1, - # Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, CI, TgOut1, diffsh, ani): + # Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, CI, TgOut1, diffsh, ani): # This is the core function of the SOLWEIG model # 2016-Aug-28 @@ -218,7 +217,7 @@ def Solweig_2025a_calc( 1 - (1 + msteg) * np.exp(-((1.2 + 3.0 * msteg) ** 0.5)) ) + elvis # -0.04 old error from Jonsson et al.2006 - if altitude > 0: # # # # # # DAYTIME # # # # # # + if altitude > 0: # DAYTIME # # # # # # # Clearness Index on Earth's surface after Crawford and Dunchon (1999) with a correction # factor for low sun elevations after Lindberg et al.(2008) I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( @@ -244,16 +243,10 @@ def Solweig_2025a_calc( zenDeg = zen * (180 / np.pi) # Relative luminance lv, pc_, pb_ = Perez_v3( - zenDeg, - azimuth, - radD, - radI, - jday, - patchchoice, - patch_option, - "cpu", + zenDeg, azimuth, radD, radI, jday, patchchoice, patch_option ) - # Total relative luminance from sky, i.e. from each patch, into each cell + # Total relative luminance from sky, i.e. from each patch, into + # each cell aniLum = np.zeros((rows, cols)) for idx in range(lv.shape[0]): aniLum += diffsh[:, :, idx] * lv[idx, 2] @@ -327,11 +320,11 @@ def Solweig_2025a_calc( # Tg = 0 Tgwall = 0 - # New estimation of Tg reduction for non - clear situation based on Reindl et al.1990 + # New estimation of Tg reduction for non - clear situation based on + # Reindl et al.1990 radI0, _ = diffusefraction(I0, altitude, 1.0, Ta, RH) - corr = ( - 0.1473 * np.log(90 - (zen / np.pi * 180)) + 0.3454 - ) # 20070329 correction of lat, Lindberg et al. 2008 + # 20070329 correction of lat, Lindberg et al. 2008 + corr = 0.1473 * np.log(90 - (zen / np.pi * 180)) + 0.3454 CI_Tg = (radG / radI0) + (1 - corr) if (CI_Tg > 1) or (CI_Tg == np.inf): CI_Tg = 1 @@ -418,9 +411,8 @@ def Solweig_2025a_calc( ) # timeadd only here v2021a # Building height angle from svf - F_sh = cylindric_wedge( - zen, svfalfa, rows, cols - ) # Fraction shadow on building walls based on sun alt and svf + # Fraction shadow on building walls based on sun alt and svf + F_sh = cylindric_wedge(zen, svfalfa, rows, cols) F_sh[np.isnan(F_sh)] = 0.5 # # # # # # # Calculation of shortwave daytime radiative fluxes # # # # # # # @@ -536,7 +528,8 @@ def Solweig_2025a_calc( + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + (2 - svf - svfveg) * (1 - ewall) * esky * SBC * ((Ta + 273.15) ** 4) ) # Jonsson et al.(2006) - # Ldown = Ldown - 25 # Shown by Jonsson et al.(2006) and Duarte et al.(2006) + # Ldown = Ldown - 25 # Shown by Jonsson et al.(2006) and Duarte et + # al.(2006) if CI < 0.95: # non - clear conditions c = 1 - CI @@ -544,8 +537,10 @@ def Solweig_2025a_calc( (svf + svfveg - 1) * SBC * ((Ta + 273.15) ** 4) + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) - + (2 - svf - svfveg) * (1 - ewall) * SBC * ((Ta + 273.15) ** 4) - ) # NOT REALLY TESTED!!! BUT MORE CORRECT? + + + # NOT REALLY TESTED!!! BUT MORE CORRECT? + (2 - svf - svfveg) * (1 - ewall) * SBC * ((Ta + 273.15) ** 4) + ) # # # # Lside # # # # Least, Lsouth, Lwest, Lnorth = Lside_veg_v2022a( @@ -601,7 +596,8 @@ def Solweig_2025a_calc( # Anisotropic sky if anisotropic_sky == 1: if "lv" not in locals(): - # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) + # Creating skyvault of patches of constant radians (Tregeneza and + # Sharples, 1993) skyvaultalt, skyvaultazi, _, _, _, _, _ = create_patches( patch_option ) @@ -630,7 +626,8 @@ def Solweig_2025a_calc( KupW = 0 KupN = 0 - # Adjust sky emissivity under semi-cloudy/hazy/cloudy/overcast conditions, i.e. CI lower than 0.95 + # Adjust sky emissivity under semi-cloudy/hazy/cloudy/overcast + # conditions, i.e. CI lower than 0.95 if CI < 0.95: esky_c = CI * esky + (1 - CI) * 1.0 esky = esky_c @@ -780,4 +777,4 @@ def Solweig_2025a_calc( Kside, steradians, voxelTable, - ) + ) \ No newline at end of file diff --git a/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py index c34e000..898985b 100644 --- a/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py +++ b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing.py @@ -1,8 +1,6 @@ -""" -@author Fredrik Lindberg, University of Gothenburg -""" - from __future__ import absolute_import + +import numpy as np from .daylen import daylen from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import ( clearnessindex_2013b, @@ -25,22 +23,16 @@ from ...util.SEBESOLWEIGCommonFiles.create_patches import create_patches # Anisotropic longwave -from .Lcyl_v2022a import Lcyl_v2022a -from .Lside_veg import Lside_veg_v2022a, Lside_veg_v2026 +from .Lside_veg import Lside_veg_v2022a from .anisotropic_sky import anisotropic_sky as ani_sky from .patch_radiation import patch_steradians from copy import deepcopy -import time -import torch # Wall surface temperature scheme from .wall_surface_temperature import wall_surface_temperature -# Ground surface temperature -from .ground_surface import surfaceTemperature_calc, outgoingLongwave_calc - -def Solweig_2026a_calc( +def Solweig_2025a_calc( i, dsm, scale, @@ -117,6 +109,7 @@ def Solweig_2026a_calc( Tgmap1W, Tgmap1N, CI, + TgOut1, diffsh, shmat, vegshmat, @@ -132,80 +125,7 @@ def Solweig_2026a_calc( steradians, walls_scheme, dirwalls_scheme, - groundScheme, - outgoingLW, - Tg, - Rn, - Rn_past, - G, - Tm, - cap_grid, - diff_grid, - a1_grid, - a2_grid, - a3_grid, - shadow_past, ): - """ - This is the core function of the SOLWEIG model - 2016-Aug-28 - Fredrik Lindberg, fredrikl@gvc.gu.se - Goteborg Urban Climate Group - Gothenburg University - - :Input variables: - dsm = digital surface model - scale = height to pixel size (2m pixel gives scale = 0.5) - svf,svfN,svfW,svfE,svfS = SVFs for building and ground - svfveg,svfNveg,svfEveg,svfSveg,svfWveg = Veg SVFs blocking sky - svfaveg,svfEaveg,svfSaveg,svfWaveg,svfNaveg = Veg SVFs blocking buildings - vegdem = Vegetation canopy DSM - vegdem2 = Vegetation trunk zone DSM - albedo_b = building wall albedo - absK = human absorption coefficient for shortwave radiation - absL = human absorption coefficient for longwave radiation - ewall = Emissivity of building walls - Fside = The angular factors between a person and the surrounding surfaces - Fup = The angular factors between a person and the surrounding surfaces - Fcyl = The angular factors between a culidric person and the surrounding surfaces - altitude = Sun altitude (degree) - azimuth = Sun azimuth (degree) - zen = Sun zenith angle (radians) - jday = day of year - usevegdem = use vegetation scheme - onlyglobal = calculate dir and diff from global shortwave (Reindl et al. 1990) - buildings = Boolena grid to identify building pixels - location = geographic location - height = height of measurements point (center of gravity of human) - psi = 1 - Transmissivity of shortwave through vegetation - landcover = use landcover scheme !!!NEW IN 2015a!!! - lc_grid = grid with landcoverclasses - lc_class = table with landcover properties - dectime = decimal time - altmax = maximum sun altitude - dirwalls = aspect of walls - walls = one pixel row outside building footprint. height of building walls - cyl = consider man as cylinder instead of cude - elvis = dummy - Ta = air temp - RH - radG = global radiation - radD = diffuse - radI = direct - P = pressure - amaxvalue = max height of buildings - bush = grid representing bushes - Twater = temperature of water (daily) - TgK, Tstart, TgK_wall, Tstart_wall, TmaxLST,TmaxLST_wall, - alb_grid, emis_grid = albedo and emmissivity on ground - first, second = conneted to old Ts model (source area based on Smidt et al.) - svfalfa = SVF recalculated to angle - svfbuveg = complete SVF - firstdaytime, timeadd, timestepdec, Tgmap1, Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, - CI = Clearness index - TgOut1 = old Ts model - diffsh, ani = Used in anisotrpic models (Wallenberg et al. 2019, 2022) - """ # # # Core program start # # # # Instrument offset in degrees @@ -215,36 +135,27 @@ def Solweig_2026a_calc( SBC = 5.67051e-8 # Degrees to radians - deg2rad = torch.pi / 180 - device = ( - Tg.device - if isinstance(Tg, torch.Tensor) - else ( - Ta.device - if isinstance(Ta, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "cpu") - ) - ) + deg2rad = np.pi / 180 # Find sunrise decimal hour - new from 2014a _, _, _, SNUP = daylen(jday, location["latitude"]) - # Vapor pressure in hPa + # Vapor pressure ea = 6.107 * 10 ** ((7.5 * Ta) / (237.3 + Ta)) * (RH / 100.0) - # Determination of clear-sky emissivity from Prata (1996) + # Determination of clear - sky emissivity from Prata (1996) msteg = 46.5 * (ea / (Ta + 273.15)) esky = ( - 1 - (1 + msteg) * torch.exp(-((1.2 + 3.0 * msteg) ** 0.5)) + 1 - (1 + msteg) * np.exp(-((1.2 + 3.0 * msteg) ** 0.5)) ) + elvis # -0.04 old error from Jonsson et al.2006 if altitude > 0: # # # # # # DAYTIME # # # # # # # Clearness Index on Earth's surface after Crawford and Dunchon (1999) with a correction - # factor for low sun elevations after Lindberg et al.(2008) + # factor for low sun elevations after Lindberg et al.(2008) I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( zen, jday, Ta, RH / 100.0, radG, location, P ) - if (CI > 1) or (CI == torch.inf): + if (CI > 1) or (CI == np.inf): CI = 1 # Estimation of radD and radI if not measured after Reindl et al.(1990) @@ -252,7 +163,7 @@ def Solweig_2026a_calc( I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( zen, jday, Ta, RH / 100.0, radG, location, P ) - if (CI > 1) or (CI == torch.inf): + if (CI > 1) or (CI == np.inf): CI = 1 radI, radD = diffusefraction(radG, altitude, Kt, Ta, RH) @@ -261,7 +172,7 @@ def Solweig_2026a_calc( # Anisotropic Diffuse Radiation after Perez et al. 1993 if anisotropic_sky == 1: patchchoice = 1 - zenDeg = zen * (180 / torch.pi) + zenDeg = zen * (180 / np.pi) # Relative luminance lv, pc_, pb_ = Perez_v3( zenDeg, @@ -271,10 +182,9 @@ def Solweig_2026a_calc( jday, patchchoice, patch_option, - device, ) # Total relative luminance from sky, i.e. from each patch, into each cell - aniLum = torch.zeros((rows, cols), device=device) + aniLum = np.zeros((rows, cols)) for idx in range(lv.shape[0]): aniLum += diffsh[:, :, idx] * lv[idx, 2] @@ -299,9 +209,9 @@ def Solweig_2026a_calc( amaxvalue, bush, walls, - dirwalls * torch.pi / 180.0, + dirwalls * np.pi / 180.0, walls_scheme, - dirwalls_scheme * torch.pi / 180.0, + dirwalls_scheme * np.pi / 180.0, ) ) shadow = sh - (1 - vegsh) * (1 - psi) @@ -313,223 +223,143 @@ def Solweig_2026a_calc( altitude, scale, walls, - dirwalls * torch.pi / 180.0, + dirwalls * np.pi / 180.0, walls_scheme, - dirwalls_scheme * torch.pi / 180.0, + dirwalls_scheme * np.pi / 180.0, ) ) shadow = sh - # Building height angle from svf - F_sh = cylindric_wedge( - zen, svfalfa, rows, cols - ) # Fraction shadow on building walls based on sun alt and svf - F_sh[torch.isnan(F_sh)] = 0.5 - - # New estimation of Tgwall with reduction for non-clear situation based on Reindl et al. 1990 - radI0, _ = diffusefraction(I0, altitude, 1.0, Ta, RH) - radG0 = radI0 * (torch.sin(altitude * deg2rad)) + _ - corr = ( - 0.1473 * torch.log(90 - (zen / torch.pi * 180)) + 0.3454 - ) # 20070329 correction of lat, Lindberg et al. 2008 - CI_TgG = (radG / radG0) + (1 - corr) - if (CI_TgG > 1) or (CI_TgG == torch.inf): - CI_TgG = 1 - + # # # Surface temperature parameterisation during daytime # # # # + # new using max sun alt.instead of dfm + # Tgamp = (TgK * altmax - Tstart) + Tstart # Old + Tgamp = TgK * altmax + Tstart # Fixed 2021 + # Tgampwall = (TgK_wall * altmax - (Tstart_wall)) + (Tstart_wall) # Old Tgampwall = TgK_wall * altmax + Tstart_wall - Tgwall = Tgampwall * torch.sin( + Tg = Tgamp * np.sin( + ( + ((dectime - np.floor(dectime)) - SNUP / 24) + / (TmaxLST / 24 - SNUP / 24) + ) + * np.pi + / 2 + ) # 2015 a, based on max sun altitude + Tgwall = Tgampwall * np.sin( ( - ((dectime - torch.floor(dectime)) - SNUP / 24) + ((dectime - np.floor(dectime)) - SNUP / 24) / (TmaxLST_wall / 24 - SNUP / 24) ) - * torch.pi + * np.pi / 2 ) # 2015a, based on max sun altitude + if Tgwall < 0: # temporary for removing low Tg during morning 20130205 + # Tg = 0 Tgwall = 0 - Tgwall = Tgwall * CI_TgG - - # # # # Kdown # # # # - Kdown = ( - radI * shadow * torch.sin(altitude * (torch.pi / 180)) - + dRad - + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) - ) # *sin(altitude(i) * (pi / 180)) - # # # # Ldown # # # # - Ldown = ( - (svf + svfveg - 1) * esky * SBC * ((Ta + 273.15) ** 4) - + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) - + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) - + (2 - svf - svfveg) - * (1 - ewall) - * esky - * SBC - * ((Ta + 273.15) ** 4) - ) # Jonsson et al.(2006) - # Ldown = Ldown - 25 # Shown by Jonsson et al.(2006) and Duarte et al.(2006) - if CI < 0.95: # non - clear conditions - c = 1 - CI - Ldown = Ldown * (1 - c) + c * ( - (svf + svfveg - 1) * SBC * ((Ta + 273.15) ** 4) - + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) - + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) - + (2 - svf - svfveg) * (1 - ewall) * SBC * ((Ta + 273.15) ** 4) - ) # NOT REALLY TESTED!!! BUT MORE CORRECT? - - # Surface temperature parameterization during daytime - if groundScheme == 1: - # calculate the ground surface temperature, and relevant heat fluxes - Tg, Rn, Rn_past, G = surfaceTemperature_calc( - Kdown, - Ldown, - Rn, - Rn_past, - G, - Tg, - Tm, - alb_grid, - emis_grid, - cap_grid, - diff_grid, - lc_grid, - a1_grid, - a2_grid, - a3_grid, - timeStep, - RH, - shadow, - shadow_past, - ) - - else: - # using max sun alt instead of dfm - Tgamp = TgK * altmax + Tstart # Fixed 2021 - Tgdiff = Tgamp * torch.sin( - ( - ((dectime - torch.floor(dectime)) - SNUP / 24) - / (TmaxLST / 24 - SNUP / 24) - ) - * torch.pi - / 2 - ) # 2015 a, based on max sun altitude + # New estimation of Tg reduction for non - clear situation based on Reindl et al.1990 + radI0, _ = diffusefraction(I0, altitude, 1.0, Ta, RH) + corr = ( + 0.1473 * np.log(90 - (zen / np.pi * 180)) + 0.3454 + ) # 20070329 correction of lat, Lindberg et al. 2008 + CI_Tg = (radG / radI0) + (1 - corr) + if (CI_Tg > 1) or (CI_Tg == np.inf): + CI_Tg = 1 - Tgdiff = Tgdiff * CI_TgG # new estimation + radG0 = radI0 * (np.sin(altitude * deg2rad)) + _ + CI_TgG = (radG / radG0) + (1 - corr) + if (CI_TgG > 1) or (CI_TgG == np.inf): + CI_TgG = 1 - # For Tg output in POIs - TgTemp = Tgdiff * shadow + Ta - _, timeadd, Tg = TsWaveDelay_2015a( - TgTemp, firstdaytime, timeadd, timestepdec, Tg - ) # timeadd only here v2021a + # Tg = Tg * CI_Tg # new estimation + # Tgwall = Tgwall * CI_Tg + Tg = Tg * CI_TgG # new estimation + Tgwall = Tgwall * CI_TgG + if landcover == 1: + Tg[Tg < 0] = ( + 0 # temporary for removing low Tg during morning 20130205 + ) - if landcover == 1: - Tg[Tg < 0] = ( - 0 # temporary for removing low Tg during morning 20130205 - ) + # # # # Ground View Factors # # # # + ( + gvfLup, + gvfalb, + gvfalbnosh, + gvfLupE, + gvfalbE, + gvfalbnoshE, + gvfLupS, + gvfalbS, + gvfalbnoshS, + gvfLupW, + gvfalbW, + gvfalbnoshW, + gvfLupN, + gvfalbN, + gvfalbnoshN, + gvfSum, + gvfNorm, + ) = gvf_2018a( + wallsun, + walls, + buildings, + scale, + shadow, + first, + second, + dirwalls, + Tg, + Tgwall, + Ta, + emis_grid, + ewall, + alb_grid, + SBC, + albedo_b, + rows, + cols, + Twater, + lc_grid, + landcover, + ) - # Calculate the outgoing longwave radiation - if outgoingLW == 1: + # # # # Lup, daytime # # # # + # Surface temperature wave delay - new as from 2014a + Lup, timeaddnotused, Tgmap1 = TsWaveDelay_2015a( + gvfLup, firstdaytime, timeadd, timestepdec, Tgmap1 + ) + LupE, timeaddnotused, Tgmap1E = TsWaveDelay_2015a( + gvfLupE, firstdaytime, timeadd, timestepdec, Tgmap1E + ) + LupS, timeaddnotused, Tgmap1S = TsWaveDelay_2015a( + gvfLupS, firstdaytime, timeadd, timestepdec, Tgmap1S + ) + LupW, timeaddnotused, Tgmap1W = TsWaveDelay_2015a( + gvfLupW, firstdaytime, timeadd, timestepdec, Tgmap1W + ) + LupN, timeaddnotused, Tgmap1N = TsWaveDelay_2015a( + gvfLupN, firstdaytime, timeadd, timestepdec, Tgmap1N + ) - # # # # Lup, daytime # # # # - ( - Lup, - gvfalb, - gvfalbnosh, - LupE, - gvfalbE, - gvfalbnoshE, - LupS, - gvfalbS, - gvfalbnoshS, - LupW, - gvfalbW, - gvfalbnoshW, - LupN, - gvfalbN, - gvfalbnoshN, - gvfLsideW, - gvfLsideS, - gvfLsideE, - gvfLsideN, - ) = outgoingLongwave_calc( - Tg, - Tgwall, - Ta, - Ldown, - emis_grid, - alb_grid, - buildings, - shadow, - wallsun, - walls, - rows, - cols, - 1 / scale, - ) + # # For Tg output in POIs + TgTemp = Tg * shadow + Ta + TgOut, timeadd, TgOut1 = TsWaveDelay_2015a( + TgTemp, firstdaytime, timeadd, timestepdec, TgOut1 + ) # timeadd only here v2021a - else: - ### Ground View Factors - ( - gvfLup, - gvfalb, - gvfalbnosh, - gvfLupE, - gvfalbE, - gvfalbnoshE, - gvfLupS, - gvfalbS, - gvfalbnoshS, - gvfLupW, - gvfalbW, - gvfalbnoshW, - gvfLupN, - gvfalbN, - gvfalbnoshN, - gvfSum, - gvfNorm, - ) = gvf_2018a( - wallsun, - walls, - buildings, - scale, - shadow, - first, - second, - dirwalls, - Tg, - Tgwall, - Ta, - emis_grid, - ewall, - alb_grid, - SBC, - albedo_b, - rows, - cols, - Twater, - lc_grid, - landcover, - ) + # Building height angle from svf + F_sh = cylindric_wedge( + zen, svfalfa, rows, cols + ) # Fraction shadow on building walls based on sun alt and svf + F_sh[np.isnan(F_sh)] = 0.5 - # # # # Lup, daytime # # # # - # Surface temperature wave delay - new as from 2014a - Lup, timeaddnotused, Tgmap1 = TsWaveDelay_2015a( - gvfLup, firstdaytime, timeadd, timestepdec, Tgmap1 - ) - LupE, timeaddnotused, Tgmap1E = TsWaveDelay_2015a( - gvfLupE, firstdaytime, timeadd, timestepdec, Tgmap1E - ) - LupS, timeaddnotused, Tgmap1S = TsWaveDelay_2015a( - gvfLupS, firstdaytime, timeadd, timestepdec, Tgmap1S - ) - LupW, timeaddnotused, Tgmap1W = TsWaveDelay_2015a( - gvfLupW, firstdaytime, timeadd, timestepdec, Tgmap1W - ) - LupN, timeaddnotused, Tgmap1N = TsWaveDelay_2015a( - gvfLupN, firstdaytime, timeadd, timestepdec, Tgmap1N - ) + # # # # # # # Calculation of shortwave daytime radiative fluxes # # # # # # # + Kdown = ( + radI * shadow * np.sin(altitude * (np.pi / 180)) + + dRad + + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) + ) # *sin(altitude(i) * (pi / 180)) - # # # # Kup # # # # Kup, KupE, KupS, KupW, KupN = Kup_veg_2015a( radI, radD, @@ -588,217 +418,131 @@ def Solweig_2026a_calc( firstdaytime = 0 else: # # # # # # # NIGHTTIME # # # # # # # # - # Nocturnal K fluxes set to 0 - Knight = torch.zeros((rows, cols), device=device) - Kdown = torch.zeros((rows, cols), device=device) - Kwest = torch.zeros((rows, cols), device=device) - Kup = torch.zeros((rows, cols), device=device) - Keast = torch.zeros((rows, cols), device=device) - Ksouth = torch.zeros((rows, cols), device=device) - Knorth = torch.zeros((rows, cols), device=device) - KsideI = torch.zeros((rows, cols), device=device) - KsideD = torch.zeros((rows, cols), device=device) - F_sh = torch.zeros((rows, cols), device=device) - shadow = torch.zeros((rows, cols), device=device) - CI_TgG = deepcopy(CI) - dRad = torch.zeros((rows, cols), device=device) - Kside = torch.zeros((rows, cols), device=device) - wallsun = torch.zeros((rows, cols), device=device) Tgwall = 0 + # CI_Tg = -999 # F_sh = [] - # # # # Ldown # # # # - Ldown = ( - (svf + svfveg - 1) * esky * SBC * ((Ta + 273.15) ** 4) - + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) - + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) - + (2 - svf - svfveg) - * (1 - ewall) - * esky - * SBC - * ((Ta + 273.15) ** 4) - ) # Jonsson et al.(2006) - # Ldown = Ldown - 25 # Shown by Jonsson et al.(2006) and Duarte et al.(2006) - - if CI < 0.95: # non - clear conditions - c = 1 - CI - Ldown = Ldown * (1 - c) + c * ( - (svf + svfveg - 1) * SBC * ((Ta + 273.15) ** 4) - + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) - + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) - + (2 - svf - svfveg) * (1 - ewall) * SBC * ((Ta + 273.15) ** 4) - ) # NOT REALLY TESTED!!! BUT MORE CORRECT? - - # Surface temperature parameterization - if groundScheme == 1: - # calculate the ground surface temperature, and relevant heat fluxes - Tg, Rn, Rn_past, G = surfaceTemperature_calc( - Kdown, - Ldown, - Rn, - Rn_past, - G, - Tg, - Tm, - alb_grid, - emis_grid, - cap_grid, - diff_grid, - lc_grid, - a1_grid, - a2_grid, - a3_grid, - timeStep, - RH, - shadow, - shadow_past, - ) - else: - # In the old scheme the ground surface temperature is equal to the air temperature during nighttime - Tg = torch.ones((rows, cols), device=device) * Ta + # Nocturnal K fluxes set to 0 + Knight = np.zeros((rows, cols)) + Kdown = np.zeros((rows, cols)) + Kwest = np.zeros((rows, cols)) + Kup = np.zeros((rows, cols)) + Keast = np.zeros((rows, cols)) + Ksouth = np.zeros((rows, cols)) + Knorth = np.zeros((rows, cols)) + KsideI = np.zeros((rows, cols)) + KsideD = np.zeros((rows, cols)) + F_sh = np.zeros((rows, cols)) + Tg = np.zeros((rows, cols)) + shadow = np.zeros((rows, cols)) + CI_Tg = deepcopy(CI) + CI_TgG = deepcopy(CI) + dRad = np.zeros((rows, cols)) + Kside = np.zeros((rows, cols)) - # Calculate the outgoing longwave radiation - if outgoingLW == 1: - # # # # Lup, daytime # # # # - ( - Lup, - gvfalb, - gvfalbnosh, - LupE, - gvfalbE, - gvfalbnoshE, - LupS, - gvfalbS, - gvfalbnoshS, - LupW, - gvfalbW, - gvfalbnoshW, - LupN, - gvfalbN, - gvfalbnoshN, - gvfLsideW, - gvfLsideS, - gvfLsideE, - gvfLsideN, - ) = outgoingLongwave_calc( - Tg, - Tgwall, - Ta, - Ldown, - emis_grid, - alb_grid, - buildings, - shadow, - wallsun, - walls, - rows, - cols, - 1 / scale, - ) + # # # # Lup # # # # + Lup = SBC * emis_grid * ((Knight + Ta + Tg + 273.15) ** 4) + if landcover == 1: + Lup[lc_grid == 3] = ( + SBC * 0.98 * (Twater + 273.15) ** 4 + ) # nocturnal Water temp - else: - # # # # Lup, nighttime # # # # - Lup = SBC * emis_grid * ((Knight + Tg + 273.15) ** 4) - LupE = Lup - LupS = Lup - LupW = Lup - LupN = Lup + LupE = Lup + LupS = Lup + LupW = Lup + LupN = Lup + + # # For Tg output in POIs + TgOut = Ta + Tg I0 = 0 timeadd = 0 firstdaytime = 1 + # # # # Ldown # # # # + Ldown = ( + (svf + svfveg - 1) * esky * SBC * ((Ta + 273.15) ** 4) + + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) + + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + + (2 - svf - svfveg) * (1 - ewall) * esky * SBC * ((Ta + 273.15) ** 4) + ) # Jonsson et al.(2006) + # Ldown = Ldown - 25 # Shown by Jonsson et al.(2006) and Duarte et al.(2006) + + if CI < 0.95: # non - clear conditions + c = 1 - CI + Ldown = Ldown * (1 - c) + c * ( + (svf + svfveg - 1) * SBC * ((Ta + 273.15) ** 4) + + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) + + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + + (2 - svf - svfveg) * (1 - ewall) * SBC * ((Ta + 273.15) ** 4) + ) # NOT REALLY TESTED!!! BUT MORE CORRECT? + # # # # Lside # # # # - if groundScheme == 1: - Least = torch.clone(gvfLsideE) - Lsouth = torch.clone(gvfLsideS) - Lwest = torch.clone(gvfLsideW) - Lnorth = torch.clone(gvfLsideN) - Least_, Lsouth_, Lwest_, Lnorth_ = Lside_veg_v2026( - svfS, - svfW, - svfN, - svfE, - svfEveg, - svfSveg, - svfWveg, - svfNveg, - svfEaveg, - svfSaveg, - svfWaveg, - svfNaveg, - azimuth, + Least, Lsouth, Lwest, Lnorth = Lside_veg_v2022a( + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + azimuth, + altitude, + Ta, + Tgwall, + SBC, + ewall, + Ldown, + esky, + t, + F_sh, + CI, + LupE, + LupS, + LupW, + LupN, + anisotropic_sky, + ) + + # New parameterization scheme for wall temperatures + if wallScheme == 1: + # albedo_g = 0.15 #TODO Change to correct + if altitude < 0: + wallsh_ = 0 + voxelTable = wall_surface_temperature( + voxelTable, + wallsh_, altitude, - Ta, - Tgwall, - SBC, - ewall, - Ldown, - esky, - t, - F_sh, - CI, - anisotropic_sky, - ) - else: - Least = torch.zeros_like(Ldown) - Lnorth = torch.zeros_like(Ldown) - Lwest = torch.zeros_like(Ldown) - Lsouth = torch.zeros_like(Ldown) - Least_, Lsouth_, Lwest_, Lnorth_ = Lside_veg_v2022a( - svfS, - svfW, - svfN, - svfE, - svfEveg, - svfSveg, - svfWveg, - svfNveg, - svfEaveg, - svfSaveg, - svfWaveg, - svfNaveg, azimuth, - altitude, - Ta, - Tgwall, - SBC, - ewall, + timeStep, + radI, + radD, + radG, Ldown, + Lup, + Ta, esky, - t, - F_sh, - CI, - LupE, - LupS, - LupW, - LupN, - anisotropic_sky, ) - - Least += Least_ - Lsouth += Lsouth_ - Lwest += Lwest_ - Lnorth += Lnorth_ - Lside = (Lsouth + Lnorth + Least + Lwest) / 4 - - ### Anisotropic sky + # Anisotropic sky if anisotropic_sky == 1: if "lv" not in locals(): # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) skyvaultalt, skyvaultazi, _, _, _, _, _ = create_patches( - patch_option, device + patch_option ) - patch_emissivities = torch.zeros( - skyvaultalt.shape[0], device=device - ) + patch_emissivities = np.zeros(skyvaultalt.shape[0]) - x = torch.transpose(torch.atleast_2d(skyvaultalt)) - y = torch.transpose(torch.atleast_2d(skyvaultazi)) - z = torch.transpose(torch.atleast_2d(patch_emissivities)) + x = np.transpose(np.atleast_2d(skyvaultalt)) + y = np.transpose(np.atleast_2d(skyvaultazi)) + z = np.transpose(np.atleast_2d(patch_emissivities)) - L_patches = torch.append(torch.append(x, y, axis=1), z, axis=1) + L_patches = np.append(np.append(x, y, axis=1), z, axis=1) else: L_patches = deepcopy(lv) @@ -823,7 +567,7 @@ def Solweig_2026a_calc( ( Ldown, - Lside_, + Lside, Lside_sky, Lside_veg, Lside_sh, @@ -874,9 +618,8 @@ def Solweig_2026a_calc( KupN, i, ) - Lside += Lside_ else: - Lside_ = torch.zeros((rows, cols), device=device) + Lside = np.zeros((rows, cols)) L_patches = None # Box and anisotropic longwave @@ -886,7 +629,7 @@ def Solweig_2026a_calc( Lnorth += Lnorth_ Lsouth += Lsouth_ - # Calculation of radiant flux density + # # # # Calculation of radiant flux density and Tmrt # # # # # Human body considered as a cylinder with isotropic all-sky diffuse if cyl == 1 and anisotropic_sky == 0: Sstr = absK * ( @@ -908,16 +651,15 @@ def Solweig_2026a_calc( + Lside * Fcyl + (Lnorth + Least + Lsouth + Lwest) * Fside ) - # Human body considered as a standing cube - else: + # Knorth = nan Ksouth = nan Kwest = nan Keast = nan + else: # Human body considered as a standing cube Sstr = absK * ( (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside ) + absL * ( (Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside ) - # # # # Tmrt # # # # - Tmrt = torch.sqrt(torch.sqrt((Sstr / (absL * SBC)))) - 273.2 + Tmrt = np.sqrt(np.sqrt((Sstr / (absL * SBC)))) - 273.2 # Add longwave to cardinal directions for output in POI if (cyl == 1) and (anisotropic_sky == 1): @@ -955,18 +697,17 @@ def Solweig_2026a_calc( Lwest, Lnorth, KsideI, + TgOut1, + TgOut, radI, radD, Lside, L_patches, + CI_Tg, CI_TgG, KsideD, dRad, Kside, steradians, voxelTable, - Rn, - Rn_past, - Tm, - G, ) diff --git a/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing_torch.py b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing_torch.py new file mode 100644 index 0000000..48af240 --- /dev/null +++ b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing_torch.py @@ -0,0 +1,970 @@ +""" +@author Fredrik Lindberg, University of Gothenburg +""" + +from __future__ import absolute_import +from .daylen_torch import daylen +from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b_torch import ( + clearnessindex_2013b, +) +from ...util.SEBESOLWEIGCommonFiles.diffusefraction_torch import diffusefraction +from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_13_torch import ( + shadowingfunction_wallheight_13, +) +from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_23_torch import ( + shadowingfunction_wallheight_23, +) +from .gvf_2018a import gvf_2018a +from .cylindric_wedge_torch import cylindric_wedge +from .TsWaveDelay_2015a import TsWaveDelay_2015a +from .Kup_veg_2015a_torch import Kup_veg_2015a + + +from .Kside_veg_v2022a_torch import Kside_veg_v2022a +from ...util.SEBESOLWEIGCommonFiles.Perez_v3_torch import Perez_v3 +from ...util.SEBESOLWEIGCommonFiles.create_patches_torch import create_patches + +# Anisotropic longwave +from .Lside_veg_torch import Lside_veg_v2022a, Lside_veg_v2026 +from .anisotropic_sky import anisotropic_sky as ani_sky +from .patch_radiation import patch_steradians +from copy import deepcopy +try: + import torch +except: + pass + +# Ground surface temperature +from .ground_surface_torch import surfaceTemperature_calc, outgoingLongwave_calc + + +def Solweig_2026a_calc( + i, + dsm, + scale, + rows, + cols, + svf, + svfN, + svfW, + svfE, + svfS, + svfveg, + svfNveg, + svfEveg, + svfSveg, + svfWveg, + svfaveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + vegdem, + vegdem2, + albedo_b, + absK, + absL, + ewall, + Fside, + Fup, + Fcyl, + altitude, + azimuth, + zen, + jday, + usevegdem, + onlyglobal, + buildings, + location, + psi, + landcover, + lc_grid, + dectime, + altmax, + dirwalls, + walls, + cyl, + elvis, + Ta, + RH, + radG, + radD, + radI, + P, + amaxvalue, + bush, + Twater, + TgK, + Tstart, + alb_grid, + emis_grid, + TgK_wall, + Tstart_wall, + TmaxLST, + TmaxLST_wall, + first, + second, + svfalfa, + svfbuveg, + firstdaytime, + timeadd, + timestepdec, + Tgmap1, + Tgmap1E, + Tgmap1S, + Tgmap1W, + Tgmap1N, + CI, + diffsh, + shmat, + vegshmat, + vbshvegshmat, + anisotropic_sky, + asvf, + patch_option, + voxelMaps, + voxelTable, + ws, + wallScheme, + timeStep, + steradians, + walls_scheme, + dirwalls_scheme, + groundScheme, + outgoingLW, + Tg, + Rn, + Rn_past, + G, + Tm, + cap_grid, + diff_grid, + a1_grid, + a2_grid, + a3_grid, + shadow_past, +): + """ + This is the core function of the SOLWEIG model + 2016-Aug-28 + Fredrik Lindberg, fredrikl@gvc.gu.se + Goteborg Urban Climate Group + Gothenburg University + + :Input variables: + dsm = digital surface model + scale = height to pixel size (2m pixel gives scale = 0.5) + svf,svfN,svfW,svfE,svfS = SVFs for building and ground + svfveg,svfNveg,svfEveg,svfSveg,svfWveg = Veg SVFs blocking sky + svfaveg,svfEaveg,svfSaveg,svfWaveg,svfNaveg = Veg SVFs blocking buildings + vegdem = Vegetation canopy DSM + vegdem2 = Vegetation trunk zone DSM + albedo_b = building wall albedo + absK = human absorption coefficient for shortwave radiation + absL = human absorption coefficient for longwave radiation + ewall = Emissivity of building walls + Fside = The angular factors between a person and the surrounding surfaces + Fup = The angular factors between a person and the surrounding surfaces + Fcyl = The angular factors between a culidric person and the surrounding surfaces + altitude = Sun altitude (degree) + azimuth = Sun azimuth (degree) + zen = Sun zenith angle (radians) + jday = day of year + usevegdem = use vegetation scheme + onlyglobal = calculate dir and diff from global shortwave (Reindl et al. 1990) + buildings = Boolena grid to identify building pixels + location = geographic location + height = height of measurements point (center of gravity of human) + psi = 1 - Transmissivity of shortwave through vegetation + landcover = use landcover scheme !!!NEW IN 2015a!!! + lc_grid = grid with landcoverclasses + lc_class = table with landcover properties + dectime = decimal time + altmax = maximum sun altitude + dirwalls = aspect of walls + walls = one pixel row outside building footprint. height of building walls + cyl = consider man as cylinder instead of cude + elvis = dummy + Ta = air temp + RH + radG = global radiation + radD = diffuse + radI = direct + P = pressure + amaxvalue = max height of buildings + bush = grid representing bushes + Twater = temperature of water (daily) + TgK, Tstart, TgK_wall, Tstart_wall, TmaxLST,TmaxLST_wall, + alb_grid, emis_grid = albedo and emmissivity on ground + first, second = conneted to old Ts model (source area based on Smidt et al.) + svfalfa = SVF recalculated to angle + svfbuveg = complete SVF + firstdaytime, timeadd, timestepdec, Tgmap1, Tgmap1E, Tgmap1S, Tgmap1W, Tgmap1N, + CI = Clearness index + TgOut1 = old Ts model + diffsh, ani = Used in anisotrpic models (Wallenberg et al. 2019, 2022) + """ + + # # # Core program start # # # + # Instrument offset in degrees + t = 0.0 + + # Stefan Bolzmans Constant + SBC = 5.67051e-8 + + # Degrees to radians + deg2rad = torch.pi / 180 + device = ( + Tg.device + if isinstance(Tg, torch.Tensor) + else ( + Ta.device + if isinstance(Ta, torch.Tensor) + else torch.device("cuda" if torch.cuda.is_available() else "cpu") + ) + ) + + # Find sunrise decimal hour - new from 2014a + _, _, _, SNUP = daylen(jday, location["latitude"]) + + # Vapor pressure in hPa + ea = 6.107 * 10 ** ((7.5 * Ta) / (237.3 + Ta)) * (RH / 100.0) + + # Determination of clear-sky emissivity from Prata (1996) + msteg = 46.5 * (ea / (Ta + 273.15)) + esky = ( + 1 - (1 + msteg) * torch.exp(-((1.2 + 3.0 * msteg) ** 0.5)) + ) + elvis # -0.04 old error from Jonsson et al.2006 + + if altitude > 0: # # # # # # DAYTIME # # # # # # + # Clearness Index on Earth's surface after Crawford and Dunchon (1999) with a correction + # factor for low sun elevations after Lindberg et al.(2008) + I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( + zen, jday, Ta, RH / 100.0, radG, location, P + ) + if (CI > 1) or (CI == torch.inf): + CI = 1 + + # Estimation of radD and radI if not measured after Reindl et al.(1990) + if onlyglobal == 1: + I0, CI, Kt, I0et, CIuncorr = clearnessindex_2013b( + zen, jday, Ta, RH / 100.0, radG, location, P + ) + if (CI > 1) or (CI == torch.inf): + CI = 1 + + radI, radD = diffusefraction(radG, altitude, Kt, Ta, RH) + + # Diffuse Radiation + # Anisotropic Diffuse Radiation after Perez et al. 1993 + if anisotropic_sky == 1: + patchchoice = 1 + zenDeg = zen * (180 / torch.pi) + # Relative luminance + lv, pc_, pb_ = Perez_v3( + zenDeg, + azimuth, + radD, + radI, + jday, + patchchoice, + patch_option, + device, + ) + # Total relative luminance from sky, i.e. from each patch, into each cell + aniLum = torch.zeros((rows, cols), device=device) + for idx in range(lv.shape[0]): + aniLum += diffsh[:, :, idx] * lv[idx, 2] + + dRad = ( + aniLum * radD + ) # Total diffuse radiation from sky into each cell + else: + dRad = radD * svfbuveg + patchchoice = 1 + lv = None + + # Shadow images + if usevegdem == 1: + vegsh, sh, _, wallsh, wallsun, wallshve, _, facesun, wallsh_ = ( + shadowingfunction_wallheight_23( + dsm, + vegdem, + vegdem2, + azimuth, + altitude, + scale, + amaxvalue, + bush, + walls, + dirwalls * torch.pi / 180.0, + walls_scheme, + dirwalls_scheme * torch.pi / 180.0, + ) + ) + shadow = sh - (1 - vegsh) * (1 - psi) + else: + sh, wallsh, wallsun, facesh, facesun, wallsh_ = ( + shadowingfunction_wallheight_13( + dsm, + azimuth, + altitude, + scale, + walls, + dirwalls * torch.pi / 180.0, + walls_scheme, + dirwalls_scheme * torch.pi / 180.0, + ) + ) + shadow = sh + + # Building height angle from svf + F_sh = cylindric_wedge( + zen, svfalfa, rows, cols + ) # Fraction shadow on building walls based on sun alt and svf + F_sh[torch.isnan(F_sh)] = 0.5 + + # New estimation of Tgwall with reduction for non-clear situation based on Reindl et al. 1990 + radI0, _ = diffusefraction(I0, altitude, 1.0, Ta, RH) + radG0 = radI0 * (torch.sin(altitude * deg2rad)) + _ + corr = ( + 0.1473 * torch.log(90 - (zen / torch.pi * 180)) + 0.3454 + ) # 20070329 correction of lat, Lindberg et al. 2008 + CI_TgG = (radG / radG0) + (1 - corr) + if (CI_TgG > 1) or (CI_TgG == torch.inf): + CI_TgG = 1 + + Tgampwall = TgK_wall * altmax + Tstart_wall + Tgwall = Tgampwall * torch.sin( + ( + ((dectime - torch.floor(dectime)) - SNUP / 24) + / (TmaxLST_wall / 24 - SNUP / 24) + ) + * torch.pi + / 2 + ) # 2015a, based on max sun altitude + if Tgwall < 0: # temporary for removing low Tg during morning 20130205 + Tgwall = 0 + Tgwall = Tgwall * CI_TgG + + # # # # Kdown # # # # + Kdown = ( + radI * shadow * torch.sin(altitude * (torch.pi / 180)) + + dRad + + albedo_b * (1 - svfbuveg) * (radG * (1 - F_sh) + radD * F_sh) + ) # *sin(altitude(i) * (pi / 180)) + + # # # # Ldown # # # # + Ldown = ( + (svf + svfveg - 1) * esky * SBC * ((Ta + 273.15) ** 4) + + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) + + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + + (2 - svf - svfveg) + * (1 - ewall) + * esky + * SBC + * ((Ta + 273.15) ** 4) + ) # Jonsson et al.(2006) + # Ldown = Ldown - 25 # Shown by Jonsson et al.(2006) and Duarte et al.(2006) + if CI < 0.95: # non - clear conditions + c = 1 - CI + Ldown = Ldown * (1 - c) + c * ( + (svf + svfveg - 1) * SBC * ((Ta + 273.15) ** 4) + + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) + + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + + (2 - svf - svfveg) * (1 - ewall) * SBC * ((Ta + 273.15) ** 4) + ) # NOT REALLY TESTED!!! BUT MORE CORRECT? + + # Surface temperature parameterization during daytime + if groundScheme == 1: + # calculate the ground surface temperature, and relevant heat fluxes + Tg, Rn, Rn_past, G = surfaceTemperature_calc( + Kdown, + Ldown, + Rn, + Rn_past, + G, + Tg, + Tm, + alb_grid, + emis_grid, + cap_grid, + diff_grid, + lc_grid, + a1_grid, + a2_grid, + a3_grid, + timeStep, + RH, + shadow, + shadow_past, + ) + + else: + # using max sun alt instead of dfm + Tgamp = TgK * altmax + Tstart # Fixed 2021 + Tgdiff = Tgamp * torch.sin( + ( + ((dectime - torch.floor(dectime)) - SNUP / 24) + / (TmaxLST / 24 - SNUP / 24) + ) + * torch.pi + / 2 + ) # 2015 a, based on max sun altitude + + Tgdiff = Tgdiff * CI_TgG # new estimation + + # For Tg output in POIs + TgTemp = Tgdiff * shadow + Ta + _, timeadd, Tg = TsWaveDelay_2015a( + TgTemp, firstdaytime, timeadd, timestepdec, Tg + ) # timeadd only here v2021a + + if landcover == 1: + Tg[Tg < 0] = ( + 0 # temporary for removing low Tg during morning 20130205 + ) + + # Calculate the outgoing longwave radiation + if outgoingLW == 1: + + # # # # Lup, daytime # # # # + ( + Lup, + gvfalb, + gvfalbnosh, + LupE, + gvfalbE, + gvfalbnoshE, + LupS, + gvfalbS, + gvfalbnoshS, + LupW, + gvfalbW, + gvfalbnoshW, + LupN, + gvfalbN, + gvfalbnoshN, + gvfLsideW, + gvfLsideS, + gvfLsideE, + gvfLsideN, + ) = outgoingLongwave_calc( + Tg, + Tgwall, + Ta, + Ldown, + emis_grid, + alb_grid, + buildings, + shadow, + wallsun, + walls, + rows, + cols, + 1 / scale, + ) + + else: + ### Ground View Factors + ( + gvfLup, + gvfalb, + gvfalbnosh, + gvfLupE, + gvfalbE, + gvfalbnoshE, + gvfLupS, + gvfalbS, + gvfalbnoshS, + gvfLupW, + gvfalbW, + gvfalbnoshW, + gvfLupN, + gvfalbN, + gvfalbnoshN, + gvfSum, + gvfNorm, + ) = gvf_2018a( + wallsun, + walls, + buildings, + scale, + shadow, + first, + second, + dirwalls, + Tg, + Tgwall, + Ta, + emis_grid, + ewall, + alb_grid, + SBC, + albedo_b, + rows, + cols, + Twater, + lc_grid, + landcover, + ) + + # # # # Lup, daytime # # # # + # Surface temperature wave delay - new as from 2014a + Lup, timeaddnotused, Tgmap1 = TsWaveDelay_2015a( + gvfLup, firstdaytime, timeadd, timestepdec, Tgmap1 + ) + LupE, timeaddnotused, Tgmap1E = TsWaveDelay_2015a( + gvfLupE, firstdaytime, timeadd, timestepdec, Tgmap1E + ) + LupS, timeaddnotused, Tgmap1S = TsWaveDelay_2015a( + gvfLupS, firstdaytime, timeadd, timestepdec, Tgmap1S + ) + LupW, timeaddnotused, Tgmap1W = TsWaveDelay_2015a( + gvfLupW, firstdaytime, timeadd, timestepdec, Tgmap1W + ) + LupN, timeaddnotused, Tgmap1N = TsWaveDelay_2015a( + gvfLupN, firstdaytime, timeadd, timestepdec, Tgmap1N + ) + + # # # # Kup # # # # + Kup, KupE, KupS, KupW, KupN = Kup_veg_2015a( + radI, + radD, + radG, + altitude, + svfbuveg, + albedo_b, + F_sh, + gvfalb, + gvfalbE, + gvfalbS, + gvfalbW, + gvfalbN, + gvfalbnosh, + gvfalbnoshE, + gvfalbnoshS, + gvfalbnoshW, + gvfalbnoshN, + ) + + Keast, Ksouth, Kwest, Knorth, KsideI, KsideD, Kside = Kside_veg_v2022a( + radI, + radD, + radG, + shadow, + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + azimuth, + altitude, + psi, + t, + albedo_b, + F_sh, + KupE, + KupS, + KupW, + KupN, + cyl, + lv, + anisotropic_sky, + diffsh, + rows, + cols, + asvf, + shmat, + vegshmat, + vbshvegshmat, + ) + + firstdaytime = 0 + + else: # # # # # # # NIGHTTIME # # # # # # # # + # Nocturnal K fluxes set to 0 + Knight = torch.zeros((rows, cols), device=device) + Kdown = torch.zeros((rows, cols), device=device) + Kwest = torch.zeros((rows, cols), device=device) + Kup = torch.zeros((rows, cols), device=device) + Keast = torch.zeros((rows, cols), device=device) + Ksouth = torch.zeros((rows, cols), device=device) + Knorth = torch.zeros((rows, cols), device=device) + KsideI = torch.zeros((rows, cols), device=device) + KsideD = torch.zeros((rows, cols), device=device) + F_sh = torch.zeros((rows, cols), device=device) + shadow = torch.zeros((rows, cols), device=device) + CI_TgG = deepcopy(CI) + dRad = torch.zeros((rows, cols), device=device) + Kside = torch.zeros((rows, cols), device=device) + wallsun = torch.zeros((rows, cols), device=device) + + Tgwall = 0 + + # # # # Ldown # # # # + Ldown = ( + (svf + svfveg - 1) * esky * SBC * ((Ta + 273.15) ** 4) + + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) + + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + + (2 - svf - svfveg) + * (1 - ewall) + * esky + * SBC + * ((Ta + 273.15) ** 4) + ) # Jonsson et al.(2006) + # Ldown = Ldown - 25 # Shown by Jonsson et al.(2006) and Duarte et al.(2006) + + if CI < 0.95: # non - clear conditions + c = 1 - CI + Ldown = Ldown * (1 - c) + c * ( + (svf + svfveg - 1) * SBC * ((Ta + 273.15) ** 4) + + (2 - svfveg - svfaveg) * ewall * SBC * ((Ta + 273.15) ** 4) + + (svfaveg - svf) * ewall * SBC * ((Ta + 273.15 + Tgwall) ** 4) + + (2 - svf - svfveg) * (1 - ewall) * SBC * ((Ta + 273.15) ** 4) + ) # NOT REALLY TESTED!!! BUT MORE CORRECT? + + # Surface temperature parameterization + if groundScheme == 1: + # calculate the ground surface temperature, and relevant heat fluxes + Tg, Rn, Rn_past, G = surfaceTemperature_calc( + Kdown, + Ldown, + Rn, + Rn_past, + G, + Tg, + Tm, + alb_grid, + emis_grid, + cap_grid, + diff_grid, + lc_grid, + a1_grid, + a2_grid, + a3_grid, + timeStep, + RH, + shadow, + shadow_past, + ) + else: + # In the old scheme the ground surface temperature is equal to the air temperature during nighttime + Tg = torch.ones((rows, cols), device=device) * Ta + + # Calculate the outgoing longwave radiation + if outgoingLW == 1: + # # # # Lup, daytime # # # # + ( + Lup, + gvfalb, + gvfalbnosh, + LupE, + gvfalbE, + gvfalbnoshE, + LupS, + gvfalbS, + gvfalbnoshS, + LupW, + gvfalbW, + gvfalbnoshW, + LupN, + gvfalbN, + gvfalbnoshN, + gvfLsideW, + gvfLsideS, + gvfLsideE, + gvfLsideN, + ) = outgoingLongwave_calc( + Tg, + Tgwall, + Ta, + Ldown, + emis_grid, + alb_grid, + buildings, + shadow, + wallsun, + walls, + rows, + cols, + 1 / scale, + ) + + else: + # # # # Lup, nighttime # # # # + Lup = SBC * emis_grid * ((Knight + Tg + 273.15) ** 4) + LupE = Lup + LupS = Lup + LupW = Lup + LupN = Lup + + I0 = 0 + timeadd = 0 + firstdaytime = 1 + + # # # # Lside # # # # + if groundScheme == 1: + Least = torch.clone(gvfLsideE) + Lsouth = torch.clone(gvfLsideS) + Lwest = torch.clone(gvfLsideW) + Lnorth = torch.clone(gvfLsideN) + Least_, Lsouth_, Lwest_, Lnorth_ = Lside_veg_v2026( + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + azimuth, + altitude, + Ta, + Tgwall, + SBC, + ewall, + Ldown, + esky, + t, + F_sh, + CI, + anisotropic_sky, + ) + else: + Least = torch.zeros_like(Ldown) + Lnorth = torch.zeros_like(Ldown) + Lwest = torch.zeros_like(Ldown) + Lsouth = torch.zeros_like(Ldown) + Least_, Lsouth_, Lwest_, Lnorth_ = Lside_veg_v2022a( + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + azimuth, + altitude, + Ta, + Tgwall, + SBC, + ewall, + Ldown, + esky, + t, + F_sh, + CI, + LupE, + LupS, + LupW, + LupN, + anisotropic_sky, + ) + + Least += Least_ + Lsouth += Lsouth_ + Lwest += Lwest_ + Lnorth += Lnorth_ + Lside = (Lsouth + Lnorth + Least + Lwest) / 4 + + ### Anisotropic sky + if anisotropic_sky == 1: + if "lv" not in locals(): + # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) + skyvaultalt, skyvaultazi, _, _, _, _, _ = create_patches( + patch_option, device + ) + + patch_emissivities = torch.zeros( + skyvaultalt.shape[0], device=device + ) + + x = torch.transpose(torch.atleast_2d(skyvaultalt)) + y = torch.transpose(torch.atleast_2d(skyvaultazi)) + z = torch.transpose(torch.atleast_2d(patch_emissivities)) + + L_patches = torch.append(torch.append(x, y, axis=1), z, axis=1) + + else: + L_patches = deepcopy(lv) + + # Calculate steradians for patches if it is the first model iteration + if i == 0: + steradians, skyalt, patch_altitude = patch_steradians(L_patches) + + # Create lv from L_patches if nighttime, i.e. lv does not exist + if altitude < 0: + # CI = deepcopy(CI) + lv = deepcopy(L_patches) + KupE = 0 + KupS = 0 + KupW = 0 + KupN = 0 + + # Adjust sky emissivity under semi-cloudy/hazy/cloudy/overcast conditions, i.e. CI lower than 0.95 + if CI < 0.95: + esky_c = CI * esky + (1 - CI) * 1.0 + esky = esky_c + + ( + Ldown, + Lside_, + Lside_sky, + Lside_veg, + Lside_sh, + Lside_sun, + Lside_ref, + Least_, + Lwest_, + Lnorth_, + Lsouth_, + Keast, + Ksouth, + Kwest, + Knorth, + KsideI, + KsideD, + Kside, + steradians, + skyalt, + ) = ani_sky( + shmat, + vegshmat, + vbshvegshmat, + altitude, + azimuth, + asvf, + cyl, + esky, + L_patches, + wallScheme, + voxelTable, + voxelMaps, + steradians, + Ta, + Tgwall, + ewall, + Lup, + radI, + radD, + radG, + lv, + albedo_b, + 0, + diffsh, + shadow, + KupE, + KupS, + KupW, + KupN, + i, + ) + Lside += Lside_ + else: + Lside_ = torch.zeros((rows, cols), device=device) + L_patches = None + + # Box and anisotropic longwave + if cyl == 0 and anisotropic_sky == 1: + Least += Least_ + Lwest += Lwest_ + Lnorth += Lnorth_ + Lsouth += Lsouth_ + + # Calculation of radiant flux density + # Human body considered as a cylinder with isotropic all-sky diffuse + if cyl == 1 and anisotropic_sky == 0: + Sstr = absK * ( + KsideI * Fcyl + + (Kdown + Kup) * Fup + + (Knorth + Keast + Ksouth + Kwest) * Fside + ) + absL * ( + (Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside + ) + # Human body considered as a cylinder with Perez et al. (1993) (anisotropic sky diffuse) + # and Martin and Berdahl (1984) (anisotropic sky longwave) + elif cyl == 1 and anisotropic_sky == 1: + Sstr = absK * ( + Kside * Fcyl + + (Kdown + Kup) * Fup + + (Knorth + Keast + Ksouth + Kwest) * Fside + ) + absL * ( + (Ldown + Lup) * Fup + + Lside * Fcyl + + (Lnorth + Least + Lsouth + Lwest) * Fside + ) + # Human body considered as a standing cube + else: + Sstr = absK * ( + (Kdown + Kup) * Fup + (Knorth + Keast + Ksouth + Kwest) * Fside + ) + absL * ( + (Ldown + Lup) * Fup + (Lnorth + Least + Lsouth + Lwest) * Fside + ) + + # # # # Tmrt # # # # + Tmrt = torch.sqrt(torch.sqrt((Sstr / (absL * SBC)))) - 273.2 + + # Add longwave to cardinal directions for output in POI + if (cyl == 1) and (anisotropic_sky == 1): + Least += Least_ + Lwest += Lwest_ + Lnorth += Lnorth_ + Lsouth += Lsouth_ + + return ( + Tmrt, + Kdown, + Kup, + Ldown, + Lup, + Tg, + ea, + esky, + I0, + CI, + shadow, + firstdaytime, + timestepdec, + timeadd, + Tgmap1, + Tgmap1E, + Tgmap1S, + Tgmap1W, + Tgmap1N, + Keast, + Ksouth, + Kwest, + Knorth, + Least, + Lsouth, + Lwest, + Lnorth, + KsideI, + radI, + radD, + Lside, + L_patches, + CI_TgG, + KsideD, + dRad, + Kside, + steradians, + voxelTable, + Rn, + Rn_past, + Tm, + G, + ) diff --git a/functions/SOLWEIGpython/Solweig_run.py b/functions/SOLWEIGpython/Solweig_run.py index 5318a55..1d899cc 100644 --- a/functions/SOLWEIGpython/Solweig_run.py +++ b/functions/SOLWEIGpython/Solweig_run.py @@ -6,8 +6,6 @@ # sommon imports from __future__ import absolute_import - -import numpy as np from ...util.umep_solweig_export_component import read_solweig_config from ...util.SEBESOLWEIGCommonFiles.Solweig_v2015_metdata_noload import ( Solweig_2015a_metdata_noload, @@ -16,7 +14,9 @@ clearnessindex_2013b, ) -from ...functions.SOLWEIGpython import Solweig_2026a_calc_forprocessing as so +from ...functions.SOLWEIGpython import ( + Solweig_2026a_calc_forprocessing as so, +) # from ...functions.SOLWEIGpython import WriteMetadataSOLWEIG # Not needed anymore? from ...functions.SOLWEIGpython import PET_calculations as p @@ -28,20 +28,22 @@ from ...functions.SOLWEIGpython.wallsAsNetCDF import walls_as_netcdf from ...functions.SOLWEIGpython.Tgmaps_v1 import Tgmaps_v1 from ...functions import wallalgorithms as wa -from ...functions.SOLWEIGpython.ground_surface import initiate_groundScheme +from ...functions.SOLWEIGpython.ground_surface import ( + initiate_groundScheme, +) +import numpy as np import json import zipfile import pandas as pd import matplotlib.pylab as plt from shutil import copyfile -import torch # imports from osgeo/qgis dependency try: from osgeo import gdal from osgeo.gdalconst import * from ...util.misc import saveraster, xy2latlon_fromraster - from qgis.core import QgsRasterLayer + from qgis.core import QgsVectorLayer, QgsRasterLayer except: pass @@ -56,12 +58,15 @@ pass + def solweig_run(configPath, feedback): """ Input: configPath : config file including geodata paths and settings. feedback : To communicate with qgis gui. Set to None if standalone """ + + print("Running cpu") # Load config file configDict = read_solweig_config(configPath) @@ -70,8 +75,9 @@ def solweig_run(configPath, feedback): with open(configDict["para_json_path"], "r") as jsn: param = json.load(jsn) - # reading variables from config and parameters that is not yet presented standAlone = int(configDict["standalone"]) + + # reading variables from config and parameters that is not yet presented cyl = int(configDict["cyl"]) albedo_b = param["Albedo"]["Effective"]["Value"]["Walls"] ewall = param["Emissivity"]["Value"]["Walls"] @@ -80,19 +86,6 @@ def solweig_run(configPath, feedback): absK = param["Tmrt_params"]["Value"]["absK"] absL = param["Tmrt_params"]["Value"]["absL"] - # --- Load on CPU or GPU config - device = torch.device("cpu") - print("passe") - - if configDict["calculation_mode"] == "gpu" and torch.cuda.is_available(): - device = torch.device("cuda") - print("using gpu") - - else: - print("using cpu") - - standAlone = int(configDict["standalone"]) - # Load DSM if standAlone == 1: dsm, dsm_transf, dsm_crs = common.load_raster( @@ -117,7 +110,7 @@ def solweig_run(configPath, feedback): dsm_wkt = QgsRasterLayer(configDict["filepath_dsm"]).crs().toWkt() gdal_dsm = gdal.Open(configDict["filepath_dsm"]) lat, lon, scale, minx, miny = xy2latlon_fromraster(dsm_wkt, gdal_dsm) - dsm = torch.from_numpy(gdal_dsm.ReadAsArray().astype(float)).to(device) + dsm = gdal_dsm.ReadAsArray().astype(float) nd = gdal_dsm.GetRasterBand(1).GetNoDataValue() rows = dsm.shape[0] @@ -126,12 +119,12 @@ def solweig_run(configPath, feedback): # response to issue #85 dsm[dsm == nd] = 0.0 if dsm.min() < 0: - dsmraise = torch.abs(dsm.min()) + dsmraise = np.abs(dsm.min()) dsm = dsm + dsmraise else: dsmraise = 0 - alt = torch.median(dsm) + alt = np.median(dsm) if alt < 0: alt = 3 @@ -141,26 +134,22 @@ def solweig_run(configPath, feedback): usevegdem = int(configDict["usevegdem"]) if usevegdem == 1: if standAlone == 0: - vegdsm = torch.from_numpy( - ( - gdal.Open(configDict["filepath_cdsm"]) - .ReadAsArray() - .astype(float) - ) - ).to(device) + vegdsm = ( + gdal.Open(configDict["filepath_cdsm"]) + .ReadAsArray() + .astype(float) + ) else: vegdsm, _, _ = common.load_raster( configDict["filepath_cdsm"], bbox=None ) if configDict["filepath_tdsm"] != "": if standAlone == 0: - vegdsm2 = torch.from_numpy( - ( - gdal.Open(configDict["filepath_tdsm"]) - .ReadAsArray() - .astype(float) - ) - ).to(device) + vegdsm2 = ( + gdal.Open(configDict["filepath_tdsm"]) + .ReadAsArray() + .astype(float) + ) else: vegdsm2, _, _ = common.load_raster( configDict["filepath_tdsm"], bbox=None @@ -175,19 +164,17 @@ def solweig_run(configPath, feedback): landcover = int(configDict["landcover"]) if landcover == 1: if standAlone == 0: - lcgrid = torch.from_numpy( - ( - gdal.Open(configDict["filepath_lc"]) - .ReadAsArray() - .astype(float) - ) - ).to(device) + lcgrid = ( + gdal.Open(configDict["filepath_lc"]) + .ReadAsArray() + .astype(float) + ) else: lcgrid, _, _ = common.load_raster( configDict["filepath_lc"], bbox=None ) else: - lcgrid = torch.ones_like(dsm, device=device) + lcgrid = np.ones_like(dsm) # DEM for buildings #TODO: fix nodata in standalone demforbuild = int(configDict["demforbuild"]) @@ -196,9 +183,7 @@ def solweig_run(configPath, feedback): gdal_dem = gdal.Open( configDict["filepath_dem"] ) # .ReadAsArray().astype(float) - dem = torch.from_numpy(gdal_dem.ReadAsArray().astype(float)).to( - device - ) + dem = gdal_dem.ReadAsArray().astype(float) nd = gdal_dem.GetRasterBand(1).GetNoDataValue() else: dem, _, _ = common.load_raster( @@ -209,7 +194,7 @@ def solweig_run(configPath, feedback): # response to issue and #230 dem[dem == nd] = 0.0 if dem.min() < 0: - demraise = torch.abs(dem.min()) + demraise = np.abs(dem.min()) dem = dem + demraise else: demraise = 0 @@ -220,41 +205,31 @@ def solweig_run(configPath, feedback): zip.close() if standAlone == 0: - svf = torch.from_numpy( - ( - gdal.Open(configDict["working_dir"] + "/svf.tif") - .ReadAsArray() - .astype(float) - ) - ).to(device) - svfN = torch.from_numpy( - ( - gdal.Open(configDict["working_dir"] + "/svfN.tif") - .ReadAsArray() - .astype(float) - ) - ).to(device) - svfS = torch.from_numpy( - ( - gdal.Open(configDict["working_dir"] + "/svfS.tif") - .ReadAsArray() - .astype(float) - ) - ).to(device) - svfE = torch.from_numpy( - ( - gdal.Open(configDict["working_dir"] + "/svfE.tif") - .ReadAsArray() - .astype(float) - ) - ).to(device) - svfW = torch.from_numpy( - ( - gdal.Open(configDict["working_dir"] + "/svfW.tif") - .ReadAsArray() - .astype(float) - ) - ).to(device) + svf = ( + gdal.Open(configDict["working_dir"] + "/svf.tif") + .ReadAsArray() + .astype(float) + ) + svfN = ( + gdal.Open(configDict["working_dir"] + "/svfN.tif") + .ReadAsArray() + .astype(float) + ) + svfS = ( + gdal.Open(configDict["working_dir"] + "/svfS.tif") + .ReadAsArray() + .astype(float) + ) + svfE = ( + gdal.Open(configDict["working_dir"] + "/svfE.tif") + .ReadAsArray() + .astype(float) + ) + svfW = ( + gdal.Open(configDict["working_dir"] + "/svfW.tif") + .ReadAsArray() + .astype(float) + ) else: svf, _, _ = common.load_raster( configDict["working_dir"] + "/svf.tif", bbox=None @@ -274,77 +249,57 @@ def solweig_run(configPath, feedback): if usevegdem == 1: if standAlone == 0: - svfveg = torch.from_numpy( - ( - gdal.Open(configDict["working_dir"] + "/svfveg.tif") - .ReadAsArray() - .astype(float) - ) - ).to(device) - svfNveg = torch.from_numpy( - ( - gdal.Open(configDict["working_dir"] + "/svfNveg.tif") - .ReadAsArray() - .astype(float) - ) - ).to(device) - svfSveg = torch.from_numpy( - ( - gdal.Open(configDict["working_dir"] + "/svfSveg.tif") - .ReadAsArray() - .astype(float) - ) - ).to(device) - svfEveg = torch.from_numpy( - ( - gdal.Open(configDict["working_dir"] + "/svfEveg.tif") - .ReadAsArray() - .astype(float) - ) - ).to(device) - svfWveg = torch.from_numpy( - ( - gdal.Open(configDict["working_dir"] + "/svfWveg.tif") - .ReadAsArray() - .astype(float) - ) - ).to(device) + svfveg = ( + gdal.Open(configDict["working_dir"] + "/svfveg.tif") + .ReadAsArray() + .astype(float) + ) + svfNveg = ( + gdal.Open(configDict["working_dir"] + "/svfNveg.tif") + .ReadAsArray() + .astype(float) + ) + svfSveg = ( + gdal.Open(configDict["working_dir"] + "/svfSveg.tif") + .ReadAsArray() + .astype(float) + ) + svfEveg = ( + gdal.Open(configDict["working_dir"] + "/svfEveg.tif") + .ReadAsArray() + .astype(float) + ) + svfWveg = ( + gdal.Open(configDict["working_dir"] + "/svfWveg.tif") + .ReadAsArray() + .astype(float) + ) - svfaveg = torch.from_numpy( - ( - gdal.Open(configDict["working_dir"] + "/svfaveg.tif") - .ReadAsArray() - .astype(float) - ) - ).to(device) - svfNaveg = torch.from_numpy( - ( - gdal.Open(configDict["working_dir"] + "/svfNaveg.tif") - .ReadAsArray() - .astype(float) - ) - ).to(device) - svfSaveg = torch.from_numpy( - ( - gdal.Open(configDict["working_dir"] + "/svfSaveg.tif") - .ReadAsArray() - .astype(float) - ) - ).to(device) - svfEaveg = torch.from_numpy( - ( - gdal.Open(configDict["working_dir"] + "/svfEaveg.tif") - .ReadAsArray() - .astype(float) - ) - ).to(device) - svfWaveg = torch.from_numpy( - ( - gdal.Open(configDict["working_dir"] + "/svfWaveg.tif") - .ReadAsArray() - .astype(float) - ) - ).to(device) + svfaveg = ( + gdal.Open(configDict["working_dir"] + "/svfaveg.tif") + .ReadAsArray() + .astype(float) + ) + svfNaveg = ( + gdal.Open(configDict["working_dir"] + "/svfNaveg.tif") + .ReadAsArray() + .astype(float) + ) + svfSaveg = ( + gdal.Open(configDict["working_dir"] + "/svfSaveg.tif") + .ReadAsArray() + .astype(float) + ) + svfEaveg = ( + gdal.Open(configDict["working_dir"] + "/svfEaveg.tif") + .ReadAsArray() + .astype(float) + ) + svfWaveg = ( + gdal.Open(configDict["working_dir"] + "/svfWaveg.tif") + .ReadAsArray() + .astype(float) + ) else: svfveg, _, _ = common.load_raster( configDict["working_dir"] + "/svfveg.tif", bbox=None @@ -378,29 +333,29 @@ def solweig_run(configPath, feedback): configDict["working_dir"] + "/svfWaveg.tif", bbox=None ) else: - svfveg = torch.ones((rows, cols)).to(device) - svfNveg = torch.ones((rows, cols)).to(device) - svfSveg = torch.ones((rows, cols)).to(device) - svfEveg = torch.ones((rows, cols)).to(device) - svfWveg = torch.ones((rows, cols)).to(device) - svfaveg = torch.ones((rows, cols)).to(device) - svfNaveg = torch.ones((rows, cols)).to(device) - svfSaveg = torch.ones((rows, cols)).to(device) - svfEaveg = torch.ones((rows, cols)).to(device) - svfWaveg = torch.ones((rows, cols)).to(device) + svfveg = np.ones((rows, cols)) + svfNveg = np.ones((rows, cols)) + svfSveg = np.ones((rows, cols)) + svfEveg = np.ones((rows, cols)) + svfWveg = np.ones((rows, cols)) + svfaveg = np.ones((rows, cols)) + svfNaveg = np.ones((rows, cols)) + svfSaveg = np.ones((rows, cols)) + svfEaveg = np.ones((rows, cols)) + svfWaveg = np.ones((rows, cols)) tmp = svf + svfveg - 1.0 tmp[tmp < 0.0] = 0.0 # %matlab crazyness around 0 - svfalfa = torch.arcsin(torch.exp((torch.log((1.0 - tmp)) / 2.0))) + svfalfa = np.arcsin(np.exp((np.log((1.0 - tmp)) / 2.0))) if standAlone == 0: - wallheight = torch.from_numpy( - (gdal.Open(configDict["filepath_wh"]).ReadAsArray().astype(float)) - ).to(device) - wallaspect = torch.from_numpy( - (gdal.Open(configDict["filepath_wa"]).ReadAsArray().astype(float)) - ).to(device) + wallheight = ( + gdal.Open(configDict["filepath_wh"]).ReadAsArray().astype(float) + ) + wallaspect = ( + gdal.Open(configDict["filepath_wa"]).ReadAsArray().astype(float) + ) else: wallheight, _, _ = common.load_raster( configDict["filepath_wh"], bbox=None @@ -414,11 +369,9 @@ def solweig_run(configPath, feedback): delim = " " Twater = [] - metdata = torch.from_numpy( - np.loadtxt( - configDict["input_met"], skiprows=headernum, delimiter=delim - ) - ).to(device) + metdata = np.loadtxt( + configDict["input_met"], skiprows=headernum, delimiter=delim + ) location = {"longitude": lon, "latitude": lat, "altitude": alt} YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax = ( @@ -448,22 +401,6 @@ def solweig_run(configPath, feedback): "poi_field" ] # self.parameterAsString(parameters, self.POI_FIELD, context) if standAlone == 0: - # vlayer = QgsVectorLayer(configDict['poi_file'], 'point', 'ogr') - # idx = vlayer.fields().indexFromName(poi_field) - # numfeat = vlayer.featureCount() - # poisxy = torch.zeros((numfeat, 3)) - 999 - # ind = 0 - # for f in vlayer.getFeatures(): # looping through each POI - # y = f.geometry().centroid().asPoint().y() - # x = f.geometry().centroid().asPoint().x() - # poiname.append(f.attributes()[idx]) - # poisxy[ind, 0] = ind - # poisxy[ind, 1] = torch.round((x - minx) * scale) - # if miny >= 0: - # poisxy[ind, 2] = torch.round((miny + rows * (1. / scale) - y) * scale) - # else: - # poisxy[ind, 2] = torch.round((miny + rows * (1. / scale) - y) * scale) - # ind += 1 poi_field = configDict[ "woi_field" @@ -475,7 +412,7 @@ def solweig_run(configPath, feedback): else: pois_gdf = gpd.read_file(configDict["poi_file"]) numfeat = pois_gdf.shape[0] - poisxy = torch.zeros((numfeat, 3)).to(device) - 999 + poisxy = np.zeros((numfeat, 3)) - 999 for idx, row in pois_gdf.iterrows(): y, x = rowcol( dsm_transf, @@ -488,20 +425,12 @@ def solweig_run(configPath, feedback): poisxy[idx, 2] = y for k in range(0, poisxy.shape[0]): - poi_save = [] # torch.zeros((1, 33)) + poi_save = [] # np.zeros((1, 33)) data_out = ( configDict["output_dir"] + "/POI_" + str(poiname[k]) + ".txt" ) np.savetxt( - data_out, - ( - poi_save.cpu().numpy() - if isinstance(poi_save, torch.Tensor) - else poi_save - ), - delimiter=" ", - header=header, - comments="", + data_out, poi_save, delimiter=" ", header=header, comments="" ) # print(poisxy) # Num format for POI output @@ -522,32 +451,28 @@ def solweig_run(configPath, feedback): if param["Tmrt_params"]["Value"]["posture"] == "Standing": Fside = param["Posture"]["Standing"]["Value"]["Fside"] Fup = param["Posture"]["Standing"]["Value"]["Fup"] - height = torch.tensor( - param["Posture"]["Standing"]["Value"]["height"] - ).to(device) + height = param["Posture"]["Standing"]["Value"]["height"] Fcyl = param["Posture"]["Standing"]["Value"]["Fcyl"] pos = 1 else: Fside = param["Posture"]["Sitting"]["Value"]["Fside"] Fup = param["Posture"]["Sitting"]["Value"]["Fup"] - height = torch.tensor( - param["Posture"]["Sitting"]["Value"]["height"] - ).to(device) + height = param["Posture"]["Sitting"]["Value"]["height"] Fcyl = param["Posture"]["Sitting"]["Value"]["Fcyl"] pos = 0 # Radiative surface influence, Rule of thumb by Schmid et al. (1990). - first = torch.round(height) + first = np.round(height) if first == 0.0: first = 1.0 - second = torch.round((height * 20.0)) + second = np.round((height * 20.0)) if usevegdem == 1: # Conifer or deciduous if configDict["conifer_bool"]: - leafon = torch.ones((1, DOY.shape[0])).to(device) + leafon = np.ones((1, DOY.shape[0])) else: - leafon = torch.zeros((1, DOY.shape[0])).to(device) + leafon = np.zeros((1, DOY.shape[0])) if ( param["Tree_settings"]["Value"]["First_day_leaf"] > param["Tree_settings"]["Value"]["Last_day_leaf"] @@ -567,7 +492,7 @@ def solweig_run(configPath, feedback): # amaxvalue vegmax = vegdsm.max() amaxvalue = dsm.max() - dsm.min() - amaxvalue = torch.maximum(amaxvalue, vegmax) + amaxvalue = np.maximum(amaxvalue, vegmax) # Elevation vegdsms if buildingDEM includes ground heights vegdsm = vegdsm + dsm @@ -576,7 +501,7 @@ def solweig_run(configPath, feedback): vegdsm2[vegdsm2 == dsm] = 0 # % Bush separation - bush = torch.logical_not((vegdsm2 * vegdsm)) * vegdsm + bush = np.logical_not((vegdsm2 * vegdsm)) * vegdsm svfbuveg = svf - (1.0 - svfveg) * ( 1.0 - transVeg @@ -584,20 +509,20 @@ def solweig_run(configPath, feedback): else: psi = leafon * 0.0 + 1.0 svfbuveg = svf - bush = torch.zeros([rows, cols]).to(device) + bush = np.zeros([rows, cols]) amaxvalue = 0 # Initialization of maps - Knight = torch.zeros((rows, cols)).to(device) - Tgmap1 = torch.zeros((rows, cols)).to(device) - Tgmap1E = torch.zeros((rows, cols)).to(device) - Tgmap1S = torch.zeros((rows, cols)).to(device) - Tgmap1W = torch.zeros((rows, cols)).to(device) - Tgmap1N = torch.zeros((rows, cols)).to(device) + Knight = np.zeros((rows, cols)) + Tgmap1 = np.zeros((rows, cols)) + Tgmap1E = np.zeros((rows, cols)) + Tgmap1S = np.zeros((rows, cols)) + Tgmap1W = np.zeros((rows, cols)) + Tgmap1N = np.zeros((rows, cols)) # Create building boolean raster from either land cover or height rasters if demforbuild == 0: - buildings = lcgrid.clone() + buildings = np.copy(lcgrid) buildings[buildings == 7] = 1 buildings[buildings == 6] = 1 buildings[buildings == 5] = 1 @@ -614,12 +539,12 @@ def solweig_run(configPath, feedback): saveraster( gdal_dsm, configDict["output_dir"] + "/buildings.tif", - buildings.detach().cpu().numpy(), + buildings, ) else: common.save_raster( configDict["output_dir"] + "/buildings.tif", - buildings.detach().cpu().numpy(), + buildings, dsm_transf, dsm_crs, ) @@ -627,14 +552,12 @@ def solweig_run(configPath, feedback): # Import shadow matrices (Anisotropic sky) anisotropic_sky = int(configDict["aniso"]) if anisotropic_sky == 1: # UseAniso - data = torch.load( - configDict["input_aniso"], map_location=device, weights_only=True - ) + data = np.load(configDict["input_aniso"]) shmat = data["shadowmat"] vegshmat = data["vegshadowmat"] vbshvegshmat = data["vbshmat"] if usevegdem == 1: - diffsh = torch.zeros((rows, cols, shmat.shape[2])).to(device) + diffsh = np.zeros((rows, cols, shmat.shape[2])) for i in range(0, shmat.shape[2]): diffsh[:, :, i] = shmat[:, :, i] - (1 - vegshmat[:, :, i]) * ( 1 - transVeg @@ -653,10 +576,10 @@ def solweig_run(configPath, feedback): patch_option = 4 # patch_option = 4 # 612 patches # asvf to calculate sunlit and shaded patches - asvf = torch.arccos(torch.sqrt(svf)) + asvf = np.arccos(np.sqrt(svf)) # Empty array for steradians - steradians = torch.zeros((shmat.shape[2])).to(device) + steradians = np.zeros((shmat.shape[2])) else: # anisotropic_sky = 0 diffsh = None @@ -666,7 +589,7 @@ def solweig_run(configPath, feedback): asvf = None patch_option = 0 steradians = 0 - shadow = torch.zeros_like(dsm).to(device) + shadow = np.zeros_like(dsm) # % Ts parameterisation maps if landcover == 1.0: @@ -680,7 +603,7 @@ def solweig_run(configPath, feedback): Tstart_wall, TmaxLST, TmaxLST_wall, - ] = Tgmaps_v1(lcgrid.clone(), param) + ] = Tgmaps_v1(lcgrid.copy(), param) else: TgK = Knight + param["Ts_deg"]["Value"]["Cobble_stone_2014a"] Tstart = Knight - param["Tstart"]["Value"]["Cobble_stone_2014a"] @@ -693,7 +616,7 @@ def solweig_run(configPath, feedback): TgK_wall = param["Ts_deg"]["Value"]["Walls"] Tstart_wall = param["Tstart"]["Value"]["Walls"] TmaxLST_wall = param["TmaxLST"]["Value"]["Walls"] - + # Parameterization for the 2026 ground scheme groundSurface = int(configDict["groundmodel"]) if groundSurface == 1: @@ -701,7 +624,7 @@ def solweig_run(configPath, feedback): if configDict["input_surf"] != "": surfData = pd.read_csv(configDict["input_surf"]) Tg = surfData["Tg"] - Tm = torch.mean(surfData["Tg"]) + Tm = np.mean(surfData["Tg"]) ( _, _, @@ -714,7 +637,7 @@ def solweig_run(configPath, feedback): a2_grid, a3_grid, ) = initiate_groundScheme( - lcgrid.copy(), param, DOY[0], Ta, location, device + lcgrid.copy(), param, DOY[0], Ta, location ) else: ( @@ -729,20 +652,18 @@ def solweig_run(configPath, feedback): a2_grid, a3_grid, ) = initiate_groundScheme( - lcgrid.clone(), param, DOY[0], Ta, location, device + lcgrid.copy(), param, DOY[0], Ta, location ) else: pass - + # Replace the ground view factors with integration of solid angles - outgoingLW = int(configDict["outgoingLW"]) - + outgoingLW = int(configDict["outgoinglongwave"]) + # Import data for wall temperature parameterization TODO: fix for standalone wallScheme = int(configDict["wallscheme"]) if wallScheme == 1: - wallData = torch.load( - configDict["input_wall"], map_location=device, weights_only=True - ) + wallData = np.load(configDict["input_wall"]) voxelMaps = wallData["voxelId"] voxelTable = wallData["voxelTable"] # Get wall type from standalone @@ -761,7 +682,7 @@ def solweig_run(configPath, feedback): # Calculate wall height for wall scheme, i.e. include corners (thicker walls) walls_scheme = wa.findwalls_sp( - dsm, 2, torch.tensor([[1, 1, 1], [1, 0, 1], [1, 1, 1]]).to(device) + dsm, 2, np.array([[1, 1, 1], [1, 0, 1], [1, 1, 1]]) ) # Calculate wall aspect for wall scheme, i.e. include corners (thicker walls) dirwalls_scheme = wa.filter1Goodwin_as_aspect_v3( @@ -770,16 +691,16 @@ def solweig_run(configPath, feedback): # Used in wall temperature parameterization scheme first_timestep = ( - pd.to_datetime(int(YYYY[0][0].item()), format="%Y") - + pd.to_timedelta(DOY[0].item() - 1, unit="d") - + pd.to_timedelta(hours[0].item(), unit="h") - + pd.to_timedelta(minu[0].item(), unit="m") + pd.to_datetime(YYYY[0][0], format="%Y") + + pd.to_timedelta(DOY[0] - 1, unit="d") + + pd.to_timedelta(hours[0], unit="h") + + pd.to_timedelta(minu[0], unit="m") ) second_timestep = ( - pd.to_datetime(int(YYYY[0][1].item()), format="%Y") - + pd.to_timedelta(DOY[1].item() - 1, unit="d") - + pd.to_timedelta(hours[1].item(), unit="h") - + pd.to_timedelta(minu[1].item(), unit="m") + pd.to_datetime(YYYY[0][1], format="%Y") + + pd.to_timedelta(DOY[1] - 1, unit="d") + + pd.to_timedelta(hours[1], unit="h") + + pd.to_timedelta(minu[1], unit="m") ) timeStep = (second_timestep - first_timestep).seconds @@ -814,7 +735,7 @@ def solweig_run(configPath, feedback): else: pois_gdf = gpd.read_file(configDict["poi_file"]) numfeat = pois_gdf.shape[0] - poisxy = torch.zeros((numfeat, 3)).to(device) - 999 + poisxy = np.zeros((numfeat, 3)) - 999 for idx, row in pois_gdf.iterrows(): y, x = rowcol( dsm_transf, @@ -840,8 +761,8 @@ def solweig_run(configPath, feedback): voxelTable = 0 timeStep = 0 # thermal_effusivity = 0 - walls_scheme = torch.ones((rows, cols)).to(device) * 10.0 - dirwalls_scheme = torch.ones((rows, cols)).to(device) * 10.0 + walls_scheme = np.ones((rows, cols)) * 10.0 + dirwalls_scheme = np.ones((rows, cols)) * 10.0 # Initialisation of time related variables if Ta.__len__() == 1: @@ -866,23 +787,21 @@ def solweig_run(configPath, feedback): CI = 1.0 # Main loop - tmrtplot = torch.zeros((rows, cols)).to(device) + tmrtplot = np.zeros((rows, cols)) # Initiate array for I0 values - if torch.unique(DOY).shape[0] > 1: - unique_days = torch.unique(DOY) + if np.unique(DOY).shape[0] > 1: + unique_days = np.unique(DOY) first_unique_day = DOY[DOY == unique_days[0]] - I0_array = torch.zeros((first_unique_day.shape[0])).to(device) + I0_array = np.zeros((first_unique_day.shape[0])) else: - first_unique_day = DOY.clone() - I0_array = torch.zeros((DOY.shape[0])).to(device) + first_unique_day = DOY.copy() + I0_array = np.zeros((DOY.shape[0])) if standAlone == 1: progress = tqdm(total=Ta.__len__()) - else: - progress = None - for i in torch.arange(0, Ta.__len__()): + for i in np.arange(0, Ta.__len__()): if feedback is not None: feedback.setProgress( int(i * (100.0 / Ta.__len__())) @@ -890,19 +809,19 @@ def solweig_run(configPath, feedback): if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") break - elif progress is not None: + else: progress.update(1) # Daily water body temperature if landcover == 1: - if ((dectime[i] - torch.floor(dectime[i]))) == 0 or (i == 0): - Twater = torch.mean(Ta[jday[0] == torch.floor(dectime[i])]) + if ((dectime[i] - np.floor(dectime[i]))) == 0 or (i == 0): + Twater = np.mean(Ta[jday[0] == np.floor(dectime[i])]) # Nocturnal cloudfraction from Offerle et al. 2003 - if (dectime[i] - torch.floor(dectime[i])) == 0: - daylines = torch.where(torch.floor(dectime) == dectime[i]) + if (dectime[i] - np.floor(dectime[i])) == 0: + daylines = np.where(np.floor(dectime) == dectime[i]) if daylines.__len__() > 1: alt = altitude[0][daylines] - alt2 = torch.where(alt > 1) + alt2 = np.where(alt > 1) rise = alt2[0][0] [_, CI, _, _, _] = clearnessindex_2013b( zen[0, i + rise + 1], @@ -913,33 +832,24 @@ def solweig_run(configPath, feedback): location, P[i + rise + 1], ) - if (CI > 1.0) or (CI == torch.inf): + if (CI > 1.0) or (CI == np.inf): CI = 1.0 else: CI = 1.0 - # Only if Kdir is derived from horizontal global shortwave and horizontal diffuse shortwave - # if altitude[0][i] > 0: - # radI[i] = radI[i]/torch.sin(altitude[0][i] * torch.pi/180) - # else: - # radG[i] = 0. - # radD[i] = 0. - # radI[i] = 0. - # Timestep of the simulation used in the ground scheme calculation first_timestep = ( - pd.to_datetime(int(YYYY[0][0].item()), format="%Y") - + pd.to_timedelta(DOY[0].item() - 1, unit="d") - + pd.to_timedelta(hours[0].item(), unit="h") - + pd.to_timedelta(minu[0].item(), unit="m") + pd.to_datetime(YYYY[0][0], format="%Y") + + pd.to_timedelta(DOY[0] - 1, unit="d") + + pd.to_timedelta(hours[0], unit="h") + + pd.to_timedelta(minu[0], unit="m") ) second_timestep = ( - pd.to_datetime(int(YYYY[0][1].item()), format="%Y") - + pd.to_timedelta(DOY[1].item() - 1, unit="d") - + pd.to_timedelta(hours[1].item(), unit="h") - + pd.to_timedelta(minu[1].item(), unit="m") + pd.to_datetime(YYYY[0][1], format="%Y") + + pd.to_timedelta(DOY[1] - 1, unit="d") + + pd.to_timedelta(hours[1], unit="h") + + pd.to_timedelta(minu[1], unit="m") ) - timeStep = (second_timestep - first_timestep).seconds ( @@ -1077,7 +987,7 @@ def solweig_run(configPath, feedback): steradians, walls_scheme, dirwalls_scheme, - groundScheme, + groundSurface, outgoingLW, Tg, Rn, @@ -1119,7 +1029,7 @@ def solweig_run(configPath, feedback): # Write to POIs if not poisxy is None: for k in range(0, poisxy.shape[0]): - poi_save = torch.zeros((1, 40)).to(device) + poi_save = np.zeros((1, 40)) poi_save[0, 0] = YYYY[0][i] poi_save[0, 1] = jday[0][i] poi_save[0, 2] = hours[i] @@ -1191,15 +1101,7 @@ def solweig_run(configPath, feedback): ) # f_handle = file(data_out, 'a') f_handle = open(data_out, "ab") - np.savetxt( - f_handle, - ( - poi_save.cpu().numpy() - if isinstance(poi_save, torch.Tensor) - else poi_save - ), - fmt=numformat, - ) + np.savetxt(f_handle, poi_save, fmt=numformat) f_handle.close() # If wall temperature parameterization scheme is in use @@ -1237,14 +1139,12 @@ def solweig_run(configPath, feedback): ), "wallShade", ].to_numpy() - temp_all = torch.concatenate( + temp_all = np.concatenate( [temp_wall, K_in, L_in, wallShade] - ).to(device) - # temp_all = torch.concatenate([temp_wall]) - # wall_data = torch.zeros((1, 7 + temp_wall.shape[0])) - wall_data = torch.zeros((1, 7 + temp_all.shape[0])).to( - device ) + # temp_all = np.concatenate([temp_wall]) + # wall_data = np.zeros((1, 7 + temp_wall.shape[0])) + wall_data = np.zeros((1, 7 + temp_all.shape[0])) # Part of file name (wallid), i.e. WOI_wallid.txt data_out = ( configDict["output_dir"] @@ -1289,15 +1189,7 @@ def solweig_run(configPath, feedback): ) # Open file, add data, save f_handle = open(data_out, "ab") - np.savetxt( - f_handle, - ( - wall_data.cpu().numpy() - if isinstance(wall_data, torch.Tensor) - else wall_data - ), - fmt=woi_numformat, - ) + np.savetxt(f_handle, wall_data, fmt=woi_numformat) f_handle.close() # Save wall temperature/radiation as NetCDF TODO: fix for standAlone? @@ -1340,12 +1232,12 @@ def solweig_run(configPath, feedback): saveraster( gdal_dsm, configDict["output_dir"] + "/Tmrt_" + time_code + ".tif", - Tmrt.detach().cpu().numpy(), + Tmrt, ) else: common.save_raster( configDict["output_dir"] + "/Tmrt_" + time_code + ".tif", - Tmrt.detach().cpu().numpy(), + Tmrt, dsm_transf, dsm_crs, ) @@ -1354,12 +1246,12 @@ def solweig_run(configPath, feedback): saveraster( gdal_dsm, configDict["output_dir"] + "/Kup_" + time_code + ".tif", - Kup.detach().cpu().numpy(), + Kup, ) else: common.save_raster( configDict["output_dir"] + "/Kup_" + time_code + ".tif", - Kup.detach().cpu().numpy(), + Kup, dsm_transf, dsm_crs, ) @@ -1368,12 +1260,12 @@ def solweig_run(configPath, feedback): saveraster( gdal_dsm, configDict["output_dir"] + "/Kdown_" + time_code + ".tif", - Kdown.detach().cpu().numpy(), + Kdown, ) else: common.save_raster( configDict["output_dir"] + "/Kdown_" + time_code + ".tif", - Kdown.detach().cpu().numpy(), + Kdown, dsm_transf, dsm_crs, ) @@ -1382,12 +1274,12 @@ def solweig_run(configPath, feedback): saveraster( gdal_dsm, configDict["output_dir"] + "/Lup_" + time_code + ".tif", - Lup.detach().cpu().numpy(), + Lup, ) else: common.save_raster( configDict["output_dir"] + "/Lup_" + time_code + ".tif", - Lup.detach().cpu().numpy(), + Lup, dsm_transf, dsm_crs, ) @@ -1396,12 +1288,12 @@ def solweig_run(configPath, feedback): saveraster( gdal_dsm, configDict["output_dir"] + "/Ldown_" + time_code + ".tif", - Ldown.detach().cpu().numpy(), + Ldown, ) else: common.save_raster( configDict["output_dir"] + "/Ldown_" + time_code + ".tif", - Ldown.detach().cpu().numpy(), + Ldown, dsm_transf, dsm_crs, ) @@ -1410,12 +1302,12 @@ def solweig_run(configPath, feedback): saveraster( gdal_dsm, configDict["output_dir"] + "/Shadow_" + time_code + ".tif", - shadow.detach().cpu().numpy(), + shadow, ) else: common.save_raster( configDict["output_dir"] + "/Shadow_" + time_code + ".tif", - shadow.detach().cpu().numpy(), + shadow, dsm_transf, dsm_crs, ) @@ -1425,12 +1317,12 @@ def solweig_run(configPath, feedback): saveraster( gdal_dsm, configDict["output_dir"] + "/Kdiff_" + time_code + ".tif", - dRad.detach().cpu().numpy(), + dRad, ) else: common.save_raster( configDict["output_dir"] + "/Kdiff_" + time_code + ".tif", - dRad.detach().cpu().numpy(), + dRad, dsm_transf, dsm_crs, ) @@ -1493,7 +1385,7 @@ def solweig_run(configPath, feedback): "%1.2f", "%i", ) - settingsData = torch.tensor( + settingsData = np.array( [ [ int(configDict["utc"]), @@ -1512,15 +1404,11 @@ def solweig_run(configPath, feedback): patch_option, ] ] - ).to(device) + ) # print(settingsData) np.savetxt( configDict["output_dir"] + "/treeplantersettings.txt", - ( - settingsData.cpu().numpy() - if isinstance(settingsData, torch.Tensor) - else settingsData - ), + settingsData, fmt=settingsFmt, header=settingsHeader, delimiter=" ", @@ -1536,9 +1424,7 @@ def solweig_run(configPath, feedback): ) # fix average Tmrt instead of sum, 20191022 if standAlone == 0: saveraster( - gdal_dsm, - configDict["output_dir"] + "/Tmrt_average.tif", - tmrtplot.detach().cpu().numpy(), + gdal_dsm, configDict["output_dir"] + "/Tmrt_average.tif", tmrtplot ) else: common.save_raster( @@ -1546,4 +1432,4 @@ def solweig_run(configPath, feedback): tmrtplot, dsm_transf, dsm_crs, - ) + ) \ No newline at end of file diff --git a/functions/SOLWEIGpython/Solweig_run_torch.py b/functions/SOLWEIGpython/Solweig_run_torch.py new file mode 100644 index 0000000..07a6775 --- /dev/null +++ b/functions/SOLWEIGpython/Solweig_run_torch.py @@ -0,0 +1,1556 @@ +# This is the main function of the SOLWEIG model +# 2025-Mar-21 +# Fredrik Lindberg, fredrikl@gvc.gu.se +# Goteborg Urban Climate Group +# Gothenburg University + +# sommon imports +from __future__ import absolute_import + +import numpy as np +from ...util.umep_solweig_export_component import read_solweig_config +from ...util.SEBESOLWEIGCommonFiles.Solweig_v2015_metdata_noload_torch import ( + Solweig_2015a_metdata_noload, +) +from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b_torch import ( + clearnessindex_2013b, +) + +from . import Solweig_2026a_calc_forprocessing_torch as so + +# from ...functions.SOLWEIGpython import WriteMetadataSOLWEIG # Not needed anymore? +from . import PET_calculations as p +from . import UTCI_calculations as utci +from .CirclePlotBar import PolarBarPlot +from .wall_surface_temperature import load_walls +from .wallOfInterest import pointOfInterest +from .patch_characteristics import hemispheric_image +from .wallsAsNetCDF import walls_as_netcdf +from .Tgmaps_v1_torch import Tgmaps_v1 +from .. import wallalgorithms_torch as wa +from .ground_surface_torch import initiate_groundScheme +import json +import zipfile +import pandas as pd +import matplotlib.pylab as plt +from shutil import copyfile +try: + import torch +except: + pass +# imports from osgeo/qgis dependency +try: + from osgeo import gdal + from osgeo.gdalconst import * + from ...util.misc import saveraster, xy2latlon_fromraster + from qgis.core import QgsRasterLayer +except: + pass + +# imports for standalone +try: + from umep import common + from rasterio.transform import xy, rowcol + import pyproj + from tqdm import tqdm + import geopandas as gpd +except: + pass + + +def solweig_run(configPath, feedback): + """ + Input: + configPath : config file including geodata paths and settings. + feedback : To communicate with qgis gui. Set to None if standalone + """ + # Check if torch is the fake version from torch_fallback.py then tells the + # user to install the real pip module + if type(torch).__name__ == "MetaMock" or hasattr(torch, "__getattr__"): + raise ImportError( + "\n[UMEP Error] PyTorch is required to run gpu mode.\n" + "Please install it using: pip install torch in your venv or using osgeo4w" + ) + + + # Load config file + configDict = read_solweig_config(configPath) + + # Load parameters settings for SOLWEIG + with open(configDict["para_json_path"], "r") as jsn: + param = json.load(jsn) + + # reading variables from config and parameters that is not yet presented + standAlone = int(configDict["standalone"]) + cyl = int(configDict["cyl"]) + albedo_b = param["Albedo"]["Effective"]["Value"]["Walls"] + ewall = param["Emissivity"]["Value"]["Walls"] + onlyglobal = int(configDict["onlyglobal"]) + elvis = 0.0 + absK = param["Tmrt_params"]["Value"]["absK"] + absL = param["Tmrt_params"]["Value"]["absL"] + + # --- Load on CPU or GPU config + device = torch.device("cpu") + + if configDict["calculation_mode"] == "gpu" and torch.cuda.is_available(): + device = torch.device("cuda") + print("using gpu") + + else: + print("using cpu") + + standAlone = int(configDict["standalone"]) + + # Load DSM + if standAlone == 1: + dsm, dsm_transf, dsm_crs = common.load_raster( + configDict["filepath_dsm"], bbox=None + ) + scale = 1 / dsm_transf.a + # dsm_height, dsm_width = dsm.shape # y rows by x cols + # y is flipped - so return max for lower row + minx, miny = xy(dsm_transf, dsm.shape[0], 0) + # Define the source and target CRS + source_crs = pyproj.CRS(dsm_crs) + target_crs = pyproj.CRS(4326) # WGS 84 + # Create a transformer object + transformer = pyproj.Transformer.from_crs( + source_crs, target_crs, always_xy=True + ) + # Perform the transformation + lon, lat = transformer.transform(minx, miny) + nd = -9999 # TODO: extract nodatavalue from rasterio + else: + # dsmlayer = QgsRasterLayer(configDict['filepath_dsm']) + dsm_wkt = QgsRasterLayer(configDict["filepath_dsm"]).crs().toWkt() + gdal_dsm = gdal.Open(configDict["filepath_dsm"]) + lat, lon, scale, minx, miny = xy2latlon_fromraster(dsm_wkt, gdal_dsm) + dsm = torch.from_numpy(gdal_dsm.ReadAsArray().astype(float)).to(device) + nd = gdal_dsm.GetRasterBand(1).GetNoDataValue() + + rows = dsm.shape[0] + cols = dsm.shape[1] + + # response to issue #85 + dsm[dsm == nd] = 0.0 + if dsm.min() < 0: + dsmraise = torch.abs(dsm.min()) + dsm = dsm + dsmraise + else: + dsmraise = 0 + + alt = torch.median(dsm) + if alt < 0: + alt = 3 + + # Vegetation + transVeg = param["Tree_settings"]["Value"]["Transmissivity"] + trunkratio = param["Tree_settings"]["Value"]["Trunk_ratio"] + usevegdem = int(configDict["usevegdem"]) + if usevegdem == 1: + if standAlone == 0: + vegdsm = torch.from_numpy( + ( + gdal.Open(configDict["filepath_cdsm"]) + .ReadAsArray() + .astype(float) + ) + ).to(device) + else: + vegdsm, _, _ = common.load_raster( + configDict["filepath_cdsm"], bbox=None + ) + if configDict["filepath_tdsm"] != "": + if standAlone == 0: + vegdsm2 = torch.from_numpy( + ( + gdal.Open(configDict["filepath_tdsm"]) + .ReadAsArray() + .astype(float) + ) + ).to(device) + else: + vegdsm2, _, _ = common.load_raster( + configDict["filepath_tdsm"], bbox=None + ) + else: + vegdsm2 = vegdsm * trunkratio + else: + vegdsm = 0 + vegdsm2 = 0 + + # Land cover + landcover = int(configDict["landcover"]) + if landcover == 1: + if standAlone == 0: + lcgrid = torch.from_numpy( + ( + gdal.Open(configDict["filepath_lc"]) + .ReadAsArray() + .astype(float) + ) + ).to(device) + else: + lcgrid, _, _ = common.load_raster( + configDict["filepath_lc"], bbox=None + ) + else: + lcgrid = torch.ones_like(dsm, device=device) + + # DEM for buildings #TODO: fix nodata in standalone + demforbuild = int(configDict["demforbuild"]) + if demforbuild == 1: + if standAlone == 0: + gdal_dem = gdal.Open( + configDict["filepath_dem"] + ) # .ReadAsArray().astype(float) + dem = torch.from_numpy(gdal_dem.ReadAsArray().astype(float)).to( + device + ) + nd = gdal_dem.GetRasterBand(1).GetNoDataValue() + else: + dem, _, _ = common.load_raster( + configDict["filepath_dem"], bbox=None + ) + nd = -9999 # TODO: standAlone nd exposure + + # response to issue and #230 + dem[dem == nd] = 0.0 + if dem.min() < 0: + demraise = torch.abs(dem.min()) + dem = dem + demraise + else: + demraise = 0 + + # SVF + zip = zipfile.ZipFile(configDict["input_svf"], "r") + zip.extractall(configDict["working_dir"]) + zip.close() + + if standAlone == 0: + svf = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svf.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfN = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfN.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfS = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfS.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfE = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfE.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfW = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfW.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + else: + svf, _, _ = common.load_raster( + configDict["working_dir"] + "/svf.tif", bbox=None + ) + svfN, _, _ = common.load_raster( + configDict["working_dir"] + "/svfN.tif", bbox=None + ) + svfS, _, _ = common.load_raster( + configDict["working_dir"] + "/svfS.tif", bbox=None + ) + svfE, _, _ = common.load_raster( + configDict["working_dir"] + "/svfE.tif", bbox=None + ) + svfW, _, _ = common.load_raster( + configDict["working_dir"] + "/svfW.tif", bbox=None + ) + + if usevegdem == 1: + if standAlone == 0: + svfveg = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfveg.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfNveg = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfNveg.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfSveg = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfSveg.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfEveg = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfEveg.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfWveg = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfWveg.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + + svfaveg = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfaveg.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfNaveg = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfNaveg.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfSaveg = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfSaveg.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfEaveg = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfEaveg.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + svfWaveg = torch.from_numpy( + ( + gdal.Open(configDict["working_dir"] + "/svfWaveg.tif") + .ReadAsArray() + .astype(float) + ) + ).to(device) + else: + svfveg, _, _ = common.load_raster( + configDict["working_dir"] + "/svfveg.tif", bbox=None + ) + svfNveg, _, _ = common.load_raster( + configDict["working_dir"] + "/svfNveg.tif", bbox=None + ) + svfSveg, _, _ = common.load_raster( + configDict["working_dir"] + "/svfSveg.tif", bbox=None + ) + svfEveg, _, _ = common.load_raster( + configDict["working_dir"] + "/svfEveg.tif", bbox=None + ) + svfWveg, _, _ = common.load_raster( + configDict["working_dir"] + "/svfWveg.tif", bbox=None + ) + + svfaveg, _, _ = common.load_raster( + configDict["working_dir"] + "/svfaveg.tif", bbox=None + ) + svfNaveg, _, _ = common.load_raster( + configDict["working_dir"] + "/svfNaveg.tif", bbox=None + ) + svfSaveg, _, _ = common.load_raster( + configDict["working_dir"] + "/svfSaveg.tif", bbox=None + ) + svfEaveg, _, _ = common.load_raster( + configDict["working_dir"] + "/svfEaveg.tif", bbox=None + ) + svfWaveg, _, _ = common.load_raster( + configDict["working_dir"] + "/svfWaveg.tif", bbox=None + ) + else: + svfveg = torch.ones((rows, cols)).to(device) + svfNveg = torch.ones((rows, cols)).to(device) + svfSveg = torch.ones((rows, cols)).to(device) + svfEveg = torch.ones((rows, cols)).to(device) + svfWveg = torch.ones((rows, cols)).to(device) + svfaveg = torch.ones((rows, cols)).to(device) + svfNaveg = torch.ones((rows, cols)).to(device) + svfSaveg = torch.ones((rows, cols)).to(device) + svfEaveg = torch.ones((rows, cols)).to(device) + svfWaveg = torch.ones((rows, cols)).to(device) + + tmp = svf + svfveg - 1.0 + tmp[tmp < 0.0] = 0.0 + # %matlab crazyness around 0 + svfalfa = torch.arcsin(torch.exp((torch.log((1.0 - tmp)) / 2.0))) + + if standAlone == 0: + wallheight = torch.from_numpy( + (gdal.Open(configDict["filepath_wh"]).ReadAsArray().astype(float)) + ).to(device) + wallaspect = torch.from_numpy( + (gdal.Open(configDict["filepath_wa"]).ReadAsArray().astype(float)) + ).to(device) + else: + wallheight, _, _ = common.load_raster( + configDict["filepath_wh"], bbox=None + ) + wallheight = torch.tensor(wallheight, device=device) + wallaspect, _, _ = common.load_raster( + configDict["filepath_wa"], bbox=None + ) + wallaspect = torch.tensor(wallaspect, device=device) + + + # Metdata + headernum = 1 + delim = " " + Twater = [] + + metdata = torch.from_numpy( + np.loadtxt( + configDict["input_met"], skiprows=headernum, delimiter=delim + ) + ).to(device) + + location = {"longitude": lon, "latitude": lat, "altitude": alt} + YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax = ( + Solweig_2015a_metdata_noload(metdata, location, int(configDict["utc"])) + ) + + DOY = metdata[:, 1] + hours = metdata[:, 2] + minu = metdata[:, 3] + Ta = metdata[:, 11] + RH = metdata[:, 10] + radG = metdata[:, 14] + radD = metdata[:, 21] + radI = metdata[:, 22] + P = metdata[:, 12] + Ws = metdata[:, 9] + + # POIs check + if configDict["poi_file"] != "": # usePOI: + header = ( + "yyyy id it imin dectime altitude azimuth kdir kdiff kglobal kdown kup keast ksouth " + "kwest knorth ldown lup least lsouth lwest lnorth Ta Tg RH Esky Tmrt " + "I0 CI Shadow SVF_b SVF_bv KsideI PET UTCI CI_TgG KsideD Lside diffDown Kside " + ) + # poiname = [] + poi_field = configDict[ + "poi_field" + ] # self.parameterAsString(parameters, self.POI_FIELD, context) + if standAlone == 0: + # vlayer = QgsVectorLayer(configDict['poi_file'], 'point', 'ogr') + # idx = vlayer.fields().indexFromName(poi_field) + # numfeat = vlayer.featureCount() + # poisxy = torch.zeros((numfeat, 3)) - 999 + # ind = 0 + # for f in vlayer.getFeatures(): # looping through each POI + # y = f.geometry().centroid().asPoint().y() + # x = f.geometry().centroid().asPoint().x() + # poiname.append(f.attributes()[idx]) + # poisxy[ind, 0] = ind + # poisxy[ind, 1] = torch.round((x - minx) * scale) + # if miny >= 0: + # poisxy[ind, 2] = torch.round((miny + rows * (1. / scale) - y) * scale) + # else: + # poisxy[ind, 2] = torch.round((miny + rows * (1. / scale) - y) * scale) + # ind += 1 + + poi_field = configDict[ + "woi_field" + ] # self.parameterAsStrings(parameters, self.WOI_FIELD, context) + poisxy, poiname = pointOfInterest( + configDict["poi_file"], poi_field, scale, gdal_dsm + ) + + else: + pois_gdf = gpd.read_file(configDict["poi_file"]) + numfeat = pois_gdf.shape[0] + poisxy = torch.zeros((numfeat, 3)).to(device) - 999 + for idx, row in pois_gdf.iterrows(): + y, x = rowcol( + dsm_transf, + row["geometry"].centroid.x, + row["geometry"].centroid.y, + ) # TODO: This produce different result since no standalone round coordinates + poiname.append(row[configDict["poi_field"]]) + poisxy[idx, 0] = idx + poisxy[idx, 1] = x + poisxy[idx, 2] = y + + for k in range(0, poisxy.shape[0]): + poi_save = [] # torch.zeros((1, 33)) + data_out = ( + configDict["output_dir"] + "/POI_" + str(poiname[k]) + ".txt" + ) + np.savetxt( + data_out, + ( + poi_save.cpu().numpy() + if isinstance(poi_save, torch.Tensor) + else poi_save + ), + delimiter=" ", + header=header, + comments="", + ) + # print(poisxy) + # Num format for POI output + numformat = "%d %d %d %d %.5f " + "%.2f " * 35 + + # Other PET variables + sensorheight = param["Wind_Height"]["Value"]["magl"] + age = param["PET_settings"]["Value"]["Age"] + mbody = param["PET_settings"]["Value"]["Weight"] + ht = param["PET_settings"]["Value"]["Height"] + clo = param["PET_settings"]["Value"]["clo"] + activity = param["PET_settings"]["Value"]["Activity"] + sex = param["PET_settings"]["Value"]["Sex"] + else: + poisxy = None + + # Posture settings + if param["Tmrt_params"]["Value"]["posture"] == "Standing": + Fside = param["Posture"]["Standing"]["Value"]["Fside"] + Fup = param["Posture"]["Standing"]["Value"]["Fup"] + height = torch.tensor( + param["Posture"]["Standing"]["Value"]["height"] + ).to(device) + Fcyl = param["Posture"]["Standing"]["Value"]["Fcyl"] + pos = 1 + else: + Fside = param["Posture"]["Sitting"]["Value"]["Fside"] + Fup = param["Posture"]["Sitting"]["Value"]["Fup"] + height = torch.tensor( + param["Posture"]["Sitting"]["Value"]["height"] + ).to(device) + Fcyl = param["Posture"]["Sitting"]["Value"]["Fcyl"] + pos = 0 + + # Radiative surface influence, Rule of thumb by Schmid et al. (1990). + first = torch.round(height) + if first == 0.0: + first = 1.0 + second = torch.round((height * 20.0)) + + if usevegdem == 1: + # Conifer or deciduous + if configDict["conifer_bool"]: + leafon = torch.ones((1, DOY.shape[0])).to(device) + else: + leafon = torch.zeros((1, DOY.shape[0])).to(device) + if ( + param["Tree_settings"]["Value"]["First_day_leaf"] + > param["Tree_settings"]["Value"]["Last_day_leaf"] + ): + leaf_bool = ( + DOY > param["Tree_settings"]["Value"]["First_day_leaf"] + ) | (DOY < param["Tree_settings"]["Value"]["Last_day_leaf"]) + else: + leaf_bool = ( + DOY > param["Tree_settings"]["Value"]["First_day_leaf"] + ) & (DOY < param["Tree_settings"]["Value"]["Last_day_leaf"]) + leafon[0, leaf_bool] = 1 + + # % Vegetation transmittivity of shortwave radiation + psi = leafon * transVeg + psi[leafon == 0] = 0.5 + # amaxvalue + vegmax = vegdsm.max() + amaxvalue = dsm.max() - dsm.min() + amaxvalue = torch.maximum(amaxvalue, vegmax) + + # Elevation vegdsms if buildingDEM includes ground heights + vegdsm = vegdsm + dsm + vegdsm[vegdsm == dsm] = 0 + vegdsm2 = vegdsm2 + dsm + vegdsm2[vegdsm2 == dsm] = 0 + + # % Bush separation + bush = torch.logical_not((vegdsm2 * vegdsm)) * vegdsm + + svfbuveg = svf - (1.0 - svfveg) * ( + 1.0 - transVeg + ) # % major bug fixed 20141203 + else: + psi = leafon * 0.0 + 1.0 + svfbuveg = svf + bush = torch.zeros([rows, cols]).to(device) + amaxvalue = 0 + + # Initialization of maps + Knight = torch.zeros((rows, cols)).to(device) + Tgmap1 = torch.zeros((rows, cols)).to(device) + Tgmap1E = torch.zeros((rows, cols)).to(device) + Tgmap1S = torch.zeros((rows, cols)).to(device) + Tgmap1W = torch.zeros((rows, cols)).to(device) + Tgmap1N = torch.zeros((rows, cols)).to(device) + + # Create building boolean raster from either land cover or height rasters + if demforbuild == 0: + buildings = lcgrid.clone() + buildings[buildings == 7] = 1 + buildings[buildings == 6] = 1 + buildings[buildings == 5] = 1 + buildings[buildings == 4] = 1 + buildings[buildings == 3] = 1 + buildings[buildings == 2] = 0 + else: + buildings = dsm - dem + buildings[buildings < 2.0] = 1.0 + buildings[buildings >= 2.0] = 0.0 + + if int(configDict["savebuild"]) == 1: + if standAlone == 0: + saveraster( + gdal_dsm, + configDict["output_dir"] + "/buildings.tif", + buildings.detach().cpu().numpy(), + ) + else: + common.save_raster( + configDict["output_dir"] + "/buildings.tif", + buildings.detach().cpu().numpy(), + dsm_transf, + dsm_crs, + ) + + # Import shadow matrices (Anisotropic sky) + anisotropic_sky = int(configDict["aniso"]) + if anisotropic_sky == 1: # UseAniso + data = torch.load( + configDict["input_aniso"], map_location=device, weights_only=True + ) + shmat = data["shadowmat"] + vegshmat = data["vegshadowmat"] + vbshvegshmat = data["vbshmat"] + if usevegdem == 1: + diffsh = torch.zeros((rows, cols, shmat.shape[2])).to(device) + for i in range(0, shmat.shape[2]): + diffsh[:, :, i] = shmat[:, :, i] - (1 - vegshmat[:, :, i]) * ( + 1 - transVeg + ) # changes in psi not implemented yet + else: + diffsh = shmat + + # Estimate number of patches based on shadow matrices + if shmat.shape[2] == 145: + patch_option = 1 # patch_option = 1 # 145 patches + elif shmat.shape[2] == 153: + patch_option = 2 # patch_option = 2 # 153 patches + elif shmat.shape[2] == 306: + patch_option = 3 # patch_option = 3 # 306 patches + elif shmat.shape[2] == 612: + patch_option = 4 # patch_option = 4 # 612 patches + + # asvf to calculate sunlit and shaded patches + asvf = torch.arccos(torch.sqrt(svf)) + + # Empty array for steradians + steradians = torch.zeros((shmat.shape[2])).to(device) + else: + # anisotropic_sky = 0 + diffsh = None + shmat = None + vegshmat = None + vbshvegshmat = None + asvf = None + patch_option = 0 + steradians = 0 + shadow = torch.zeros_like(dsm).to(device) + + # % Ts parameterisation maps + if landcover == 1.0: + # Get land cover properties for Tg wave (land cover scheme based on Bogren et al. 2000, explained in Lindberg et al., 2008 and Lindberg, Onomura & Grimmond, 2016) + [ + TgK, + Tstart, + alb_grid, + emis_grid, + TgK_wall, + Tstart_wall, + TmaxLST, + TmaxLST_wall, + ] = Tgmaps_v1(lcgrid.clone(), param) + else: + TgK = Knight + param["Ts_deg"]["Value"]["Cobble_stone_2014a"] + Tstart = Knight - param["Tstart"]["Value"]["Cobble_stone_2014a"] + TmaxLST = param["TmaxLST"]["Value"]["Cobble_stone_2014a"] + alb_grid = ( + Knight + + param["Albedo"]["Effective"]["Value"]["Cobble_stone_2014a"] + ) + emis_grid = Knight + param["Emissivity"]["Value"]["Cobble_stone_2014a"] + TgK_wall = param["Ts_deg"]["Value"]["Walls"] + Tstart_wall = param["Tstart"]["Value"]["Walls"] + TmaxLST_wall = param["TmaxLST"]["Value"]["Walls"] + + # Parameterization for the 2026 ground scheme + groundSurface = int(configDict["groundmodel"]) + if groundSurface == 1: + # Initiate the maps if the surface temperature is available + if configDict["input_surf"] != "": + surfData = pd.read_csv(configDict["input_surf"]) + Tg = surfData["Tg"] + Tm = torch.mean(surfData["Tg"]) + ( + _, + _, + Rn, + Rn_past, + G, + cap_grid, + diff_grid, + a1_grid, + a2_grid, + a3_grid, + ) = initiate_groundScheme( + lcgrid.copy(), param, DOY[0], Ta, location, device + ) + else: + ( + Tg, + Tm, + Rn, + Rn_past, + G, + cap_grid, + diff_grid, + a1_grid, + a2_grid, + a3_grid, + ) = initiate_groundScheme( + lcgrid.clone(), param, DOY[0], Ta, location, device + ) + else: + pass + + # Replace the ground view factors with integration of solid angles + outgoingLW = int(configDict["outgoinglongwave"]) + + # Import data for wall temperature parameterization TODO: fix for standalone + wallScheme = int(configDict["wallscheme"]) + if wallScheme == 1: + wallData = torch.load( + configDict["input_wall"], map_location=device, weights_only=True + ) + voxelMaps = wallData["voxelId"] + voxelTable = wallData["voxelTable"] + # Get wall type from standalone + if standAlone == 1: + wall_type_standalone = { + "Brick_wall": "100", + "Concrete_wall": "101", + "Wood_wall": "102", + } + wall_type = wall_type_standalone[configDict["walltype"]] + else: + # Get wall type set in GUI + wall_type = configDict[ + "walltype" + ] # str(100 + int(self.parameterAsString(parameters, self.WALL_TYPE, context))) #TODO + + # Calculate wall height for wall scheme, i.e. include corners (thicker walls) + walls_scheme = wa.findwalls_sp( + dsm, 2, device, torch.tensor([[1, 1, 1], [1, 0, 1], [1, 1, 1]]).to(device) + ) + # Calculate wall aspect for wall scheme, i.e. include corners (thicker walls) + dirwalls_scheme = wa.filter1Goodwin_as_aspect_v3( + walls_scheme.copy(), scale, dsm, feedback, 100.0 / 180.0, device + ) + + # Used in wall temperature parameterization scheme + first_timestep = ( + pd.to_datetime(int(YYYY[0][0].item()), format="%Y") + + pd.to_timedelta(DOY[0].item() - 1, unit="d") + + pd.to_timedelta(hours[0].item(), unit="h") + + pd.to_timedelta(minu[0].item(), unit="m") + ) + second_timestep = ( + pd.to_datetime(int(YYYY[0][1].item()), format="%Y") + + pd.to_timedelta(DOY[1].item() - 1, unit="d") + + pd.to_timedelta(hours[1].item(), unit="h") + + pd.to_timedelta(minu[1].item(), unit="m") + ) + + timeStep = (second_timestep - first_timestep).seconds + + # Load voxelTable as Pandas DataFrame + voxelTable, dirwalls_scheme = load_walls( + voxelTable, + param, + wall_type, + dirwalls_scheme, + Ta[0], + timeStep, + albedo_b, + ewall, + alb_grid, + landcover, + lcgrid, + dsm, + ) + + # Use wall of interest + woi_file = configDict["woi_file"] + if woi_file: + # (dsm_minx, dsm_x_size, dsm_x_rotation, dsm_miny, dsm_y_rotation, dsm_y_size) = gdal_dsm.GetGeoTransform() #TODO: fix for standalone + if standAlone == 0: + woi_field = configDict[ + "woi_field" + ] # self.parameterAsStrings(parameters, self.WOI_FIELD, context) + woisxy, woiname = pointOfInterest( + configDict["woi_file"], woi_field, scale, gdal_dsm + ) + else: + pois_gdf = gpd.read_file(configDict["poi_file"]) + numfeat = pois_gdf.shape[0] + poisxy = torch.zeros((numfeat, 3)).to(device) - 999 + for idx, row in pois_gdf.iterrows(): + y, x = rowcol( + dsm_transf, + row["geometry"].centroid.x, + row["geometry"].centroid.y, + ) # TODO: This produce different result since no standalone round coordinates + poiname.append(row[configDict["poi_field"]]) + poisxy[idx, 0] = idx + poisxy[idx, 1] = x + poisxy[idx, 2] = y + + # Create pandas datetime object to be used when createing an xarray DataSet where wall temperatures/radiation is stored and eventually saved as a NetCDf + if configDict["wallnetcdf"] == 1: + met_for_xarray = ( + pd.to_datetime(YYYY[0][:], format="%Y") + + pd.to_timedelta(DOY - 1, unit="d") + + pd.to_timedelta(hours, unit="h") + + pd.to_timedelta(minu, unit="m") + ) + else: + wallScheme = 0 + voxelMaps = 0 + voxelTable = 0 + timeStep = 0 + # thermal_effusivity = 0 + walls_scheme = torch.ones((rows, cols)).to(device) * 10.0 + dirwalls_scheme = torch.ones((rows, cols)).to(device) * 10.0 + + # Initialisation of time related variables + if Ta.__len__() == 1: + timestepdec = 0 + else: + timestepdec = dectime[1] - dectime[0] + timeadd = 0.0 + # timeaddE = 0. + # timeaddS = 0. + # timeaddW = 0. + # timeaddN = 0. + firstdaytime = 1.0 + + # Save hemispheric image + if anisotropic_sky == 1: + if not poisxy is None: + patch_characteristics = hemispheric_image( + poisxy, shmat, vegshmat, vbshvegshmat, voxelMaps, wallScheme + ) + + # If metfile starts at night + CI = 1.0 + + # Main loop + tmrtplot = torch.zeros((rows, cols)).to(device) + + # Initiate array for I0 values + if torch.unique(DOY).shape[0] > 1: + unique_days = torch.unique(DOY) + first_unique_day = DOY[DOY == unique_days[0]] + I0_array = torch.zeros((first_unique_day.shape[0])).to(device) + else: + first_unique_day = DOY.clone() + I0_array = torch.zeros((DOY.shape[0])).to(device) + + if standAlone == 1: + progress = tqdm(total=Ta.__len__()) + else: + progress = None + + for i in torch.arange(0, Ta.__len__()): + if feedback is not None: + feedback.setProgress( + int(i * (100.0 / Ta.__len__())) + ) # move progressbar forward + if feedback.isCanceled(): + feedback.setProgressText("Calculation cancelled") + break + elif progress is not None: + progress.update(1) + + # Daily water body temperature + if landcover == 1: + if ((dectime[i] - torch.floor(dectime[i]))) == 0 or (i == 0): + Twater = torch.mean(Ta[jday[0] == torch.floor(dectime[i])]) + # Nocturnal cloudfraction from Offerle et al. 2003 + if (dectime[i] - torch.floor(dectime[i])) == 0: + daylines = torch.where(torch.floor(dectime) == dectime[i]) + if daylines.__len__() > 1: + alt = altitude[0][daylines] + alt2 = torch.where(alt > 1) + rise = alt2[0][0] + [_, CI, _, _, _] = clearnessindex_2013b( + zen[0, i + rise + 1], + jday[0, i + rise + 1], + Ta[i + rise + 1], + RH[i + rise + 1] / 100.0, + radG[i + rise + 1], + location, + P[i + rise + 1], + ) + if (CI > 1.0) or (CI == torch.inf): + CI = 1.0 + else: + CI = 1.0 + + + + # Timestep of the simulation used in the ground scheme calculation + first_timestep = ( + pd.to_datetime(int(YYYY[0][0].item()), format="%Y") + + pd.to_timedelta(DOY[0].item() - 1, unit="d") + + pd.to_timedelta(hours[0].item(), unit="h") + + pd.to_timedelta(minu[0].item(), unit="m") + ) + second_timestep = ( + pd.to_datetime(int(YYYY[0][1].item()), format="%Y") + + pd.to_timedelta(DOY[1].item() - 1, unit="d") + + pd.to_timedelta(hours[1].item(), unit="h") + + pd.to_timedelta(minu[1].item(), unit="m") + ) + + timeStep = (second_timestep - first_timestep).seconds + + + ( + Tmrt, + Kdown, + Kup, + Ldown, + Lup, + Tg, + ea, + esky, + I0, + CI, + shadow, + firstdaytime, + timestepdec, + timeadd, + Tgmap1, + Tgmap1E, + Tgmap1S, + Tgmap1W, + Tgmap1N, + Keast, + Ksouth, + Kwest, + Knorth, + Least, + Lsouth, + Lwest, + Lnorth, + KsideI, + radIout, + radDout, + Lside, + Lsky_patch_characteristics, + CI_TgG, + KsideD, + dRad, + Kside, + steradians, + voxelTable, + Rn, + Rn_past, + Tm, + G, + ) = so.Solweig_2026a_calc( + i, + dsm, + scale, + rows, + cols, + svf, + svfN, + svfW, + svfE, + svfS, + svfveg, + svfNveg, + svfEveg, + svfSveg, + svfWveg, + svfaveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + vegdsm, + vegdsm2, + albedo_b, + absK, + absL, + ewall, + Fside, + Fup, + Fcyl, + altitude[0][i], + azimuth[0][i], + zen[0][i], + jday[0][i], + usevegdem, + onlyglobal, + buildings, + location, + psi[0][i], + landcover, + lcgrid, + dectime[i], + altmax[0][i], + wallaspect, + wallheight, + cyl, + elvis, + Ta[i], + RH[i], + radG[i], + radD[i], + radI[i], + P[i], + amaxvalue, + bush, + Twater, + TgK, + Tstart, + alb_grid, + emis_grid, + TgK_wall, + Tstart_wall, + TmaxLST, + TmaxLST_wall, + first, + second, + svfalfa, + svfbuveg, + firstdaytime, + timeadd, + timestepdec, + Tgmap1, + Tgmap1E, + Tgmap1S, + Tgmap1W, + Tgmap1N, + CI, + diffsh, + shmat, + vegshmat, + vbshvegshmat, + anisotropic_sky, + asvf, + patch_option, + voxelMaps, + voxelTable, + Ws[i], + wallScheme, + timeStep, + steradians, + walls_scheme, + dirwalls_scheme, + groundSurface, + outgoingLW, + Tg, + Rn, + Rn_past, + G, + Tm, + cap_grid, + diff_grid, + a1_grid, + a2_grid, + a3_grid, + shadow, + ) + + # Save I0 for I0 vs. Kdown output plot to check if UTC is off + if i < first_unique_day.shape[0]: + I0_array[i] = I0 + elif i == first_unique_day.shape[0]: + # Output I0 vs. Kglobal plot + radG_for_plot = radG[DOY == first_unique_day[0]] + # hours_for_plot = hours[DOY == first_unique_day[0]] + dectime_for_plot = dectime[DOY == first_unique_day[0]] + fig, ax = plt.subplots() + ax.plot(dectime_for_plot, I0_array, label="I0") + ax.plot(dectime_for_plot, radG_for_plot, label="Kglobal") + ax.set_ylabel("Shortwave radiation [$Wm^{-2}$]") + ax.set_xlabel("Decimal time") + ax.set_title("UTC" + str(configDict["utc"])) + ax.legend() + fig.savefig(configDict["output_dir"] + "/metCheck.png", dpi=150) + + tmrtplot = tmrtplot + Tmrt + + if altitude[0][i] > 0: + w = "D" + else: + w = "N" + + # Write to POIs + if not poisxy is None: + for k in range(0, poisxy.shape[0]): + poi_save = torch.zeros((1, 40)).to(device) + poi_save[0, 0] = YYYY[0][i] + poi_save[0, 1] = jday[0][i] + poi_save[0, 2] = hours[i] + poi_save[0, 3] = minu[i] + poi_save[0, 4] = dectime[i] + poi_save[0, 5] = altitude[0][i] + poi_save[0, 6] = azimuth[0][i] + poi_save[0, 7] = radIout + poi_save[0, 8] = radDout + poi_save[0, 9] = radG[i] + poi_save[0, 10] = Kdown[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 11] = Kup[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 12] = Keast[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 13] = Ksouth[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 14] = Kwest[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 15] = Knorth[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 16] = Ldown[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 17] = Lup[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 18] = Least[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 19] = Lsouth[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 20] = Lwest[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 21] = Lnorth[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 22] = Ta[i] + poi_save[0, 23] = Tg[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 24] = RH[i] + poi_save[0, 25] = esky + poi_save[0, 26] = Tmrt[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 27] = I0 + poi_save[0, 28] = CI + poi_save[0, 29] = shadow[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 30] = svf[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 31] = svfbuveg[ + int(poisxy[k, 2]), int(poisxy[k, 1]) + ] + poi_save[0, 32] = KsideI[int(poisxy[k, 2]), int(poisxy[k, 1])] + # Recalculating wind speed based on powerlaw + WsPET = (1.1 / sensorheight) ** 0.2 * Ws[i] + WsUTCI = (10.0 / sensorheight) ** 0.2 * Ws[i] + resultPET = p._PET( + Ta[i], + RH[i], + Tmrt[int(poisxy[k, 2]), int(poisxy[k, 1])], + WsPET, + mbody, + age, + ht, + activity, + clo, + sex, + ) + poi_save[0, 33] = resultPET + resultUTCI = utci.utci_calculator( + Ta[i], + RH[i], + Tmrt[int(poisxy[k, 2]), int(poisxy[k, 1])], + WsUTCI, + ) + poi_save[0, 34] = resultUTCI + poi_save[0, 35] = CI_TgG + poi_save[0, 36] = KsideD[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 37] = Lside[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 38] = dRad[int(poisxy[k, 2]), int(poisxy[k, 1])] + poi_save[0, 39] = Kside[int(poisxy[k, 2]), int(poisxy[k, 1])] + data_out = ( + configDict["output_dir"] + + "/POI_" + + str(poiname[k]) + + ".txt" + ) + # f_handle = file(data_out, 'a') + f_handle = open(data_out, "ab") + np.savetxt( + f_handle, + ( + poi_save.cpu().numpy() + if isinstance(poi_save, torch.Tensor) + else poi_save + ), + fmt=numformat, + ) + f_handle.close() + + # If wall temperature parameterization scheme is in use + if ( + configDict["wallscheme"] == 1 + ): # folderWallScheme: TODO: Fix for standalone + # Store wall data for output + if not woisxy is None: + for k in range(0, woisxy.shape[0]): + temp_wall = voxelTable.loc[ + ( + (voxelTable["ypos"] == woisxy[k, 2]) + & (voxelTable["xpos"] == woisxy[k, 1]) + ), + "wallTemperature", + ].to_numpy() + K_in = voxelTable.loc[ + ( + (voxelTable["ypos"] == woisxy[k, 2]) + & (voxelTable["xpos"] == woisxy[k, 1]) + ), + "K_in", + ].to_numpy() + L_in = voxelTable.loc[ + ( + (voxelTable["ypos"] == woisxy[k, 2]) + & (voxelTable["xpos"] == woisxy[k, 1]) + ), + "L_in", + ].to_numpy() + wallShade = voxelTable.loc[ + ( + (voxelTable["ypos"] == woisxy[k, 2]) + & (voxelTable["xpos"] == woisxy[k, 1]) + ), + "wallShade", + ].to_numpy() + temp_all = torch.concatenate( + [temp_wall, K_in, L_in, wallShade] + ).to(device) + # temp_all = torch.concatenate([temp_wall]) + # wall_data = torch.zeros((1, 7 + temp_wall.shape[0])) + wall_data = torch.zeros((1, 7 + temp_all.shape[0])).to( + device + ) + # Part of file name (wallid), i.e. WOI_wallid.txt + data_out = ( + configDict["output_dir"] + + "/WOI_" + + str(woiname[k]) + + ".txt" + ) + if i == 0: + # Output file header + # header = 'yyyy id it imin dectime Ta SVF Ts' + header = ( + "yyyy id it imin dectime Ta SVF" + + " Ts" * temp_wall.shape[0] + + " Kin" * K_in.shape[0] + + " Lin" * L_in.shape[0] + + " shade" * wallShade.shape[0] + ) + # Part of file name (wallid), i.e. WOI_wallid.txt + # woiname = voxelTable.loc[((voxelTable['ypos'] == woisxy[k, 2]) & (voxelTable['xpos'] == woisxy[k, 1])), 'wallId'].to_numpy()[0] + woi_save = [] # + np.savetxt( + data_out, + woi_save, + delimiter=" ", + header=header, + comments="", + ) + # Fill wall_data with variables + wall_data[0, 0] = YYYY[0][i] + wall_data[0, 1] = jday[0][i] + wall_data[0, 2] = hours[i] + wall_data[0, 3] = minu[i] + wall_data[0, 4] = dectime[i] + wall_data[0, 5] = Ta[i] + wall_data[0, 6] = svf[int(woisxy[k, 2]), int(woisxy[k, 1])] + wall_data[0, 7:] = temp_all + + # Num format for output file data + woi_numformat = ( + "%d %d %d %d %.5f %.2f %.2f" + + " %.2f" * temp_all.shape[0] + ) + # Open file, add data, save + f_handle = open(data_out, "ab") + np.savetxt( + f_handle, + ( + wall_data.cpu().numpy() + if isinstance(wall_data, torch.Tensor) + else wall_data + ), + fmt=woi_numformat, + ) + f_handle.close() + + # Save wall temperature/radiation as NetCDF TODO: fix for standAlone? + if configDict["wallnetcdf"] == "1": # wallNetCDF: + netcdf_output = configDict["outputDir"] + "/walls.nc" + walls_as_netcdf( + voxelTable, + rows, + cols, + met_for_xarray, + i, + dsm, + configDict["filepath_dsm"], + netcdf_output, + ) + + if hours[i] < 10: + XH = "0" + else: + XH = "" + if minu[i] < 10: + XM = "0" + else: + XM = "" + + time_code = ( + str(int(YYYY[0, i])) + + "_" + + str(int(DOY[i])) + + "_" + + XH + + str(int(hours[i])) + + XM + + str(int(minu[i])) + + w + ) + + if configDict["outputtmrt"] == "1": + if standAlone == 0: + saveraster( + gdal_dsm, + configDict["output_dir"] + "/Tmrt_" + time_code + ".tif", + Tmrt.detach().cpu().numpy(), + ) + else: + common.save_raster( + configDict["output_dir"] + "/Tmrt_" + time_code + ".tif", + Tmrt.detach().cpu().numpy(), + dsm_transf, + dsm_crs, + ) + if configDict["outputkup"] == "1": + if standAlone == 0: + saveraster( + gdal_dsm, + configDict["output_dir"] + "/Kup_" + time_code + ".tif", + Kup.detach().cpu().numpy(), + ) + else: + common.save_raster( + configDict["output_dir"] + "/Kup_" + time_code + ".tif", + Kup.detach().cpu().numpy(), + dsm_transf, + dsm_crs, + ) + if configDict["outputkdown"] == "1": + if standAlone == 0: + saveraster( + gdal_dsm, + configDict["output_dir"] + "/Kdown_" + time_code + ".tif", + Kdown.detach().cpu().numpy(), + ) + else: + common.save_raster( + configDict["output_dir"] + "/Kdown_" + time_code + ".tif", + Kdown.detach().cpu().numpy(), + dsm_transf, + dsm_crs, + ) + if configDict["outputlup"] == "1": + if standAlone == 0: + saveraster( + gdal_dsm, + configDict["output_dir"] + "/Lup_" + time_code + ".tif", + Lup.detach().cpu().numpy(), + ) + else: + common.save_raster( + configDict["output_dir"] + "/Lup_" + time_code + ".tif", + Lup.detach().cpu().numpy(), + dsm_transf, + dsm_crs, + ) + if configDict["outputldown"] == "1": + if standAlone == 0: + saveraster( + gdal_dsm, + configDict["output_dir"] + "/Ldown_" + time_code + ".tif", + Ldown.detach().cpu().numpy(), + ) + else: + common.save_raster( + configDict["output_dir"] + "/Ldown_" + time_code + ".tif", + Ldown.detach().cpu().numpy(), + dsm_transf, + dsm_crs, + ) + if configDict["outputsh"] == "1": + if standAlone == 0: + saveraster( + gdal_dsm, + configDict["output_dir"] + "/Shadow_" + time_code + ".tif", + shadow.detach().cpu().numpy(), + ) + else: + common.save_raster( + configDict["output_dir"] + "/Shadow_" + time_code + ".tif", + shadow.detach().cpu().numpy(), + dsm_transf, + dsm_crs, + ) + + if configDict["outputkdiff"] == "1": + if standAlone == 0: + saveraster( + gdal_dsm, + configDict["output_dir"] + "/Kdiff_" + time_code + ".tif", + dRad.detach().cpu().numpy(), + ) + else: + common.save_raster( + configDict["output_dir"] + "/Kdiff_" + time_code + ".tif", + dRad.detach().cpu().numpy(), + dsm_transf, + dsm_crs, + ) + + # Sky view image of patches + if (anisotropic_sky == 1) & (i == 0) & (not poisxy is None): + for k in range(poisxy.shape[0]): + Lsky_patch_characteristics[:, 2] = patch_characteristics[:, k] + skyviewimage_out = ( + configDict["output_dir"] + + "/POI_" + + str(poiname[k]) + + ".png" + ) + PolarBarPlot( + Lsky_patch_characteristics, + altitude[0][i], + azimuth[0][i], + "Hemisphere partitioning", + skyviewimage_out, + 0, + 5, + 0, + ) + + # Save files for Tree Planter + if configDict["outputtreeplanter"] == "1": # outputTreeplanter: + if feedback is not None: + feedback.setProgressText("Saving files for Tree Planter tool") + # Save DSM + copyfile( + configDict["filepath_dsm"], configDict["output_dir"] + "/DSM.tif" + ) + + # Save CDSM + if usevegdem == 1: + copyfile( + configDict["filepath_cdsm"], + configDict["output_dir"] + "/CDSM.tif", + ) + + albedo_g = param["Albedo"]["Effective"]["Value"]["Cobble_stone_2014a"] + eground = param["Emissivity"]["Value"]["Cobble_stone_2014a"] + + # Saving settings from SOLWEIG for SOLWEIG1D in TreePlanter + settingsHeader = "UTC, posture, onlyglobal, landcover, anisotropic, cylinder, albedo_walls, albedo_ground, emissivity_walls, emissivity_ground, absK, absL, elevation, patch_option" + settingsFmt = ( + "%i", + "%i", + "%i", + "%i", + "%i", + "%i", + "%1.2f", + "%1.2f", + "%1.2f", + "%1.2f", + "%1.2f", + "%1.2f", + "%1.2f", + "%i", + ) + settingsData = torch.tensor( + [ + [ + int(configDict["utc"]), + pos, + onlyglobal, + landcover, + anisotropic_sky, + cyl, + albedo_b, + albedo_g, + ewall, + eground, + absK, + absL, + alt, + patch_option, + ] + ] + ).to(device) + # print(settingsData) + np.savetxt( + configDict["output_dir"] + "/treeplantersettings.txt", + ( + settingsData.cpu().numpy() + if isinstance(settingsData, torch.Tensor) + else settingsData + ), + fmt=settingsFmt, + header=settingsHeader, + delimiter=" ", + ) + + # Copying met file for SpatialTC + copyfile( + configDict["input_met"], configDict["output_dir"] + "/metforcing.txt" + ) + + tmrtplot = ( + tmrtplot / Ta.__len__() + ) # fix average Tmrt instead of sum, 20191022 + if standAlone == 0: + saveraster( + gdal_dsm, + configDict["output_dir"] + "/Tmrt_average.tif", + tmrtplot.detach().cpu().numpy(), + ) + else: + common.save_raster( + configDict["output_dir"] + "/Tmrt_average.tif", + tmrtplot, + dsm_transf, + dsm_crs, + ) diff --git a/functions/SOLWEIGpython/Tgmaps_v1.py b/functions/SOLWEIGpython/Tgmaps_v1.py index d7e29ac..b3a856e 100644 --- a/functions/SOLWEIGpython/Tgmaps_v1.py +++ b/functions/SOLWEIGpython/Tgmaps_v1.py @@ -1,5 +1,4 @@ import numpy as np -import torch def Tgmaps_v1(lc_grid, solweig_parameters): @@ -7,30 +6,30 @@ def Tgmaps_v1(lc_grid, solweig_parameters): # Tgmaps_v1 Populates grids with cooeficients for Tg wave # Detailed explanation goes here lc_grid[lc_grid >= 100] = 2 - id = torch.unique(lc_grid) - id = lc_grid[lc_grid <= 7].to(int) - TgK = torch.clone(lc_grid) - Tstart = torch.clone(lc_grid) - alb_grid = torch.clone(lc_grid) - emis_grid = torch.clone(lc_grid) - TmaxLST = torch.clone(lc_grid) + id = np.unique(lc_grid) + id = lc_grid[lc_grid <= 7].astype(int) + TgK = np.copy(lc_grid) + Tstart = np.copy(lc_grid) + alb_grid = np.copy(lc_grid) + emis_grid = np.copy(lc_grid) + TmaxLST = np.copy(lc_grid) for i in id: # row = (lc_class[:, 0] == id[i]) Tstart[Tstart == i] = solweig_parameters["Tstart"]["Value"][ - solweig_parameters["Names"]["Value"][str((int(i.item())))] + solweig_parameters["Names"]["Value"][str(i)] ] alb_grid[alb_grid == i] = solweig_parameters["Albedo"]["Effective"][ "Value" - ][solweig_parameters["Names"]["Value"][str((int(i.item())))]] + ][solweig_parameters["Names"]["Value"][str(i)]] emis_grid[emis_grid == i] = solweig_parameters["Emissivity"]["Value"][ - solweig_parameters["Names"]["Value"][str((int(i.item())))] + solweig_parameters["Names"]["Value"][str(i)] ] TmaxLST[TmaxLST == i] = solweig_parameters["TmaxLST"]["Value"][ - solweig_parameters["Names"]["Value"][str((int(i.item())))] + solweig_parameters["Names"]["Value"][str(i)] ] TgK[TgK == i] = solweig_parameters["Ts_deg"]["Value"][ - solweig_parameters["Names"]["Value"][str((int(i.item())))] + solweig_parameters["Names"]["Value"][str(i)] ] TgK_wall = solweig_parameters["Ts_deg"]["Value"]["Walls"] diff --git a/functions/SOLWEIGpython/Tgmaps_v1_torch.py b/functions/SOLWEIGpython/Tgmaps_v1_torch.py new file mode 100644 index 0000000..ad15225 --- /dev/null +++ b/functions/SOLWEIGpython/Tgmaps_v1_torch.py @@ -0,0 +1,50 @@ +try: + import torch +except: + pass + +def Tgmaps_v1(lc_grid, solweig_parameters): + + # Tgmaps_v1 Populates grids with cooeficients for Tg wave + # Detailed explanation goes here + lc_grid[lc_grid >= 100] = 2 + id = torch.unique(lc_grid) + id = lc_grid[lc_grid <= 7].to(int) + TgK = torch.clone(lc_grid) + Tstart = torch.clone(lc_grid) + alb_grid = torch.clone(lc_grid) + emis_grid = torch.clone(lc_grid) + TmaxLST = torch.clone(lc_grid) + + for i in id: + # row = (lc_class[:, 0] == id[i]) + Tstart[Tstart == i] = solweig_parameters["Tstart"]["Value"][ + solweig_parameters["Names"]["Value"][str((int(i.item())))] + ] + alb_grid[alb_grid == i] = solweig_parameters["Albedo"]["Effective"][ + "Value" + ][solweig_parameters["Names"]["Value"][str((int(i.item())))]] + emis_grid[emis_grid == i] = solweig_parameters["Emissivity"]["Value"][ + solweig_parameters["Names"]["Value"][str((int(i.item())))] + ] + TmaxLST[TmaxLST == i] = solweig_parameters["TmaxLST"]["Value"][ + solweig_parameters["Names"]["Value"][str((int(i.item())))] + ] + TgK[TgK == i] = solweig_parameters["Ts_deg"]["Value"][ + solweig_parameters["Names"]["Value"][str((int(i.item())))] + ] + + TgK_wall = solweig_parameters["Ts_deg"]["Value"]["Walls"] + Tstart_wall = solweig_parameters["Tstart"]["Value"]["Walls"] + TmaxLST_wall = solweig_parameters["TmaxLST"]["Value"]["Walls"] + + return ( + TgK, + Tstart, + alb_grid, + emis_grid, + TgK_wall, + Tstart_wall, + TmaxLST, + TmaxLST_wall, + ) diff --git a/functions/SOLWEIGpython/anisotropic_sky.py b/functions/SOLWEIGpython/anisotropic_sky.py index bd8971b..efc5a39 100644 --- a/functions/SOLWEIGpython/anisotropic_sky.py +++ b/functions/SOLWEIGpython/anisotropic_sky.py @@ -6,7 +6,6 @@ from . import patch_radiation from . import sunlit_shaded_patches from . import patch_radiation -import pandas as pd def anisotropic_sky( diff --git a/functions/SOLWEIGpython/cylindric_wedge.py b/functions/SOLWEIGpython/cylindric_wedge.py index c876d88..7564af8 100644 --- a/functions/SOLWEIGpython/cylindric_wedge.py +++ b/functions/SOLWEIGpython/cylindric_wedge.py @@ -1,62 +1,59 @@ -import torch +import numpy as np def cylindric_wedge(zen, svfalfa, rows, cols): + np.seterr(divide="ignore", invalid="ignore") + # Fraction of sunlit walls based on sun altitude and svf wieghted building angles # input: # sun zenith angle "beta" # svf related angle "alfa" - device = ( - svfalfa.device - if isinstance(svfalfa, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "cpu") - ) beta = zen # alfa=svfalfa - alfa = torch.zeros((rows, cols), device=device) + svfalfa + alfa = np.zeros((rows, cols)) + svfalfa # measure the size of the image # sizex=size(svfalfa,2) # sizey=size(svfalfa,1) - xa = 1 - 2.0 / (torch.tan(alfa) * torch.tan(beta)) - ha = 2.0 / (torch.tan(alfa) * torch.tan(beta)) - ba = 1.0 / torch.tan(alfa) + xa = 1 - 2.0 / (np.tan(alfa) * np.tan(beta)) + ha = 2.0 / (np.tan(alfa) * np.tan(beta)) + ba = 1.0 / np.tan(alfa) hkil = 2.0 * ba * ha - qa = torch.zeros((rows, cols), device=device) + qa = np.zeros((rows, cols)) # qa(length(svfalfa),length(svfalfa))=0; - qa[xa < 0] = torch.tan(beta) / 2 + qa[xa < 0] = np.tan(beta) / 2 - Za = torch.zeros((rows, cols), device=device) + Za = np.zeros((rows, cols)) # Za(length(svfalfa),length(svfalfa))=0; - Za[xa < 0] = (((ba[xa < 0] ** 2) - ((qa[xa < 0] ** 2) / 4)) ** 0.5).float() + Za[xa < 0] = ((ba[xa < 0] ** 2) - ((qa[xa < 0] ** 2) / 4)) ** 0.5 - phi = torch.zeros((rows, cols), device=device) + phi = np.zeros((rows, cols)) # phi(length(svfalfa),length(svfalfa))=0; - phi[xa < 0] = torch.arctan(Za[xa < 0] / qa[xa < 0]) + phi[xa < 0] = np.arctan(Za[xa < 0] / qa[xa < 0]) - A = torch.zeros((rows, cols), device=device) + A = np.zeros((rows, cols)) # A(length(svfalfa),length(svfalfa))=0; - A[xa < 0] = ( - torch.sin(phi[xa < 0]) - phi[xa < 0] * torch.cos(phi[xa < 0]) - ) / (1 - torch.cos(phi[xa < 0])) + A[xa < 0] = (np.sin(phi[xa < 0]) - phi[xa < 0] * np.cos(phi[xa < 0])) / ( + 1 - np.cos(phi[xa < 0]) + ) - ukil = torch.zeros((rows, cols), device=device) + ukil = np.zeros((rows, cols)) # ukil(length(svfalfa),length(svfalfa))=0 - ukil[xa < 0] = (2 * ba[xa < 0] * xa[xa < 0] * A[xa < 0]).float() + ukil[xa < 0] = 2 * ba[xa < 0] * xa[xa < 0] * A[xa < 0] Ssurf = hkil + ukil - F_sh = (2 * torch.pi * ba - Ssurf) / (2 * torch.pi * ba) # Xa + F_sh = (2 * np.pi * ba - Ssurf) / (2 * np.pi * ba) # Xa return F_sh def cylindric_wedge_voxel(zen, svfalfa): - torch.seterr(divide="ignore", invalid="ignore") + np.seterr(divide="ignore", invalid="ignore") # Fraction of sunlit walls based on sun altitude and svf wieghted building angles # input: @@ -70,41 +67,35 @@ def cylindric_wedge_voxel(zen, svfalfa): # sizex=size(svfalfa,2) # sizey=size(svfalfa,1) - xa = 1 - 2.0 / (torch.tan(svfalfa) * torch.tan(beta)) - ha = 2.0 / (torch.tan(svfalfa) * torch.tan(beta)) - ba = 1.0 / torch.tan(svfalfa) + xa = 1 - 2.0 / (np.tan(svfalfa) * np.tan(beta)) + ha = 2.0 / (np.tan(svfalfa) * np.tan(beta)) + ba = 1.0 / np.tan(svfalfa) hkil = 2.0 * ba * ha - device = ( - svfalfa.device - if isinstance(svfalfa, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "cpu") - ) - - qa = torch.zeros((svfalfa.shape[0]), device=device) + qa = np.zeros((svfalfa.shape[0])) # qa(length(svfalfa),length(svfalfa))=0; - qa[xa < 0] = torch.tan(beta) / 2 + qa[xa < 0] = np.tan(beta) / 2 - Za = torch.zeros((svfalfa.shape[0]), device=device) + Za = np.zeros((svfalfa.shape[0])) # Za(length(svfalfa),length(svfalfa))=0; Za[xa < 0] = ((ba[xa < 0] ** 2) - ((qa[xa < 0] ** 2) / 4)) ** 0.5 - phi = torch.zeros((svfalfa.shape[0]), device=device) + phi = np.zeros((svfalfa.shape[0])) # phi(length(svfalfa),length(svfalfa))=0; - phi[xa < 0] = torch.arctan(Za[xa < 0] / qa[xa < 0]) + phi[xa < 0] = np.arctan(Za[xa < 0] / qa[xa < 0]) - A = torch.zeros((svfalfa.shape[0]), device=device) + A = np.zeros((svfalfa.shape[0])) # A(length(svfalfa),length(svfalfa))=0; - A[xa < 0] = ( - torch.sin(phi[xa < 0]) - phi[xa < 0] * torch.cos(phi[xa < 0]) - ) / (1 - torch.cos(phi[xa < 0])) + A[xa < 0] = (np.sin(phi[xa < 0]) - phi[xa < 0] * np.cos(phi[xa < 0])) / ( + 1 - np.cos(phi[xa < 0]) + ) - ukil = torch.zeros((svfalfa.shape[0]), device=device) + ukil = np.zeros((svfalfa.shape[0])) # ukil(length(svfalfa),length(svfalfa))=0 ukil[xa < 0] = 2 * ba[xa < 0] * xa[xa < 0] * A[xa < 0] Ssurf = hkil + ukil - F_sh = (2 * torch.pi * ba - Ssurf) / (2 * torch.pi * ba) # Xa + F_sh = (2 * np.pi * ba - Ssurf) / (2 * np.pi * ba) # Xa return F_sh diff --git a/functions/SOLWEIGpython/cylindric_wedge_torch.py b/functions/SOLWEIGpython/cylindric_wedge_torch.py new file mode 100644 index 0000000..ffd4355 --- /dev/null +++ b/functions/SOLWEIGpython/cylindric_wedge_torch.py @@ -0,0 +1,112 @@ +try: + import torch +except: + pass + + +def cylindric_wedge(zen, svfalfa, rows, cols): + + # Fraction of sunlit walls based on sun altitude and svf wieghted building angles + # input: + # sun zenith angle "beta" + # svf related angle "alfa" + try: + import torch + except: + Exception("Error, pytorch must be imported.") + + device = ( + svfalfa.device + if isinstance(svfalfa, torch.Tensor) + else torch.device("cuda" if torch.cuda.is_available() else "cpu") + ) + beta = zen + # alfa=svfalfa + alfa = torch.zeros((rows, cols), device=device) + svfalfa + # measure the size of the image + # sizex=size(svfalfa,2) + # sizey=size(svfalfa,1) + + xa = 1 - 2.0 / (torch.tan(alfa) * torch.tan(beta)) + ha = 2.0 / (torch.tan(alfa) * torch.tan(beta)) + ba = 1.0 / torch.tan(alfa) + hkil = 2.0 * ba * ha + + qa = torch.zeros((rows, cols), device=device) + # qa(length(svfalfa),length(svfalfa))=0; + qa[xa < 0] = torch.tan(beta) / 2 + + Za = torch.zeros((rows, cols), device=device) + # Za(length(svfalfa),length(svfalfa))=0; + Za[xa < 0] = (((ba[xa < 0] ** 2) - ((qa[xa < 0] ** 2) / 4)) ** 0.5).float() + + phi = torch.zeros((rows, cols), device=device) + # phi(length(svfalfa),length(svfalfa))=0; + phi[xa < 0] = torch.arctan(Za[xa < 0] / qa[xa < 0]) + + A = torch.zeros((rows, cols), device=device) + # A(length(svfalfa),length(svfalfa))=0; + A[xa < 0] = ( + torch.sin(phi[xa < 0]) - phi[xa < 0] * torch.cos(phi[xa < 0]) + ) / (1 - torch.cos(phi[xa < 0])) + + ukil = torch.zeros((rows, cols), device=device) + # ukil(length(svfalfa),length(svfalfa))=0 + ukil[xa < 0] = (2 * ba[xa < 0] * xa[xa < 0] * A[xa < 0]).float() + + Ssurf = hkil + ukil + + F_sh = (2 * torch.pi * ba - Ssurf) / (2 * torch.pi * ba) # Xa + + return F_sh + + +def cylindric_wedge_voxel(zen, svfalfa): + + torch.seterr(divide="ignore", invalid="ignore") + + # Fraction of sunlit walls based on sun altitude and svf wieghted building angles + # input: + # sun zenith angle "beta" + # svf related angle "alfa" + + beta = zen + + xa = 1 - 2.0 / (torch.tan(svfalfa) * torch.tan(beta)) + ha = 2.0 / (torch.tan(svfalfa) * torch.tan(beta)) + ba = 1.0 / torch.tan(svfalfa) + hkil = 2.0 * ba * ha + + device = ( + svfalfa.device + if isinstance(svfalfa, torch.Tensor) + else torch.device("cuda" if torch.cuda.is_available() else "cpu") + ) + + qa = torch.zeros((svfalfa.shape[0]), device=device) + # qa(length(svfalfa),length(svfalfa))=0; + qa[xa < 0] = torch.tan(beta) / 2 + + Za = torch.zeros((svfalfa.shape[0]), device=device) + # Za(length(svfalfa),length(svfalfa))=0; + Za[xa < 0] = ((ba[xa < 0] ** 2) - ((qa[xa < 0] ** 2) / 4)) ** 0.5 + + phi = torch.zeros((svfalfa.shape[0]), device=device) + # phi(length(svfalfa),length(svfalfa))=0; + phi[xa < 0] = torch.arctan(Za[xa < 0] / qa[xa < 0]) + + A = torch.zeros((svfalfa.shape[0]), device=device) + # A(length(svfalfa),length(svfalfa))=0; + A[xa < 0] = ( + torch.sin(phi[xa < 0]) - phi[xa < 0] * torch.cos(phi[xa < 0]) + ) / (1 - torch.cos(phi[xa < 0])) + + ukil = torch.zeros((svfalfa.shape[0]), device=device) + # ukil(length(svfalfa),length(svfalfa))=0 + ukil[xa < 0] = 2 * ba[xa < 0] * xa[xa < 0] * A[xa < 0] + + Ssurf = hkil + ukil + + F_sh = (2 * torch.pi * ba - Ssurf) / (2 * torch.pi * ba) # Xa + + return F_sh diff --git a/functions/SOLWEIGpython/daylen.py b/functions/SOLWEIGpython/daylen.py index 6d23938..442185e 100644 --- a/functions/SOLWEIGpython/daylen.py +++ b/functions/SOLWEIGpython/daylen.py @@ -1,4 +1,4 @@ -import torch +import numpy as np def daylen(DOY, XLAT): @@ -7,23 +7,15 @@ def daylen(DOY, XLAT): # Sun angles. SOC limited for latitudes above polar circles. # Calculate daylength, sunrise and sunset (Eqn. 17) - device = ( - DOY.device - if isinstance(DOY, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "cpu") - ) - if not isinstance(XLAT, torch.Tensor): - XLAT = torch.tensor(XLAT, device=device) + RAD = np.pi / 180.0 - RAD = torch.tensor(torch.pi / 180.0, device=device) + DEC = -23.45 * np.cos(2.0 * np.pi * (DOY + 10.0) / 365.0) - DEC = -23.45 * torch.cos(2.0 * torch.pi * (DOY + 10.0) / 365.0) - - SOC = torch.tan(RAD * DEC) * torch.tan(RAD * XLAT) - SOC = torch.clamp(SOC, -1.0, 1.0) + SOC = np.tan(RAD * DEC) * np.tan(RAD * XLAT) + SOC = min(max(SOC, -1.0), 1.0) # SOC=alt - DAYL = 12.0 + 24.0 * torch.arcsin(SOC) / torch.pi + DAYL = 12.0 + 24.0 * np.arcsin(SOC) / np.pi SNUP = 12.0 - DAYL / 2.0 SNDN = 12.0 + DAYL / 2.0 diff --git a/functions/SOLWEIGpython/daylen_torch.py b/functions/SOLWEIGpython/daylen_torch.py new file mode 100644 index 0000000..5c746d4 --- /dev/null +++ b/functions/SOLWEIGpython/daylen_torch.py @@ -0,0 +1,32 @@ +try: + import torch +except: + pass + +def daylen(DOY, XLAT): + # Calculation of declination of sun (Eqn. 16). Amplitude= +/-23.45 + # deg. Minimum = DOY 355 (DEC 21), maximum = DOY 172.5 (JUN 21/22). + # Sun angles. SOC limited for latitudes above polar circles. + # Calculate daylength, sunrise and sunset (Eqn. 17) + + device = ( + DOY.device + if isinstance(DOY, torch.Tensor) + else torch.device("cuda" if torch.cuda.is_available() else "cpu") + ) + if not isinstance(XLAT, torch.Tensor): + XLAT = torch.tensor(XLAT, device=device) + + RAD = torch.tensor(torch.pi / 180.0, device=device) + + DEC = -23.45 * torch.cos(2.0 * torch.pi * (DOY + 10.0) / 365.0) + + SOC = torch.tan(RAD * DEC) * torch.tan(RAD * XLAT) + SOC = torch.clamp(SOC, -1.0, 1.0) + # SOC=alt + + DAYL = 12.0 + 24.0 * torch.arcsin(SOC) / torch.pi + SNUP = 12.0 - DAYL / 2.0 + SNDN = 12.0 + DAYL / 2.0 + + return DAYL, DEC, SNDN, SNUP diff --git a/functions/SOLWEIGpython/ground_surface.py b/functions/SOLWEIGpython/ground_surface.py index 120fae1..b8225e7 100644 --- a/functions/SOLWEIGpython/ground_surface.py +++ b/functions/SOLWEIGpython/ground_surface.py @@ -4,8 +4,6 @@ import numpy as np import math -import matplotlib.pyplot as plt -import torch # Stefan-Boltzmann s constant SBC = 5.67e-8 @@ -27,7 +25,7 @@ def saturated_vp(T): R = 8.314 # Gas constant # August-Roche-Magnus approx. in Pa - qs = 6109.4 * torch.exp(17.625 * T / (T + 243.04)) + qs = 6109.4 * np.exp(17.625 * T / (T + 243.04)) # Clausius-Clapeyron equation slope = L * qs / R / (T + 273.15) ** 2 @@ -35,9 +33,7 @@ def saturated_vp(T): return slope, qs -def initiate_groundScheme( - lc_grid, solweig_parameters, day, Ta, location, device -): +def initiate_groundScheme(lc_grid, solweig_parameters, day, Ta, location): """ Setup the maps used in the ground scheme calculations depending on the landcover @@ -53,65 +49,61 @@ def initiate_groundScheme( # Get the landcover data from lc_grid array lc_grid[lc_grid >= 100] = 2 - id = torch.unique(lc_grid) - id = id.int() + id = np.unique(lc_grid) + id = id.astype(int) # Physical parameters grids - cap_grid = torch.clone(lc_grid) # Heat capacity - diff_grid = torch.clone(lc_grid) # Thermal diffusivity - a1_grid = torch.clone(lc_grid) - a2_grid = torch.clone(lc_grid) - a3_grid = torch.clone(lc_grid) # The 3 OHM coefficients + cap_grid = np.copy(lc_grid) # Heat capacity + diff_grid = np.copy(lc_grid) # Thermal diffusivity + a1_grid = np.copy(lc_grid) + a2_grid = np.copy(lc_grid) + a3_grid = np.copy(lc_grid) # The 3 OHM coefficients # Initial fluxes and temperature grids - Rn = torch.zeros_like(lc_grid) # Net radiation - Rn_past = torch.zeros_like(lc_grid) # Stored net radiation - G = torch.zeros_like(lc_grid) # Ground heat flux - Tg = torch.clone(lc_grid) # Surface temperature - Tm = torch.clone(lc_grid) # Mean daily surface temperature + Rn = np.zeros_like(lc_grid) # Net radiation + Rn_past = np.zeros_like(lc_grid) # Stored net radiation + G = np.zeros_like(lc_grid) # Ground heat flux + Tg = np.copy(lc_grid) # Surface temperature + Tm = np.copy(lc_grid) # Mean daily surface temperature for i in id: cap_grid[cap_grid == i] = solweig_parameters["Heat capacity"]["Value"][ - solweig_parameters["Names"]["Value"][str((int(i.item())))] + solweig_parameters["Names"]["Value"][str(i)] ] diff_grid[diff_grid == i] = solweig_parameters["Thermal_diffusivity"][ "Value" - ][solweig_parameters["Names"]["Value"][str((int(i.item())))]] + ][solweig_parameters["Names"]["Value"][str(i)]] # Coefficients of the OHM per land cover mean_a1 = solweig_parameters["OHM_coefficients"]["Values"][ - solweig_parameters["Names"]["Value"][str((int(i.item())))] + solweig_parameters["Names"]["Value"][str(i)] ][0] phi_a1 = solweig_parameters["OHM_coefficients"]["Values"][ - solweig_parameters["Names"]["Value"][str((int(i.item())))] + solweig_parameters["Names"]["Value"][str(i)] ][1] a1_grid[a1_grid == i] = mean_a1 * ( 1 + 0.33 - * torch.sin(2 * torch.pi / 365.25 * day + phi_a1) - * torch.sign(torch.tensor(location["latitude"], device=device)) + * np.sin(2 * np.pi / 365.25 * day + phi_a1) + * np.sign(location["latitude"]) ) a2_grid[a2_grid == i] = solweig_parameters["OHM_coefficients"][ "Values" - ][solweig_parameters["Names"]["Value"][str((int(i.item())))]][2] + ][solweig_parameters["Names"]["Value"][str(i)]][2] a3_grid[a3_grid == i] = solweig_parameters["OHM_coefficients"][ "Values" - ][solweig_parameters["Names"]["Value"][str((int(i.item())))]][3] + ][solweig_parameters["Names"]["Value"][str(i)]][3] # Initial ground surface temperature parameters offset_Tg = solweig_parameters["Tg_ini coefficients"]["Values"][ - solweig_parameters["Names"]["Value"][str((int(i.item())))] + solweig_parameters["Names"]["Value"][str(i)] ][0] - slope_Tg = solweig_parameters["Tg_ini coefficients"]["Values"][ - solweig_parameters["Names"]["Value"][str((int(i.item())))] - ][0] - - ratio_Tg = float( - solweig_parameters["Tg_ini coefficients"]["Values"][ - solweig_parameters["Names"]["Value"][str((int(i.item())))] - ][1] - ) + solweig_parameters["Names"]["Value"][str(i)] + ][1] + ratio_Tg = solweig_parameters["Tg_ini coefficients"]["Values"][ + solweig_parameters["Names"]["Value"][str(i)] + ][2] phi_Tg = 1.6 # Correct the offset value given the latitude @@ -119,14 +111,14 @@ def initiate_groundScheme( # Mean daily soil temperature parameters ampl_Tm = solweig_parameters["Tm_ini coefficients"]["Values"][ - solweig_parameters["Names"]["Value"][str((int(i.item())))] + solweig_parameters["Names"]["Value"][str(i)] ][0] - slope_Tm = solweig_parameters["Tm_inicoefficients"]["Values"][ - solweig_parameters["Names"]["Value"][str((int(i.item())))] - ][1] phi_Tm = 1.7 + slope_Tm = solweig_parameters["Tm_ini coefficients"]["Values"][ + solweig_parameters["Names"]["Value"][str(i)] + ][1] offset_Tm = solweig_parameters["Tm_ini coefficients"]["Values"][ - solweig_parameters["Names"]["Value"][str((int(i.item())))] + solweig_parameters["Names"]["Value"][str(i)] ][2] # Correct the offset value given the latitude @@ -140,18 +132,16 @@ def initiate_groundScheme( * ( 1 + ratio_Tg - * torch.sin(2 * torch.pi / 365.25 * day + phi_Tg) - * torch.sign( - torch.tensor(location["latitude"], device=device) - ) + * np.sin(2 * np.pi / 365.25 * day + phi_Tg) + * np.sign(location["latitude"]) ) + 4 ) Tm[Tm == i] = ( - torch.mean(Ta) + np.mean(Ta) + ampl_Tm - * torch.sin(2 * torch.pi / 365.25 * day + phi_Tm) - * torch.sign(torch.tensor(location["latitude"], device=device)) + * np.sin(2 * np.pi / 365.25 * day + phi_Tm) + * np.sign(location["latitude"]) + offset_Tm + 4 ) @@ -164,28 +154,26 @@ def initiate_groundScheme( * ( 1 + ratio_Tg - * torch.sin(2 * torch.pi / 365.25 * day + phi_Tg) - * torch.sign( - torch.tensor(location["latitude"], device=device) - ) + * np.sin(2 * np.pi / 365.25 * day + phi_Tg) + * np.sign(location["latitude"]) ) + 4 ) - Tm[Tm == i] = torch.mean(Ta) + offset_Tm + Tm[Tm == i] = np.mean(Ta) + offset_Tm elif i == 5: # For grass surfaces Tg[Tg == i] = Ta[0] + offset_Tg * ( 1 + ratio_Tg - * torch.sin(2 * torch.pi / 365.25 * day + phi_Tg) - * torch.sign(torch.tensor(location["latitude"], device=device)) + * np.sin(2 * np.pi / 365.25 * day + phi_Tg) + * np.sign(location["latitude"]) ) Tm[Tm == i] = ( - torch.mean(Ta) + np.mean(Ta) + ampl_Tm - * torch.sin(2 * torch.pi / 365.25 * day + phi_Tm) - * torch.sign(torch.tensor(location["latitude"], device=device)) + * np.sin(2 * np.pi / 365.25 * day + phi_Tm) + * np.sign(location["latitude"]) + offset_Tm ) @@ -197,18 +185,16 @@ def initiate_groundScheme( * ( 1 + ratio_Tg - * torch.sin(2 * torch.pi / 365.25 * day + phi_Tg) - * torch.sign( - torch.tensor(location["latitude"], device=device) - ) + * np.sin(2 * np.pi / 365.25 * day + phi_Tg) + * np.sign(location["latitude"]) ) + 2 ) Tm[Tm == i] = ( - torch.mean(Ta) + np.mean(Ta) + ampl_Tm - * torch.sin(2 * torch.pi / 365.25 * day + phi_Tm) - * torch.sign(torch.tensor(location["latitude"], device=device)) + * np.sin(2 * np.pi / 365.25 * day + phi_Tm) + * np.sign(location["latitude"]) + offset_Tm + 2 ) @@ -285,7 +271,7 @@ def surfaceTemperature_calc( Tg_stored = Tg # Damping depths of the daily surface temperature wave - D = torch.sqrt((2 * diff) / (2 * math.pi / 86400)) + D = np.sqrt((2 * diff) / (2 * math.pi / 86400)) ### Runge Kutta method for the surface temperature calc # First estimate of the surface temperature and of the deep soil temperature given past ground heat flux @@ -308,10 +294,10 @@ def surfaceTemperature_calc( radCriterion = abs( a1 * (Rn_temp - Rn_past) ) # Criterion regarding the radiation step - mask = torch.logical_and( + mask = np.logical_and( deltaG > radCriterion, abs(shadow - shadow_past) > 0.5 ) # Grid of the pixels where the ground heat flux spikes - G_temp[mask] = G[mask] + torch.sign(G_temp - G)[mask] * radCriterion[mask] + G_temp[mask] = G[mask] + np.sign(G_temp - G)[mask] * radCriterion[mask] # Correction of the temperature estimates k2 = 2 * G_temp / cap / D - 2 * math.pi / 86400 * (Tg_temp - Tm) @@ -330,10 +316,10 @@ def surfaceTemperature_calc( radCriterion = abs( a1 * (Rn - Rn_past) ) # Criterion regarding the radiation step - mask = torch.logical_and( + mask = np.logical_and( deltaG > radCriterion, abs(shadow - shadow_past) > 0.5 ) # Grid of the pixels where the ground heat flux spikes - G[mask] = G_past[mask] + torch.sign(G - G_past)[mask] * radCriterion[mask] + G[mask] = G_past[mask] + np.sign(G - G_past)[mask] * radCriterion[mask] ### Water bodies surface temperature estimate beta = 0.45 # Amount of shortwave rad absorbed by the water surface layer @@ -341,18 +327,13 @@ def surfaceTemperature_calc( lamb = 2.260e6 # Latent heat of vaporisation rho = 1000 # Density of water (kg.m-3) Rn_water = ( - Kdown - * (1 - alb) - * ( - beta - + (1 - beta) * (1 - torch.exp(torch.tensor(-1, device=Tg.device))) - ) + Kdown * (1 - alb) * (beta + (1 - beta) * (1 - np.exp(-1))) + Ldown - Lup ) # Net radiation for the top water layer beta described the transmitted rad _, es = saturated_vp(Tg) E = 0.0858 * (es / 1000) * (1 - RH / 100) / 3600 / 1000 * rho * lamb - deltaTg = torch.clone(lc_grid) + deltaTg = np.copy(lc_grid) deltaTg = ( timestep / cap @@ -402,12 +383,9 @@ def outgoingLongwave_calc( """ # Assessment of the distance from a pixel at which most of the radiation are received (cf view factor Lambert) - device = Tg.device - factor = torch.tensor( - 0.99, device=device - ) # Percentage of radiation accounted for + factor = 0.99 # Percentage of radiation accounted for zs = 1.1 # in m - r_max = zs * torch.sqrt( + r_max = zs * np.sqrt( factor / (1 - factor) ) # in m, maximum radius for the radiation calc @@ -419,6 +397,9 @@ def outgoingLongwave_calc( sunlitwall = sunwall sunlitwall[sunlitwall > 0] = 1 + # Boolean array 1 if the pixel is a wall, 0 if not + wallbol = walls > 0 + # The alb grids only take into account the sunlit surfaces in the alb calculation albnosh calculate it for all the surfaces albsunlit = alb * shadow @@ -428,100 +409,93 @@ def outgoingLongwave_calc( # step in meters between every iteration step = 1 - ### Initialize the ground view factor grids as torch.zeros() + # Grid of the outgoing longwave radiation coming from the ground + Lup = (SBC * emis * (Tg + 273.15) ** 4 + Ldown * (1 - emis)) * buildings + + ### Initialize the ground view factor grids as np.zeros() # Upwelling longwave radiation - gvfLup = torch.zeros((rows, cols), device=device) - gvfLupE = torch.zeros((rows, cols), device=device) - gvfLupS = torch.zeros((rows, cols), device=device) - gvfLupW = torch.zeros((rows, cols), device=device) - gvfLupN = torch.zeros((rows, cols), device=device) + gvfLup = np.zeros((rows, cols)) + gvfLupE = np.zeros((rows, cols)) + gvfLupS = np.zeros((rows, cols)) + gvfLupW = np.zeros((rows, cols)) + gvfLupN = np.zeros((rows, cols)) # Albedo of the sunlit surfaces - gvfalbsun = torch.zeros((rows, cols), device=device) - gvfalbsunE = torch.zeros((rows, cols), device=device) - gvfalbsunS = torch.zeros((rows, cols), device=device) - gvfalbsunW = torch.zeros((rows, cols), device=device) - gvfalbsunN = torch.zeros((rows, cols), device=device) + gvfalbsun = np.zeros((rows, cols)) + gvfalbsunE = np.zeros((rows, cols)) + gvfalbsunS = np.zeros((rows, cols)) + gvfalbsunW = np.zeros((rows, cols)) + gvfalbsunN = np.zeros((rows, cols)) # Albedo complete - gvfalbtot = torch.zeros((rows, cols), device=device) - gvfalbtotE = torch.zeros((rows, cols), device=device) - gvfalbtotS = torch.zeros((rows, cols), device=device) - gvfalbtotW = torch.zeros((rows, cols), device=device) - gvfalbtotN = torch.zeros((rows, cols), device=device) + gvfalbtot = np.zeros((rows, cols)) + gvfalbtotE = np.zeros((rows, cols)) + gvfalbtotS = np.zeros((rows, cols)) + gvfalbtotW = np.zeros((rows, cols)) + gvfalbtotN = np.zeros((rows, cols)) # Longwave radiation coming from the side - gvfLsideE = torch.zeros((rows, cols), device=device) - gvfLsideS = torch.zeros((rows, cols), device=device) - gvfLsideW = torch.zeros((rows, cols), device=device) - gvfLsideN = torch.zeros((rows, cols), device=device) + gvfLsideE = np.zeros((rows, cols)) + gvfLsideS = np.zeros((rows, cols)) + gvfLsideW = np.zeros((rows, cols)) + gvfLsideN = np.zeros((rows, cols)) # Add the radiation from the pixel directly below, only for the total gvf # Do not take the roofs into account for now view_factor = (sizepx / 2) ** 2 / ((sizepx / 2) ** 2 + zs**2) - gvfLup = ( - gvfLup + (SBC * emis * (Tg + 273.15) ** 4) * view_factor * buildings - ) + gvfLup = gvfLup + Lup * view_factor gvfalbsun = gvfalbsun + albsunlit * view_factor * buildings gvfalbtot = gvfalbtot + alb * view_factor * buildings # Division of the 360° field of view in 20 and convert the array in radian - azimuths = torch.linspace(18, 360, steps=20, device=device) - azimuths = azimuths * (torch.pi / 180) + azimuths = np.linspace(18, 360, num=20, endpoint=True) + azimuths = azimuths * (np.pi / 180) ### Loop for the number of azimuth values for azimuth in azimuths: # Copy of the building grid - building_copy = buildings - + building_copy = np.copy(buildings) + + # Boolean array 1 if the pixel is (or was) a wall, 0 if not + pastwalls = np.copy(wallbol) + + # Grid of the longwave radiation emitted by the walls + Lwall = SBC * emis_wall * (Tgwall + Ta + 273.15) ** 4 * wallbol + # Initialisation of the tables # First the ones containing the translated rasters (temporary) - building_temp = torch.zeros((rows, cols), device=device) - Lup_temp = torch.zeros((rows, cols), device=device) - Lwall_temp = torch.zeros((rows, cols), device=device) - albsun_temp = torch.zeros((rows, cols), device=device) - albtot_temp = torch.zeros((rows, cols), device=device) - sunlitwall_temp = torch.zeros((rows, cols), device=device) + building_temp = np.copy(buildings) + Lup_temp = np.copy(Lup) + Lwall_temp = np.copy(Lwall) + albsun_temp = np.copy(albsunlit) + albtot_temp = np.copy(alb) + walls_temp = np.zeros((rows, cols)) + sunlitwall_temp = np.zeros((rows, cols)) + onlywall_temp = np.zeros((rows, cols)) # Then the tables containing the sum of the radiations (or albedo) for this azimuth - Lup_sum = torch.zeros((rows, cols), device=device) - LsideE_sum = torch.zeros((rows, cols), device=device) - LsideN_sum = torch.zeros((rows, cols), device=device) - LsideW_sum = torch.zeros((rows, cols), device=device) - LsideS_sum = torch.zeros((rows, cols), device=device) - albsun_sum = torch.zeros((rows, cols), device=device) - albtot_sum = torch.zeros((rows, cols), device=device) + Lup_sum = np.zeros((rows, cols)) + LsideE_sum = np.zeros((rows, cols)) + LsideN_sum = np.zeros((rows, cols)) + LsideW_sum = np.zeros((rows, cols)) + LsideS_sum = np.zeros((rows, cols)) + albsun_sum = np.zeros((rows, cols)) + albtot_sum = np.zeros((rows, cols)) ### Shadow casting algorithm # Translation ranges from 1/2 a pixel to the max radius r_max - for r in torch.arange(sizepx / 2, r_max, step=step): - # Longwave radiation grids both at the ground level and from the walls - Lup = ( - SBC * emis * (Tg + 273.15) ** 4 + Ldown * (1 - emis) - ) * building_copy - Lwall = ( - SBC * emis_wall * (Tgwall + Ta + 273.15) ** 4 * building_copy - ) - + for r in np.arange(sizepx / 2, r_max, step=step): # Step of the raster translation - dx = -torch.cos(azimuth) - dy = -torch.sin(azimuth) + dx = -np.cos(azimuth) + dy = -np.sin(azimuth) # Scale so that the grid is at least translated from 1px if abs(dx) > abs(dy): - dx = -r * torch.sign(torch.cos(azimuth)) - dy = ( - -r - * abs(torch.tan(azimuth)) - * torch.sign(torch.sin(azimuth)) - ) + dx = -r * np.sign(np.cos(azimuth)) + dy = -r * abs(np.tan(azimuth)) * np.sign(np.sin(azimuth)) else: - dx = ( - -r - / abs(torch.tan(azimuth)) - * torch.sign(torch.cos(azimuth)) - ) - dy = -r * torch.sign(torch.sin(azimuth)) + dx = -r / abs(np.tan(azimuth)) * np.sign(np.cos(azimuth)) + dy = -r * np.sign(np.sin(azimuth)) # Select the interested part of the initial raster and the translated one from their four corners and # translating toward the direction azimuth = 0° for dx > 0 @@ -603,8 +577,17 @@ def outgoingLongwave_calc( int(y_select_start) : math.ceil(y_select_end), ] + # All walls grid + walls_temp[ + int(x_transl_start) : math.ceil(x_transl_end), + int(y_transl_start) : math.ceil(y_transl_end), + ] = wallbol[ + int(x_select_start) : math.ceil(x_select_end), + int(y_select_start) : math.ceil(y_select_end), + ] + # Change the boolean building grid, if the px was already a building it remains one (px value = 0) - building_copy = torch.min(building_copy, building_temp) + building_copy = np.min([building_copy, building_temp], axis=0) # For each pixel add the translated Lup to the received rad if there where there is no building view_factor = ((r + step) ** 2 / (zs**2 + (r + step) ** 2)) - ( @@ -613,58 +596,87 @@ def outgoingLongwave_calc( Lup_sum += Lup_temp * view_factor * building_copy / 20 albsun_sum += albsun_temp * view_factor * building_copy / 20 albtot_sum += albtot_temp * view_factor * building_copy / 20 - + # Create a boolean grid to assert that the sunlit walls are not inside a building - wall_temp = wallbol * building_copy + onlywall_temp = np.logical_and( + walls_temp, np.logical_not(pastwalls) + ) + onlywall_temp = onlywall_temp * building_copy onlysunwall_temp = sunlitwall_temp * building_copy + pastwalls = np.logical_or(pastwalls, walls_temp) + # Compute the view factor of the wall surface for a given translatio distance + viewfactor_wall = ( + 1 + / 2 ** (1 / 2) + / 3 + * np.sqrt(1 + (r + step) / np.sqrt((r + step) ** 2 + zs**2)) + * ( + 2 + + (r + sizepx / 2) / np.sqrt((r + sizepx / 2) ** 2 + zs**2) + ) + * ( + 1 + - (r + sizepx / 2) / np.sqrt((r + sizepx / 2) ** 2 + zs**2) + ) + / zs + * np.sqrt((r + sizepx / 2) ** 2 + zs**2) + ) + # Then add the radiation incoming from those walls Lup_sum += ( - wall_temp * Lwall_temp * zs**2 / ((r + step) ** 2 + zs**2) / 20 + onlywall_temp + * Lwall_temp + * viewfactor_wall + * building_copy + / 20 ) albsun_sum += ( onlysunwall_temp * alb_wall - * zs**2 - / ((r + step) ** 2 + zs**2) + * viewfactor_wall + * building_copy / 20 ) albtot_sum += ( - wall_temp * alb_wall * zs**2 / ((r + step) ** 2 + zs**2) / 20 + onlywall_temp * alb_wall * viewfactor_wall * building_copy / 20 ) - - # Finally add the radiation received from the side - dphi = torch.arctan((r + step) / zs) - torch.arctan(r / zs) - dtrigo = zs / torch.sqrt(r**2 + zs**2) * r / torch.sqrt( + + # Finally add the radiation in Lside + dphi = np.arctan((r + step) / zs) - np.arctan(r / zs) + dtrigo = zs / np.sqrt(r**2 + zs**2) * r / np.sqrt( r**2 + zs**2 - ) - zs / torch.sqrt((r + step) ** 2 + zs**2) * ( - r + step - ) / torch.sqrt( + ) - zs / np.sqrt((r + step) ** 2 + zs**2) * (r + step) / np.sqrt( (r + step) ** 2 + zs**2 ) - + # Calculation of the solid angle for each of the cardinal points + # plus add the radiation from a potential wall steradiansW, steradiansS, steradiansE, steradiansN = 0, 0, 0, 0 - if (azimuth >= 0) and (azimuth < torch.pi): - dthetaW = 2 * torch.pi / 20 + if (azimuth >= 0) and (azimuth < np.pi): + dthetaW = 2 * np.pi / 20 steradiansW += dthetaW * (dphi + dtrigo) / 2 + LsideW_sum += onlywall_temp * Lwall_temp * viewfactor_wall / 10 - if (azimuth >= torch.pi / 2) and (azimuth < 3 * torch.pi / 2): - dthetaS = 2 * torch.pi / 20 + if (azimuth >= np.pi / 2) and (azimuth < 3 * np.pi / 2): + dthetaS = 2 * np.pi / 20 steradiansS += dthetaS * (dphi + dtrigo) / 2 + LsideS_sum += onlywall_temp * Lwall_temp * viewfactor_wall / 10 - if (azimuth >= torch.pi) and (azimuth < 2 * torch.pi): - dthetaE = 2 * torch.pi / 20 + if (azimuth >= np.pi) and (azimuth < 2 * np.pi): + dthetaE = 2 * np.pi / 20 steradiansE += dthetaE * (dphi + dtrigo) / 2 + LsideE_sum += onlywall_temp * Lwall_temp * viewfactor_wall / 10 - if (azimuth >= 3 * torch.pi / 2) or (azimuth < torch.pi / 2): - dthetaN = 2 * torch.pi / 20 + if (azimuth >= 3 * np.pi / 2) or (azimuth < np.pi / 2): + dthetaN = 2 * np.pi / 20 steradiansN += dthetaN * (dphi + dtrigo) / 2 + LsideN_sum += onlywall_temp * Lwall_temp * viewfactor_wall / 10 - LsideW_sum += Lup_temp / torch.pi * steradiansW * building_copy - LsideS_sum += Lup_temp / torch.pi * steradiansS * building_copy - LsideE_sum += Lup_temp / torch.pi * steradiansE * building_copy - LsideN_sum += Lup_temp / torch.pi * steradiansN * building_copy + LsideW_sum += Lup_temp / np.pi * steradiansW * building_copy + LsideS_sum += Lup_temp / np.pi * steradiansS * building_copy + LsideE_sum += Lup_temp / np.pi * steradiansE * building_copy + LsideN_sum += Lup_temp / np.pi * steradiansN * building_copy ### Add the value for the computed part of the field of view gvfLup += Lup_sum @@ -672,25 +684,25 @@ def outgoingLongwave_calc( gvfalbtot += albtot_sum # Add the value if the azimuth correspond to the side of the compass - if (azimuth >= 0) and (azimuth < torch.pi): + if (azimuth >= 0) and (azimuth < np.pi): gvfLupW += Lup_sum gvfalbsunW += albsun_sum gvfalbtotW += albtot_sum gvfLsideW += LsideW_sum - if (azimuth >= torch.pi / 2) and (azimuth < 3 * torch.pi / 2): + if (azimuth >= np.pi / 2) and (azimuth < 3 * np.pi / 2): gvfLupS += Lup_sum gvfalbsunS += albsun_sum gvfalbtotS += albtot_sum gvfLsideS += LsideS_sum - if (azimuth >= torch.pi) and (azimuth < 2 * torch.pi): + if (azimuth >= np.pi) and (azimuth < 2 * np.pi): gvfLupE += Lup_sum gvfalbsunE += albsun_sum gvfalbtotE += albtot_sum gvfLsideE += LsideE_sum - if (azimuth >= 3 * torch.pi / 2) or (azimuth < torch.pi / 2): + if (azimuth >= 3 * np.pi / 2) or (azimuth < np.pi / 2): gvfLupN += Lup_sum gvfalbsunN += albsun_sum gvfalbtotN += albtot_sum @@ -699,9 +711,20 @@ def outgoingLongwave_calc( # If the px is associated with a roof landcover, for now Lup = 0 # Here their Lup value is allocated to those px gvfLup += (SBC * emis * (Tg + 273.15) ** 4) * (buildings * -1 + 1) - - # # Finally add the reflection from the downwelling longwave radiation - # gvfLup += Ldown * (1-emis) + gvfLsideE += (SBC * emis * (Tg + 273.15) ** 4) * 0.5 * (buildings * -1 + 1) + gvfLsideN += (SBC * emis * (Tg + 273.15) ** 4) * 0.5 * (buildings * -1 + 1) + gvfLsideW += (SBC * emis * (Tg + 273.15) ** 4) * 0.5 * (buildings * -1 + 1) + gvfLsideS += (SBC * emis * (Tg + 273.15) ** 4) * 0.5 * (buildings * -1 + 1) + gvfalbsun += albsunlit * (buildings * -1 + 1) + gvfalbtot += alb * (buildings * -1 + 1) + gvfalbsunE += albsunlit * 0.5 * (buildings * -1 + 1) + gvfalbsunN += albsunlit * 0.5 * (buildings * -1 + 1) + gvfalbsunW += albsunlit * 0.5 * (buildings * -1 + 1) + gvfalbsunS += albsunlit * 0.5 * (buildings * -1 + 1) + gvfalbtotE += alb * 0.5 * (buildings * -1 + 1) + gvfalbtotN += alb * 0.5 * (buildings * -1 + 1) + gvfalbtotW += alb * 0.5 * (buildings * -1 + 1) + gvfalbtotS += alb * 0.5 * (buildings * -1 + 1) return ( gvfLup, @@ -723,4 +746,4 @@ def outgoingLongwave_calc( gvfLsideS, gvfLsideE, gvfLsideN, - ) + ) \ No newline at end of file diff --git a/functions/SOLWEIGpython/ground_surface_torch.py b/functions/SOLWEIGpython/ground_surface_torch.py new file mode 100644 index 0000000..1d3b8cd --- /dev/null +++ b/functions/SOLWEIGpython/ground_surface_torch.py @@ -0,0 +1,785 @@ +""" +@author: Eliott Bridoux, University of Gothenburg +""" + +import math +try: + import torch +except: + pass +# Stefan-Boltzmann s constant +SBC = 5.67e-8 + + +def saturated_vp(T): + """ + Used in the calculation of the surface temperature for the water bodies. + + :Parameters: + :T: the temperature just above the water + + :Return: + :qs: the saturated vapor pressure + :slope: the slope of the tangent to the saturated vapor pressure-temperature curve + """ + + L = 2.260e6 # Latent heat of vaporisation + R = 8.314 # Gas constant + + # August-Roche-Magnus approx. in Pa + qs = 6109.4 * torch.exp(17.625 * T / (T + 243.04)) + + # Clausius-Clapeyron equation + slope = L * qs / R / (T + 273.15) ** 2 + + return slope, qs + + +def initiate_groundScheme( + lc_grid, solweig_parameters, day, Ta, location, device +): + """ + Setup the maps used in the ground scheme calculations depending on the landcover + + :Parameters: + :lc_grid: Array of landcover values + :solweig_param: Dict of physical parameters + :day: day of the year (int) + :Ta: air temperature (float) + :location: Dict containing latitude and longitude + + :Return: Array for the surface temperature, radiation flux and physical parameters + """ + + # Get the landcover data from lc_grid array + lc_grid[lc_grid >= 100] = 2 + id = torch.unique(lc_grid) + id = id.int() + + # Physical parameters grids + cap_grid = torch.clone(lc_grid) # Heat capacity + diff_grid = torch.clone(lc_grid) # Thermal diffusivity + a1_grid = torch.clone(lc_grid) + a2_grid = torch.clone(lc_grid) + a3_grid = torch.clone(lc_grid) # The 3 OHM coefficients + + # Initial fluxes and temperature grids + Rn = torch.zeros_like(lc_grid) # Net radiation + Rn_past = torch.zeros_like(lc_grid) # Stored net radiation + G = torch.zeros_like(lc_grid) # Ground heat flux + Tg = torch.clone(lc_grid) # Surface temperature + Tm = torch.clone(lc_grid) # Mean daily surface temperature + + for i in id: + cap_grid[cap_grid == i] = solweig_parameters["Heat capacity"]["Value"][ + solweig_parameters["Names"]["Value"][str((int(i.item())))] + ] + diff_grid[diff_grid == i] = solweig_parameters["Thermal_diffusivity"][ + "Value" + ][solweig_parameters["Names"]["Value"][str((int(i.item())))]] + + # Coefficients of the OHM per land cover + mean_a1 = solweig_parameters["OHM_coefficients"]["Values"][ + solweig_parameters["Names"]["Value"][str((int(i.item())))] + ][0] + phi_a1 = solweig_parameters["OHM_coefficients"]["Values"][ + solweig_parameters["Names"]["Value"][str((int(i.item())))] + ][1] + a1_grid[a1_grid == i] = mean_a1 * ( + 1 + + 0.33 + * torch.sin(2 * torch.pi / 365.25 * day + phi_a1) + * torch.sign(torch.tensor(location["latitude"], device=device)) + ) + a2_grid[a2_grid == i] = solweig_parameters["OHM_coefficients"][ + "Values" + ][solweig_parameters["Names"]["Value"][str((int(i.item())))]][2] + a3_grid[a3_grid == i] = solweig_parameters["OHM_coefficients"][ + "Values" + ][solweig_parameters["Names"]["Value"][str((int(i.item())))]][3] + + # Initial ground surface temperature parameters + offset_Tg = solweig_parameters["Tg_ini coefficients"]["Values"][ + solweig_parameters["Names"]["Value"][str((int(i.item())))] + ][0] + + slope_Tg = solweig_parameters["Tg_ini coefficients"]["Values"][ + solweig_parameters["Names"]["Value"][str((int(i.item())))] + ][0] + + ratio_Tg = float( + solweig_parameters["Tg_ini coefficients"]["Values"][ + solweig_parameters["Names"]["Value"][str((int(i.item())))] + ][1] + ) + phi_Tg = 1.6 + + # Correct the offset value given the latitude + offset_Tg += slope_Tg * location["latitude"] + + # Mean daily soil temperature parameters + ampl_Tm = solweig_parameters["Tm_ini coefficients"]["Values"][ + solweig_parameters["Names"]["Value"][str((int(i.item())))] + ][0] + slope_Tm = solweig_parameters["Tm_ini coefficients"]["Values"][ + solweig_parameters["Names"]["Value"][str((int(i.item())))] + ][1] + phi_Tm = 1.7 + offset_Tm = solweig_parameters["Tm_ini coefficients"]["Values"][ + solweig_parameters["Names"]["Value"][str((int(i.item())))] + ][2] + + # Correct the offset value given the latitude + offset_Tm += slope_Tm * location["latitude"] + + if i == 0 or i == 1: + # For paved and asphalt landcover + Tg[Tg == i] = ( + Ta[0] + + offset_Tg + * ( + 1 + + ratio_Tg + * torch.sin(2 * torch.pi / 365.25 * day + phi_Tg) + * torch.sign( + torch.tensor(location["latitude"], device=device) + ) + ) + + 4 + ) + Tm[Tm == i] = ( + torch.mean(Ta) + + ampl_Tm + * torch.sin(2 * torch.pi / 365.25 * day + phi_Tm) + * torch.sign(torch.tensor(location["latitude"], device=device)) + + offset_Tm + + 4 + ) + + elif i == 2: + # For roofs + Tg[Tg == i] = ( + Ta[0] + + offset_Tg + * ( + 1 + + ratio_Tg + * torch.sin(2 * torch.pi / 365.25 * day + phi_Tg) + * torch.sign( + torch.tensor(location["latitude"], device=device) + ) + ) + + 4 + ) + Tm[Tm == i] = torch.mean(Ta) + offset_Tm + + elif i == 5: + # For grass surfaces + Tg[Tg == i] = Ta[0] + offset_Tg * ( + 1 + + ratio_Tg + * torch.sin(2 * torch.pi / 365.25 * day + phi_Tg) + * torch.sign(torch.tensor(location["latitude"], device=device)) + ) + Tm[Tm == i] = ( + torch.mean(Ta) + + ampl_Tm + * torch.sin(2 * torch.pi / 365.25 * day + phi_Tm) + * torch.sign(torch.tensor(location["latitude"], device=device)) + + offset_Tm + ) + + elif i == 6: + # For bare soil landcover + Tg[Tg == i] = ( + Ta[0] + + offset_Tg + * ( + 1 + + ratio_Tg + * torch.sin(2 * torch.pi / 365.25 * day + phi_Tg) + * torch.sign( + torch.tensor(location["latitude"], device=device) + ) + ) + + 2 + ) + Tm[Tm == i] = ( + torch.mean(Ta) + + ampl_Tm + * torch.sin(2 * torch.pi / 365.25 * day + phi_Tm) + * torch.sign(torch.tensor(location["latitude"], device=device)) + + offset_Tm + + 2 + ) + + elif i == 7: + # For water bodies + Tg[Tg == i] = Ta[0] + + return ( + Tg, + Tm, + Rn, + Rn_past, + G, + cap_grid, + diff_grid, + a1_grid, + a2_grid, + a3_grid, + ) + + +def surfaceTemperature_calc( + Kdown, + Ldown, + Rn, + Rn_past, + G, + Tg, + Tm, + alb, + emis, + cap, + diff, + lc_grid, + a1, + a2, + a3, + timestep, + RH, + shadow, + shadow_past, +): + """ + Calculation of the ground surface temperature + + Based on the force restore method with the ground heat flux modeled according to the heat storage flux formulation depicted in the OHM (Grimmond 1991). + A simple model is implemented to assess the surface temperature for water landcover (lc==7) since the OHM fails to model the corresponding ground heat + flux. The temporal integration is done using the Runge-Kutta 2nd order scheme + + :Parameters: + :Kdown: the downwelling shortwave radiation + :Ldown: the downwelling longwave radiation + :Rn_past: net radiation stored to calculate the radiation rate + :G: ground heat flux stored from the previous timestep + :Tg: ground surface temperature grid + :Tm: Temperature of the deep soil + :alb: albedo grid of the ground surface + :emis: emissivity grid of the surface + :cap: heat capacity grid of the material + :diff: thermal diffusivity grid of the material + :lc_grid: landcover grid to identify the water bodies + :a: coefficient grids related to the OHM + :timestep: the timestep between 2 iteration of the simulation in min + + :Return: + :Tg: Surface temperature + :Rn: Net radiation for the current timestep + :Rn_past: Stored net radiation for upcoming radiation rate calc + :G: Ground heat flux for the current timestep + """ + + # Store the past ground surface temperature + Tg_stored = Tg + + # Damping depths of the daily surface temperature wave + D = torch.sqrt((2 * diff) / (2 * math.pi / 86400)) + + ### Runge Kutta method for the surface temperature calc + # First estimate of the surface temperature and of the deep soil temperature given past ground heat flux + k1 = 2 * G / cap / D - 2 * math.pi / 86400 * (Tg - Tm) + Tg_temp = Tg + k1 * timestep + + ### Estimate k2 the surface temperature step based on updated heat fluxes + # The fluxes involved are calculated using the estimated Ts + Lup_temp = SBC * emis * (Tg_temp + 273.15) ** 4 + Ldown * ( + 1 - emis + ) # Temporary outgoing longwave rad (W.m-2) + Rn_temp = Kdown * (1 - alb) + Ldown - Lup_temp # Temporary net rad (W.m-2) + RnStar_temp = (Rn_temp - Rn) / 1 # Temporary radiation rate (W.m-2.h-1) + G_temp = ( + a1 * Rn_temp + a2 * RnStar_temp + a3 + ) # Temporary ground heat flux (W.m-2) + + # Damping of the ground heat flux if it increases (or drops) too quickly + deltaG = abs(G_temp - G) + radCriterion = abs( + a1 * (Rn_temp - Rn_past) + ) # Criterion regarding the radiation step + mask = torch.logical_and( + deltaG > radCriterion, abs(shadow - shadow_past) > 0.5 + ) # Grid of the pixels where the ground heat flux spikes + G_temp[mask] = G[mask] + torch.sign(G_temp - G)[mask] * radCriterion[mask] + + # Correction of the temperature estimates + k2 = 2 * G_temp / cap / D - 2 * math.pi / 86400 * (Tg_temp - Tm) + Tg += (k1 + k2) / 2 * timestep + + ### Finally calculation of the updated heat fluxes + Rn_past = Rn + G_past = G + Lup = SBC * emis * (Tg + 273.15) ** 4 + (1 - emis) * Ldown + Rn = (1 - alb) * Kdown + Ldown - Lup + Rn_star = (Rn - Rn_past) / 1 + G = a1 * Rn + a2 * Rn_star + a3 + + # Damping of the ground heat flux if it increases (or decreases) too quickly + deltaG = abs(G - G_past) + radCriterion = abs( + a1 * (Rn - Rn_past) + ) # Criterion regarding the radiation step + mask = torch.logical_and( + deltaG > radCriterion, abs(shadow - shadow_past) > 0.5 + ) # Grid of the pixels where the ground heat flux spikes + G[mask] = G_past[mask] + torch.sign(G - G_past)[mask] * radCriterion[mask] + + ### Water bodies surface temperature estimate + beta = 0.45 # Amount of shortwave rad absorbed by the water surface layer + thickness = 1 # Depth of the water layer + lamb = 2.260e6 # Latent heat of vaporisation + rho = 1000 # Density of water (kg.m-3) + Rn_water = ( + Kdown + * (1 - alb) + * ( + beta + + (1 - beta) * (1 - torch.exp(torch.tensor(-1, device=Tg.device))) + ) + + Ldown + - Lup + ) # Net radiation for the top water layer beta described the transmitted rad + _, es = saturated_vp(Tg) + E = 0.0858 * (es / 1000) * (1 - RH / 100) / 3600 / 1000 * rho * lamb + deltaTg = torch.clone(lc_grid) + deltaTg = ( + timestep + / cap + / thickness + * (Rn_water - E - diff * cap / thickness * (Tg - Tm)) + ) + Tg[lc_grid == 7] = Tg_stored[lc_grid == 7] + deltaTg[lc_grid == 7] + + return Tg, Rn, Rn_past, G + + +def outgoingLongwave_calc( + Tg, + Tgwall, + Ta, + Ldown, + emis, + alb, + buildings, + shadow, + sunwall, + walls, + rows, + cols, + sizepx, +): + """ + Calculation of the outgoing longwave radiation from the ground, + + :Parameters: + :Tg: ground surface temperature grid + :Tgwall: wall surface temperature grid + :Ta: air temperature grid + :emis: emissivity grid of the surface + :alb: albedo grid of the surface + :emis_wall: emissivity of the wall (for now float = 0.9) + :buildings: boolean grid, 0 if the landcover is roof and 1 if there is no building + :shadow: boolean grid, 0 when the pixel is shadowed and 1 when sunlit + :sunwall: Grid where non zero values indicate a sunlit wall and its height + :walls: grid containing the heights of the walls + :aspect: grid containing the angles of the normal dir to walls + :rows: number of rows in the grids + :cols: number of columns in the grids + :sizepx: size of a pixel in m (1/scale) + + :Return: + """ + + # Assessment of the distance from a pixel at which most of the radiation are received (cf view factor Lambert) + device = Tg.device + factor = torch.tensor( + 0.99, device=device + ) # Percentage of radiation accounted for + zs = 1.1 # in m + r_max = zs * torch.sqrt( + factor / (1 - factor) + ) # in m, maximum radius for the radiation calc + + # Emissivity of the wall + emis_wall = 0.9 + alb_wall = 0.2 + + # Copy of the sunlit wall grid and replacement of the wall height with 1 if sunlit + sunlitwall = sunwall + sunlitwall[sunlitwall > 0] = 1 + + # Boolean array 1 if the pixel is a wall, 0 if not + wallbol = walls > 0 + + # The alb grids only take into account the sunlit surfaces in the alb calculation albnosh calculate it for all the surfaces + albsunlit = alb * shadow + + # Boolean array 1 if the pixel is a wall, 0 if not + wallbol = (walls > 0) * 1 + + # step in meters between every iteration + step = 1 + + # Grid of the outgoing longwave radiation coming from the ground + Lup = (SBC * emis * (Tg + 273.15) ** 4 + Ldown * (1 - emis)) * buildings + + ### Initialize the ground view factor grids as torch.zeros() + # Upwelling longwave radiation + gvfLup = torch.zeros((rows, cols), device=device) + gvfLupE = torch.zeros((rows, cols), device=device) + gvfLupS = torch.zeros((rows, cols), device=device) + gvfLupW = torch.zeros((rows, cols), device=device) + gvfLupN = torch.zeros((rows, cols), device=device) + + # Albedo of the sunlit surfaces + gvfalbsun = torch.zeros((rows, cols), device=device) + gvfalbsunE = torch.zeros((rows, cols), device=device) + gvfalbsunS = torch.zeros((rows, cols), device=device) + gvfalbsunW = torch.zeros((rows, cols), device=device) + gvfalbsunN = torch.zeros((rows, cols), device=device) + + # Albedo complete + gvfalbtot = torch.zeros((rows, cols), device=device) + gvfalbtotE = torch.zeros((rows, cols), device=device) + gvfalbtotS = torch.zeros((rows, cols), device=device) + gvfalbtotW = torch.zeros((rows, cols), device=device) + gvfalbtotN = torch.zeros((rows, cols), device=device) + + # Longwave radiation coming from the side + gvfLsideE = torch.zeros((rows, cols), device=device) + gvfLsideS = torch.zeros((rows, cols), device=device) + gvfLsideW = torch.zeros((rows, cols), device=device) + gvfLsideN = torch.zeros((rows, cols), device=device) + + # Add the radiation from the pixel directly below, only for the total gvf + # Do not take the roofs into account for now + view_factor = (sizepx / 2) ** 2 / ((sizepx / 2) ** 2 + zs**2) + gvfLup = gvfLup + Lup * view_factor + gvfalbsun = gvfalbsun + albsunlit * view_factor * buildings + gvfalbtot = gvfalbtot + alb * view_factor * buildings + + # Division of the 360° field of view in 20 and convert the array in radian + azimuths = torch.linspace(18, 360, steps=20, device=device) + azimuths = azimuths * (torch.pi / 180) + + ### Loop for the number of azimuth values + for azimuth in azimuths: + # Copy of the building grid + building_copy = buildings.clone() + + # Boolean array 1 if the pixel is (or was) a wall, 0 if not + pastwalls = wallbol.clone() + + # Grid of the longwave radiation emitted by the walls + Lwall = SBC * emis_wall * (Tgwall + Ta + 273.15) ** 4 * wallbol + + # Initialisation of the tables + # First the ones containing the translated rasters (temporary) + building_temp = buildings.clone() + Lup_temp = Lup.clone() + Lwall_temp = Lwall.clone() + albsun_temp = albsunlit.clone() + albtot_temp = alb.clone() + walls_temp = torch.zeros((rows, cols), device=device) + sunlitwall_temp = torch.zeros((rows, cols), device=device) + onlywall_temp = torch.zeros((rows, cols), device=device) + + # Then the tables containing the sum of the radiations (or albedo) for this azimuth + Lup_sum = torch.zeros((rows, cols), device=device) + LsideE_sum = torch.zeros((rows, cols), device=device) + LsideN_sum = torch.zeros((rows, cols), device=device) + LsideW_sum = torch.zeros((rows, cols), device=device) + LsideS_sum = torch.zeros((rows, cols), device=device) + albsun_sum = torch.zeros((rows, cols), device=device) + albtot_sum = torch.zeros((rows, cols), device=device) + + ### Shadow casting algorithm + # Translation ranges from 1/2 a pixel to the max radius r_max + for r in torch.arange(sizepx / 2, r_max, step=step): + + # Step of the raster translation + dx = -torch.cos(azimuth) + dy = -torch.sin(azimuth) + + # Scale so that the grid is at least translated from 1px + if abs(dx) > abs(dy): + dx = -r * torch.sign(torch.cos(azimuth)) + dy = ( + -r + * abs(torch.tan(azimuth)) + * torch.sign(torch.sin(azimuth)) + ) + else: + dx = ( + -r + / abs(torch.tan(azimuth)) + * torch.sign(torch.cos(azimuth)) + ) + dy = -r * torch.sign(torch.sin(azimuth)) + + # Select the interested part of the initial raster and the translated one from their four corners and + # translating toward the direction azimuth = 0° for dx > 0 + if dx > 0: + x_select_start = dx + x_select_end = rows + x_transl_start = 0 + x_transl_end = rows - dx + else: + x_select_start = 0 + x_select_end = rows + dx + x_transl_start = -dx + x_transl_end = rows + + # translating toward the direction azimuth = 90° for dy > 0 + if dy > 0: + y_select_start = dy + y_select_end = cols + y_transl_start = 0 + y_transl_end = cols - dy + else: + y_select_start = 0 + y_select_end = cols + dy + y_transl_start = -dy + y_transl_end = cols + + # Copy the initial rasters and input inside translated raster temporary and changing every iteration + # Building grid + building_temp[ + int(x_transl_start) : math.ceil(x_transl_end), + int(y_transl_start) : math.ceil(y_transl_end), + ] = buildings[ + int(x_select_start) : math.ceil(x_select_end), + int(y_select_start) : math.ceil(y_select_end), + ] + + # Ground longwave radiation grid + Lup_temp[ + int(x_transl_start) : math.ceil(x_transl_end), + int(y_transl_start) : math.ceil(y_transl_end), + ] = Lup[ + int(x_select_start) : math.ceil(x_select_end), + int(y_select_start) : math.ceil(y_select_end), + ] + + # Wall longwave radiation grid + Lwall_temp[ + int(x_transl_start) : math.ceil(x_transl_end), + int(y_transl_start) : math.ceil(y_transl_end), + ] = Lwall[ + int(x_select_start) : math.ceil(x_select_end), + int(y_select_start) : math.ceil(y_select_end), + ] + + # Albedo grid for the sunlit area + albsun_temp[ + int(x_transl_start) : math.ceil(x_transl_end), + int(y_transl_start) : math.ceil(y_transl_end), + ] = albsunlit[ + int(x_select_start) : math.ceil(x_select_end), + int(y_select_start) : math.ceil(y_select_end), + ] + + # Albedo grid for all the area + albtot_temp[ + int(x_transl_start) : math.ceil(x_transl_end), + int(y_transl_start) : math.ceil(y_transl_end), + ] = alb[ + int(x_select_start) : math.ceil(x_select_end), + int(y_select_start) : math.ceil(y_select_end), + ] + + # Sunlit wall grid + sunlitwall_temp[ + int(x_transl_start) : math.ceil(x_transl_end), + int(y_transl_start) : math.ceil(y_transl_end), + ] = sunlitwall[ + int(x_select_start) : math.ceil(x_select_end), + int(y_select_start) : math.ceil(y_select_end), + ] + + # All walls grid + walls_temp[ + int(x_transl_start) : math.ceil(x_transl_end), + int(y_transl_start) : math.ceil(y_transl_end), + ] = wallbol[ + int(x_select_start) : math.ceil(x_select_end), + int(y_select_start) : math.ceil(y_select_end), + ] + + # Change the boolean building grid, if the px was already a building it remains one (px value = 0) + building_copy = torch.min(building_copy, building_temp) + + # For each pixel add the translated Lup to the received rad if there where there is no building + view_factor = ((r + step) ** 2 / (zs**2 + (r + step) ** 2)) - ( + r**2 / (zs**2 + r**2) + ) + Lup_sum += Lup_temp * view_factor * building_copy / 20 + albsun_sum += albsun_temp * view_factor * building_copy / 20 + albtot_sum += albtot_temp * view_factor * building_copy / 20 + + + # Create a boolean grid to assert that the sunlit walls are not inside a building + onlywall_temp = torch.logical_and( + walls_temp, torch.logical_not(pastwalls) + ) + onlywall_temp = onlywall_temp * building_copy + onlysunwall_temp = sunlitwall_temp * building_copy + + pastwalls = torch.logical_or(pastwalls, walls_temp) + + + # Compute the view factor of the wall surface for a given translatio distance + viewfactor_wall = ( + 1 + / 2 ** (1 / 2) + / 3 + * torch.sqrt(1 + (r + step) / torch.sqrt((r + step) ** 2 + zs**2)) + * ( + 2 + + (r + sizepx / 2) / torch.sqrt((r + sizepx / 2) ** 2 + zs**2) + ) + * ( + 1 + - (r + sizepx / 2) / torch.sqrt((r + sizepx / 2) ** 2 + zs**2) + ) + / zs + * torch.sqrt((r + sizepx / 2) ** 2 + zs**2) + ) + + # Then add the radiation incoming from those walls + Lup_sum += ( + onlywall_temp + * Lwall_temp + * viewfactor_wall + * building_copy + / 20 + ) + albsun_sum += ( + onlysunwall_temp + * alb_wall + * viewfactor_wall + * building_copy + / 20 + ) + albtot_sum += ( + onlywall_temp * alb_wall * viewfactor_wall * building_copy / 20 + ) + + # Finally add the radiation in Lside + dphi = torch.arctan((r + step) / zs) - torch.arctan(r / zs) + dtrigo = zs / torch.sqrt(r**2 + zs**2) * r / torch.sqrt( + r**2 + zs**2 + ) - zs / torch.sqrt((r + step) ** 2 + zs**2) * ( + r + step + ) / torch.sqrt( + (r + step) ** 2 + zs**2 + ) + + # Calculation of the solid angle for each of the cardinal points + # plus add the radiation from a potential wall + steradiansW, steradiansS, steradiansE, steradiansN = 0, 0, 0, 0 + if (azimuth >= 0) and (azimuth < torch.pi): + dthetaW = 2 * torch.pi / 20 + steradiansW += dthetaW * (dphi + dtrigo) / 2 + LsideW_sum += onlywall_temp * Lwall_temp * viewfactor_wall / 10 + + if (azimuth >= torch.pi / 2) and (azimuth < 3 * torch.pi / 2): + dthetaS = 2 * torch.pi / 20 + steradiansS += dthetaS * (dphi + dtrigo) / 2 + LsideS_sum += onlywall_temp * Lwall_temp * viewfactor_wall / 10 + + if (azimuth >= torch.pi) and (azimuth < 2 * torch.pi): + dthetaE = 2 * torch.pi / 20 + steradiansE += dthetaE * (dphi + dtrigo) / 2 + LsideE_sum += onlywall_temp * Lwall_temp * viewfactor_wall / 10 + + if (azimuth >= 3 * torch.pi / 2) or (azimuth < torch.pi / 2): + dthetaN = 2 * torch.pi / 20 + steradiansN += dthetaN * (dphi + dtrigo) / 2 + LsideN_sum += onlywall_temp * Lwall_temp * viewfactor_wall / 10 + + LsideW_sum += Lup_temp / torch.pi * steradiansW * building_copy + LsideS_sum += Lup_temp / torch.pi * steradiansS * building_copy + LsideE_sum += Lup_temp / torch.pi * steradiansE * building_copy + LsideN_sum += Lup_temp / torch.pi * steradiansN * building_copy + + ### Add the value for the computed part of the field of view + gvfLup += Lup_sum + gvfalbsun += albsun_sum + gvfalbtot += albtot_sum + + # Add the value if the azimuth correspond to the side of the compass + if (azimuth >= 0) and (azimuth < torch.pi): + gvfLupW += Lup_sum + gvfalbsunW += albsun_sum + gvfalbtotW += albtot_sum + gvfLsideW += LsideW_sum + + if (azimuth >= torch.pi / 2) and (azimuth < 3 * torch.pi / 2): + gvfLupS += Lup_sum + gvfalbsunS += albsun_sum + gvfalbtotS += albtot_sum + gvfLsideS += LsideS_sum + + if (azimuth >= torch.pi) and (azimuth < 2 * torch.pi): + gvfLupE += Lup_sum + gvfalbsunE += albsun_sum + gvfalbtotE += albtot_sum + gvfLsideE += LsideE_sum + + if (azimuth >= 3 * torch.pi / 2) or (azimuth < torch.pi / 2): + gvfLupN += Lup_sum + gvfalbsunN += albsun_sum + gvfalbtotN += albtot_sum + gvfLsideN += LsideN_sum + + # If the px is associated with a roof landcover, for now Lup = 0 + # Here their Lup value is allocated to those px + gvfLup += (SBC * emis * (Tg + 273.15) ** 4) * (buildings * -1 + 1) + gvfLsideE += (SBC * emis * (Tg + 273.15) ** 4) * 0.5 * (buildings * -1 + 1) + gvfLsideN += (SBC * emis * (Tg + 273.15) ** 4) * 0.5 * (buildings * -1 + 1) + gvfLsideW += (SBC * emis * (Tg + 273.15) ** 4) * 0.5 * (buildings * -1 + 1) + gvfLsideS += (SBC * emis * (Tg + 273.15) ** 4) * 0.5 * (buildings * -1 + 1) + gvfalbsun += albsunlit * (buildings * -1 + 1) + gvfalbtot += alb * (buildings * -1 + 1) + gvfalbsunE += albsunlit * 0.5 * (buildings * -1 + 1) + gvfalbsunN += albsunlit * 0.5 * (buildings * -1 + 1) + gvfalbsunW += albsunlit * 0.5 * (buildings * -1 + 1) + gvfalbsunS += albsunlit * 0.5 * (buildings * -1 + 1) + gvfalbtotE += alb * 0.5 * (buildings * -1 + 1) + gvfalbtotN += alb * 0.5 * (buildings * -1 + 1) + gvfalbtotW += alb * 0.5 * (buildings * -1 + 1) + gvfalbtotS += alb * 0.5 * (buildings * -1 + 1) + + return ( + gvfLup, + gvfalbsun, + gvfalbtot, + gvfLupE, + gvfalbsunE, + gvfalbtotE, + gvfLupS, + gvfalbsunS, + gvfalbtotS, + gvfLupW, + gvfalbsunW, + gvfalbtotW, + gvfLupN, + gvfalbsunN, + gvfalbtotN, + gvfLsideW, + gvfLsideS, + gvfLsideE, + gvfLsideN, + ) diff --git a/functions/SOLWEIGpython/patch_radiation.py b/functions/SOLWEIGpython/patch_radiation.py index 4787f1f..e46ac50 100644 --- a/functions/SOLWEIGpython/patch_radiation.py +++ b/functions/SOLWEIGpython/patch_radiation.py @@ -1,5 +1,4 @@ import numpy as np -from . import sunlit_shaded_patches def shortwave_from_sky( @@ -160,8 +159,7 @@ def longwave_from_buildings( and (azimuth_difference < 270) and (solar_altitude > 0) ): - # Calculate which patches defined as buildings that are sunlit or shaded - # sunlit_patches, shaded_patches = sunlit_shaded_patches.shaded_or_sunlit(solar_altitude, solar_azimuth, patch_altitude, patch_azimuth, asvf) + # Calculate longwave radiation from sunlit walls to vertical surface Lside_sun = ( diff --git a/functions/SOLWEIGpython/wallOfInterest.py b/functions/SOLWEIGpython/wallOfInterest.py index 60c1771..fbc4bce 100644 --- a/functions/SOLWEIGpython/wallOfInterest.py +++ b/functions/SOLWEIGpython/wallOfInterest.py @@ -1,7 +1,6 @@ import numpy as np from qgis.core import QgsVectorLayer from osgeo.gdalconst import * -from osgeo import gdal, osr def pointOfInterest(poilyr, poi_field, scale, gdal_dsm): diff --git a/functions/SOLWEIGpython/wall_surface_temperature.py b/functions/SOLWEIGpython/wall_surface_temperature.py index b49bec8..7e2505c 100644 --- a/functions/SOLWEIGpython/wall_surface_temperature.py +++ b/functions/SOLWEIGpython/wall_surface_temperature.py @@ -3,7 +3,6 @@ import math from ...functions.SOLWEIGpython.wall_cover import get_wall_cover from .cylindric_wedge import cylindric_wedge_voxel -from .Lvikt_veg import Lvikt_veg # Stefan Boltzmans Constant SBC = 5.67051e-8 diff --git a/functions/svf_for_voxels.py b/functions/svf_for_voxels.py index b8a69e6..b945aaa 100644 --- a/functions/svf_for_voxels.py +++ b/functions/svf_for_voxels.py @@ -1,123 +1,92 @@ -import torch +import numpy as np try: from sklearn.cluster import KMeans -except: - print("passe") +except BaseException: pass - - -def _to_tensor(x, device, dtype=torch.float32): - if isinstance(x, torch.Tensor): - return x.to(device) - if x is None: - return None - return torch.tensor(x, dtype=dtype, device=device) - - from ..functions import svf_functions as svf from ..functions import wallalgorithms as wa -def wallscheme_prepare( - dsm, scale, pixel_resolution, feedback, device=torch.device("cpu") -): - dsm = _to_tensor(dsm, device) - - # Existing UMEP wall and aspect calculations +def wallscheme_prepare(dsm, scale, pixel_resolution, feedback): + total = 100.0 / (int(dsm.shape[0] * dsm.shape[1])) walls = wa.findwalls_sp( - dsm, - 2, - torch.tensor([[1, 1, 1], [1, 0, 1], [1, 1, 1]], device=dsm.device), + dsm, 2, np.array([[1, 1, 1], [1, 0, 1], [1, 1, 1]]) ) - walls_copy = torch.clone(walls) + walls_copy = np.copy(walls) aspect = wa.filter1Goodwin_as_aspect_v3( walls_copy, scale, dsm, feedback, 100 ) - walls_exact = walls.clone() - walls_round = torch.ceil(walls).int() - - # 1. Vectorized identification of wall pixels - wall_rows, wall_cols = torch.where(walls_round > 0) - num_walls = wall_rows.shape[0] - - # 2. Assign Wall IDs (1-indexed) vectorially - uniqueWallIDs = torch.zeros((dsm.shape[0], dsm.shape[1]), device=device) - wall_indices = torch.arange(1, num_walls + 1, device=device) - uniqueWallIDs[wall_rows, wall_cols] = wall_indices.float() - - # 3. Calculate number of voxels for every wall pixel at once - # Extract wall attributes only for the valid wall locations - walls_round_extracted = walls_round[wall_rows, wall_cols] - walls_exact_extracted = walls_exact[wall_rows, wall_cols] - - number_of_voxels = (walls_round_extracted / pixel_resolution).int() - - # 4. Repeat wall properties based on their respective voxel counts - # This replaces both the 'i' and 'j' loops entirely - wall2d_id_tensor = torch.repeat_interleave(wall_indices, number_of_voxels) - wall_height_tensor = torch.repeat_interleave( - walls_round_extracted, number_of_voxels - ) - wall_height_exact_tensor = torch.repeat_interleave( - walls_exact_extracted, number_of_voxels - ) - y_position_tensor = torch.repeat_interleave(wall_rows, number_of_voxels) - x_position_tensor = torch.repeat_interleave(wall_cols, number_of_voxels) - - # 5. Generate voxel heights using a cumulative sequence sequence - # This handles the equivalent of `j * pixel_resolution` vectorially - # We create a 1-based local sequence for each repeated segment - # e.g., if number_of_voxels is [2, 3], we generate [1, 2, 1, 2, 3] - v_ids = torch.arange(wall2d_id_tensor.shape[0], device=device) - # Find the starting index of each repeated wall sequence - cum_voxels = torch.cumsum(number_of_voxels, dim=0) - start_indices = torch.cat( - [torch.tensor([0], device=device), cum_voxels[:-1]] - ) - shift = torch.repeat_interleave(start_indices, number_of_voxels) - - local_voxel_index = v_ids - shift + 1 - voxel_height_tensor = local_voxel_index * pixel_resolution - - # 6. Append the final trailing zero-row (mimicking original code logic) - zero_val = torch.tensor([0], device=device) - - wall2d_id_tensor = torch.cat([wall2d_id_tensor, zero_val]) - voxel_height_tensor = torch.cat([voxel_height_tensor, zero_val.float()]) - wall_height_tensor = torch.cat([wall_height_tensor, zero_val]) - wall_height_exact_tensor = torch.cat( - [wall_height_exact_tensor, zero_val.float()] - ) - y_position_tensor = torch.cat([y_position_tensor, zero_val]) - x_position_tensor = torch.cat([x_position_tensor, zero_val]) - - # 7. Construct outputs - voxelId_list = torch.arange( - 1, wall2d_id_tensor.shape[0] + 1, device=device - ) - - voxelTable = torch.column_stack( + # Copy to keep exact height values + walls_exact = walls.copy() + + # Rounding wall heights (ceil or round?) + # walls_round = np.round(walls).astype(int) + walls_round = np.ceil(walls).astype(int) + + # create wall IDs + wall_rows, wall_cols = np.where(walls_round > 0) + voxel_height = list() + wall2d_id = list() + wall_height = list() + wall_height_exact = list() + y_position = list() + x_position = list() + index = 1 + uniqueWallIDs = np.zeros((dsm.shape[0], dsm.shape[1])) + for i in np.arange(wall_rows.shape[0]): + uniqueWallIDs[wall_rows[i], wall_cols[i]] = index + number_of_voxels = int( + walls_round[wall_rows[i], wall_cols[i]] / pixel_resolution + ) + # temp_aspect = wallAspect[wall_rows[i], wall_cols[i]] + voxel_index = 1 + for j in range(1, number_of_voxels + 1): + # wall_id.append(voxel_index) + wall2d_id.append(index) + voxel_height.append(j * pixel_resolution) + wall_height.append(walls_round[wall_rows[i], wall_cols[i]]) + wall_height_exact.append(walls_exact[wall_rows[i], wall_cols[i]]) + y_position.append(wall_rows[i]) + x_position.append(wall_cols[i]) + # wall_aspect.append(temp_aspect) + + voxel_index += 1 + + index += 1 + + wall2d_id.append(0) + voxel_height.append(0) + wall_height.append(0) + wall_height_exact.append(0) + y_position.append(0) + x_position.append(0) + + wall_dict = {} + for A, B in zip(wall2d_id, wall_height_exact): + wall_dict[A] = B + + # saveraster(dataSet, output_uniquewallid, uniqueWallIDs) + + # Unique IDs for each voxel + voxelId_list = np.arange(1, wall2d_id.__len__() + 1) + + # Table with unique voxel ID, height of voxel, total height of wall, + # unique ID of wall (based on 2D-location in raster) and y and x + # coordinates + voxelTable = np.column_stack( [ - voxelId_list.float(), - voxel_height_tensor, - wall_height_tensor.float(), - wall_height_exact_tensor, - wall2d_id_tensor.float(), - y_position_tensor.float(), - x_position_tensor.float(), + voxelId_list, + voxel_height, + wall_height, + wall_height_exact, + wall2d_id, + y_position, + x_position, ] ) - # 8. Construct dictionary mapping (Kept native Python as requested by return type) - # We do this from the extracted wall tensors to avoid looping over the voxel table - wall_dict = dict( - zip(wall_indices.tolist(), walls_exact_extracted.tolist()) - ) - wall_dict[0] = 0.0 - - # Convert specific lists to match original return signature formats if downstream requires it return ( voxelTable, voxelId_list, @@ -125,8 +94,8 @@ def wallscheme_prepare( walls, aspect, uniqueWallIDs, - wall2d_id_tensor.tolist(), - voxel_height_tensor.tolist(), + wall2d_id, + voxel_height, ) @@ -147,21 +116,9 @@ def svf_for_voxels( svfaveg_array, svf_height_array, feedback, - device=torch.device("cpu"), ): """This function calculates sky view factor at all voxel levels""" - dsm = _to_tensor(dsm, device) - dem = _to_tensor(dem, device) - vegdsm = _to_tensor(vegdsm, device) - vegdsm2 = _to_tensor(vegdsm2, device) - voxelTable = _to_tensor(voxelTable, device) - svf_array = _to_tensor(svf_array, device) - svfbu_array = _to_tensor(svfbu_array, device) - svfveg_array = _to_tensor(svfveg_array, device) - svfaveg_array = _to_tensor(svfaveg_array, device) - svf_height_array = _to_tensor(svf_height_array, device) - # Calculate where there are buildings and not. Used to elevate dem. ground = dsm - dem # Ground == 1 = ground @@ -169,15 +126,14 @@ def svf_for_voxels( # Ground == 0 = buildings ground[ground >= 2] = 0.0 - # Find maximum wall height, used to estimate how many iterations of svf_calc that are required - maxWallHeight = torch.max(voxelTable[:, 2]) - svf_height + # Find maximum wall height, used to estimate how many iterations of + # svf_calc that are required + maxWallHeight = np.max(voxelTable[:, 2]) - svf_height # Counter to feedback current iteration counter = 1 # How many iterations are required to calculate svf for all voxels - loop_range = torch.arange( - svf_height, maxWallHeight + svf_height, svf_height - ) + loop_range = np.arange(svf_height, maxWallHeight + svf_height, svf_height) # Loop for svf calculations of all voxel heights for i in loop_range: @@ -206,7 +162,8 @@ def svf_for_voxels( else: temp_cdsm = dsm * 0.0 temp_cdsm2 = dsm * 0.0 - # Calculate svf. wallScheme set to 0 as only svf is estimated and nothing on the location of voxels, etc. + # Calculate svf. wallScheme set to 0 as only svf is estimated and + # nothing on the location of voxels, etc. wallScheme = 0 ret_ = svf.svfForProcessing153( temp_dsm, @@ -218,7 +175,6 @@ def svf_for_voxels( wallScheme, dem, feedback, - device=device, ) svfbu = ret_["svf"] @@ -233,9 +189,7 @@ def svf_for_voxels( svftotal = svfbu - (1 - svfveg) * (1 - trans) # Get svf for each voxel - voxel_y = torch.where( - voxelTable[:, 1] == i + svf_height - ) # +svf_height) + voxel_y = np.where(voxelTable[:, 1] == i + svf_height) # +svf_height) for temp_y in voxel_y[0]: svf_array[temp_y] = svftotal[ int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) @@ -257,11 +211,11 @@ def svf_for_voxels( counter += 1 - svf_05 = svf_array.clone() + svf_05 = svf_array.copy() svf_05[svf_05 > 0.5] = 0.5 # Add svf arrays as volumns to voxelTable - voxelTable = torch.column_stack( + voxelTable = np.column_stack( [ voxelTable, svf_height_array, @@ -295,19 +249,7 @@ def svf_kmeans( svfaveg_array, svf_height_array, feedback, - device=torch.device("cpu"), ): - dsm = _to_tensor(dsm, device) - dem = _to_tensor(dem, device) - vegdsm = _to_tensor(vegdsm, device) - vegdsm2 = _to_tensor(vegdsm2, device) - wallHeights = _to_tensor(wallHeights, device) - voxelTable = _to_tensor(voxelTable, device) - svf_array = _to_tensor(svf_array, device) - svfbu_array = _to_tensor(svfbu_array, device) - svfveg_array = _to_tensor(svfveg_array, device) - svfaveg_array = _to_tensor(svfaveg_array, device) - svf_height_array = _to_tensor(svf_height_array, device) # Calculate where there are buildings and not. Used to elevate dem. ground = dsm - dem @@ -320,35 +262,35 @@ def svf_kmeans( # Reshape data for clustering # data_reshaped = building_heights.reshape(-1, 1) - data_reshaped = wallHeights.detach().cpu().numpy().reshape(-1, 1) + data_reshaped = wallHeights.reshape(-1, 1) # Apply K-means clustering # clusters = 3 # Number of clusters kmeans = KMeans(n_clusters=clusters, random_state=0) labels = kmeans.fit_predict(data_reshaped) - # Reshape the labels back into a torch tensor on the selected device - kmeans_clusters = torch.from_numpy( - labels.reshape(dsm.shape[0], dsm.shape[1]) - ).to(device) + # Reshape the labels back to the original data shape + kmeans_clusters = labels.reshape(dsm.shape[0], dsm.shape[1]) # Remove cluster representing ground areas, i.e. where dsm - dem = 0 - cluster_range = torch.arange(clusters, device=device) - # cluster_range = cluster_range[cluster_range != torch.unique(kmeans_clusters[ground == 1])] + cluster_range = np.arange(clusters) + # cluster_range = cluster_range[cluster_range != np.unique(kmeans_clusters[ground == 1])] # Array to store mean heights of clusters - cluster_heights = torch.zeros((cluster_range.shape[0]), device=device) + cluster_heights = np.zeros((cluster_range.shape[0])) counter = 0 for i in cluster_range: - # cluster_heights[counter] = torch.round(building_heights[kmeans_clusters == i].mean()) + # cluster_heights[counter] = np.round(building_heights[kmeans_clusters == i].mean()) + # Remove svf_height which is the voxel size to be below the top of the + # wall cluster_heights[counter] = ( - torch.round(wallHeights[kmeans_clusters == i].mean()) - svf_height - ) # Remove svf_height which is the voxel size to be below the top of the wall + np.round(wallHeights[kmeans_clusters == i].mean()) - svf_height + ) counter += 1 # Unique heights based on mean height of clusters, sorted from min to max - cluster_heights = torch.unique(cluster_heights) + cluster_heights = np.unique(cluster_heights) cluster_heights = cluster_heights[cluster_heights > 0] # Counter to feedback current iteration @@ -386,7 +328,8 @@ def svf_kmeans( temp_cdsm = dsm * 0.0 temp_cdsm2 = dsm * 0.0 - # Calculate svf. wallScheme set to 0 as only svf is estimated and nothing on the location of voxels, etc. + # Calculate svf. wallScheme set to 0 as only svf is estimated and + # nothing on the location of voxels, etc. wallScheme = 0 ret_ = svf.svfForProcessing153( temp_dsm, @@ -398,9 +341,7 @@ def svf_kmeans( wallScheme, dem, feedback, - device=device, ) - svfbu = ret_["svf"] if usevegdem == 0: @@ -414,9 +355,7 @@ def svf_kmeans( svftotal = svfbu - (1 - svfveg) * (1 - trans) # Get svf for each voxel - voxel_y = torch.where( - voxelTable[:, 1] == i + svf_height - ) # +svf_height) + voxel_y = np.where(voxelTable[:, 1] == i + svf_height) # +svf_height) for temp_y in voxel_y[0]: svf_array[temp_y] = svftotal[ int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) @@ -433,74 +372,87 @@ def svf_kmeans( svf_height_array[temp_y] = i + svf_height if i == cluster_heights[-1]: - temp_data = voxelTable[ - voxelTable[:, 2] > i, : - ] # Get all walls that are taller than the mean of the lowest cluster - unique_walls = torch.unique( - temp_data[:, 4] - ) # Get their unique wall ids for slicing + # Get all walls that are taller than the mean of the lowest cluster + temp_data = voxelTable[voxelTable[:, 2] > i, :] + # Get their unique wall ids for slicing + unique_walls = np.unique(temp_data[:, 4]) for ( unique_wall ) in ( unique_walls ): # Loop over all unique walls lower than lowest cluster + # Max height of highest voxel in unique_wall temp_wall = temp_data[temp_data[:, 4] == unique_wall, :][ :, 1 - ].max() # Max height of highest voxel in unique_wall - temp_y = torch.where( + ].max() + temp_y = np.where( (voxelTable[:, 4] == unique_wall) & (voxelTable[:, 1] == temp_wall) )[ 0 ] # Get row of unique_wall and highest voxel in voxelTable - svf_array[temp_y] = ( - 0.5 # Set svf to 0.5 as these are the highest voxels and nothing or little should obstruct it, i.e. svf = 0.5 - ) + # Set svf to 0.5 as these are the highest voxels and nothing or + # little should obstruct it, i.e. svf = 0.5 + svf_array[temp_y] = 0.5 svfbu_array[temp_y] = svfbu[ - int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) - ] # save svfbu for current wall pixel + int(voxelTable[temp_y, 5][0]), + int( + # save svfbu for current wall pixel + voxelTable[temp_y, 6][0] + ), + ] svfveg_array[temp_y] = svfveg[ - int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) - ] # save svfveg for current wall pixel + int(voxelTable[temp_y, 5][0]), + int( + # save svfveg for current wall pixel + voxelTable[temp_y, 6][0] + ), + ] svfaveg_array[temp_y] = svfaveg[ - int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) - ] # save svfaveg for current wall pixel - svf_height_array[temp_y] = ( - temp_wall # set svf_height to highest voxel for current wall - ) - # Add 0.5 to highest voxel on walls that are lower than cluster with lowest mean height + int(voxelTable[temp_y, 5][0]), + int( + # save svfaveg for current wall pixel + voxelTable[temp_y, 6][0] + ), + ] + # set svf_height to highest voxel for current wall + svf_height_array[temp_y] = temp_wall + # Add 0.5 to highest voxel on walls that are lower than cluster with + # lowest mean height else: if counter == 0: - temp_data = voxelTable[ - voxelTable[:, 2] < i, : - ] # Get all walls that are lower than the mean of the lowest cluster + # Get all walls that are lower than the mean of the lowest + # cluster + temp_data = voxelTable[voxelTable[:, 2] < i, :] else: temp_data = voxelTable[ (voxelTable[:, 2] > cluster_heights[counter - 1]) & (voxelTable[:, 2] < i), :, ] - unique_walls = torch.unique( - temp_data[:, 4] - ) # Get their unique wall ids for slicing + # Get their unique wall ids for slicing + unique_walls = np.unique(temp_data[:, 4]) for ( unique_wall ) in ( unique_walls ): # Loop over all unique walls lower than lowest cluster + # Max height of highest voxel in unique_wall temp_wall = temp_data[temp_data[:, 4] == unique_wall, :][ :, 1 - ].max() # Max height of highest voxel in unique_wall - temp_y = torch.where( + ].max() + temp_y = np.where( (voxelTable[:, 4] == unique_wall) & (voxelTable[:, 1] == temp_wall) )[ 0 ] # Get row of unique_wall and highest voxel in voxelTable + # get the calculated svf for the wall pixel to check if it is + # higher or lower than 0.5 temp_svf = svftotal[ - int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) - ] # get the calculated svf for the wall pixel to check if it is higher or lower than 0.5 + int(voxelTable[temp_y, 5][0]), int(voxelTable[temp_y, 6][0]) + ] if ( temp_svf < 0.5 ): # if current wall pixel is lower than 0.5, although it is estimated above the wall, save it at the highest voxel and use for interpolation @@ -508,17 +460,28 @@ def svf_kmeans( else: # else, give highest voxel a value of 0.5 svf_array[temp_y] = 0.5 svfbu_array[temp_y] = svfbu[ - int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) - ] # save svfbu for current wall pixel + int(voxelTable[temp_y, 5][0]), + int( + # save svfbu for current wall pixel + voxelTable[temp_y, 6][0] + ), + ] svfveg_array[temp_y] = svfveg[ - int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) - ] # save svfveg for current wall pixel + int(voxelTable[temp_y, 5][0]), + int( + # save svfveg for current wall pixel + voxelTable[temp_y, 6][0] + ), + ] svfaveg_array[temp_y] = svfaveg[ - int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) - ] # save svfaveg for current wall pixel - svf_height_array[temp_y] = ( - temp_wall # set svf_height to highest voxel for current wall - ) + int(voxelTable[temp_y, 5][0]), + int( + # save svfaveg for current wall pixel + voxelTable[temp_y, 6][0] + ), + ] + # set svf_height to highest voxel for current wall + svf_height_array[temp_y] = temp_wall if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") @@ -526,11 +489,11 @@ def svf_kmeans( counter += 1 - svf_05 = svf_array.clone() + svf_05 = svf_array.copy() svf_05[svf_05 > 0.5] = 0.5 # Add svf arrays as volumns to voxelTable - voxelTable = torch.column_stack( + voxelTable = np.column_stack( [ voxelTable, svf_height_array, @@ -545,30 +508,27 @@ def svf_kmeans( return voxelTable, cluster_heights -def interpolate_svf(voxelTable, cluster_heights, kmeans): +def interpolate_svf(voxelTable): - unique_wall_pixels = torch.unique(voxelTable[:, 4]) + unique_wall_pixels = np.unique(voxelTable[:, 4]) unique_wall_pixels = unique_wall_pixels[unique_wall_pixels != 0] for unique_wall in unique_wall_pixels: - temp_data = voxelTable[ - voxelTable[:, 4] == unique_wall, : - ] # All data for current wall pixel - temp_heights = temp_data[ - temp_data[:, -1] != 0, 1 - ] # Voxel heights for current wall pixel where svf has been calculated - temp_svf = temp_data[ - temp_data[:, -1] != 0, -4 - ] # SVF at voxel heights where svf has been calculated + # All data for current wall pixel + temp_data = voxelTable[voxelTable[:, 4] == unique_wall, :] + # Voxel heights for current wall pixel where svf has been calculated + temp_heights = temp_data[temp_data[:, -1] != 0, 1] + # SVF at voxel heights where svf has been calculated + temp_svf = temp_data[temp_data[:, -1] != 0, -4] if temp_heights.size == 1: new_svf = temp_data[temp_data[:, -4] != 0, -4] new_svf[new_svf == 0] = new_svf[new_svf != 0] elif temp_heights.size > 1: # Interpolate - new_svf = torch.interp( - temp_data[:, 1], temp_heights, temp_svf - ) # SVF for all voxels from interpolated values of calculated SVF at different heights (depend on svf_height) + # SVF for all voxels from interpolated values of calculated SVF at + # different heights (depend on svf_height) + new_svf = np.interp(temp_data[:, 1], temp_heights, temp_svf) voxelTable[voxelTable[:, 4] == unique_wall, -4] = ( new_svf # Add the new SVFs to table ) - return voxelTable + return voxelTable \ No newline at end of file diff --git a/functions/svf_for_voxels_torch.py b/functions/svf_for_voxels_torch.py new file mode 100644 index 0000000..cc7267b --- /dev/null +++ b/functions/svf_for_voxels_torch.py @@ -0,0 +1,578 @@ + +from sklearn.cluster import KMeans +import numpy as np + + +def _to_tensor(x, device, dtype=torch.float32): + if isinstance(x, torch.Tensor): + return x.to(device) + if x is None: + return None + return torch.tensor(x, dtype=dtype, device=device) + + +from . import svf_functions_torch as svf +from . import wallalgorithms_torch as wa + + +def wallscheme_prepare( + dsm, scale, pixel_resolution, feedback, device=torch.device("cpu") +): + dsm = _to_tensor(dsm, device) + + # Existing UMEP wall and aspect calculations + walls = wa.findwalls_sp( + dsm, + 2, + device, + torch.tensor([[1, 1, 1], [1, 0, 1], [1, 1, 1]], device=dsm.device), + ) + walls_copy = torch.clone(walls) + print("-1 device :" + str(device)) + aspect = wa.filter1Goodwin_as_aspect_v3( + walls_copy, scale, dsm, feedback, 100, device + ) + + walls_exact = walls.clone() + walls_round = torch.ceil(walls).int() + + # 1. Vectorized identification of wall pixels + wall_rows, wall_cols = torch.where(walls_round > 0) + num_walls = wall_rows.shape[0] + + # 2. Assign Wall IDs (1-indexed) vectorially + uniqueWallIDs = torch.zeros((dsm.shape[0], dsm.shape[1]), device=device) + wall_indices = torch.arange(1, num_walls + 1, device=device) + uniqueWallIDs[wall_rows, wall_cols] = wall_indices.float() + + # 3. Calculate number of voxels for every wall pixel at once + # Extract wall attributes only for the valid wall locations + walls_round_extracted = walls_round[wall_rows, wall_cols] + walls_exact_extracted = walls_exact[wall_rows, wall_cols] + + number_of_voxels = (walls_round_extracted / pixel_resolution).int() + + # 4. Repeat wall properties based on their respective voxel counts + # This replaces both the 'i' and 'j' loops entirely + wall2d_id_tensor = torch.repeat_interleave(wall_indices, number_of_voxels) + wall_height_tensor = torch.repeat_interleave( + walls_round_extracted, number_of_voxels + ) + wall_height_exact_tensor = torch.repeat_interleave( + walls_exact_extracted, number_of_voxels + ) + y_position_tensor = torch.repeat_interleave(wall_rows, number_of_voxels) + x_position_tensor = torch.repeat_interleave(wall_cols, number_of_voxels) + + # 5. Generate voxel heights using a cumulative sequence sequence + # This handles the equivalent of `j * pixel_resolution` vectorially + # We create a 1-based local sequence for each repeated segment + # e.g., if number_of_voxels is [2, 3], we generate [1, 2, 1, 2, 3] + v_ids = torch.arange(wall2d_id_tensor.shape[0], device=device) + # Find the starting index of each repeated wall sequence + cum_voxels = torch.cumsum(number_of_voxels, dim=0) + start_indices = torch.cat( + [torch.tensor([0], device=device), cum_voxels[:-1]] + ) + shift = torch.repeat_interleave(start_indices, number_of_voxels) + + local_voxel_index = v_ids - shift + 1 + voxel_height_tensor = local_voxel_index * pixel_resolution + + # 6. Append the final trailing zero-row (mimicking original code logic) + zero_val = torch.tensor([0], device=device) + + wall2d_id_tensor = torch.cat([wall2d_id_tensor, zero_val]) + voxel_height_tensor = torch.cat([voxel_height_tensor, zero_val.float()]) + wall_height_tensor = torch.cat([wall_height_tensor, zero_val]) + wall_height_exact_tensor = torch.cat( + [wall_height_exact_tensor, zero_val.float()] + ) + y_position_tensor = torch.cat([y_position_tensor, zero_val]) + x_position_tensor = torch.cat([x_position_tensor, zero_val]) + + # 7. Construct outputs + voxelId_list = torch.arange( + 1, wall2d_id_tensor.shape[0] + 1, device=device + ) + + voxelTable = torch.column_stack( + [ + voxelId_list.float(), + voxel_height_tensor, + wall_height_tensor.float(), + wall_height_exact_tensor, + wall2d_id_tensor.float(), + y_position_tensor.float(), + x_position_tensor.float(), + ] + ) + + # 8. Construct dictionary mapping (Kept native Python as requested by return type) + # We do this from the extracted wall tensors to avoid looping over the voxel table + wall_dict = dict( + zip(wall_indices.tolist(), walls_exact_extracted.tolist()) + ) + wall_dict[0] = 0.0 + + # Convert specific lists to match original return signature formats if downstream requires it + return ( + voxelTable, + voxelId_list, + wall_dict, + walls, + aspect, + uniqueWallIDs, + wall2d_id_tensor.tolist(), + voxel_height_tensor.tolist(), + ) + + +def svf_for_voxels( + dsm, + dem, + vegdsm, + vegdsm2, + transVeg, + scale, + usevegdem, + pixel_resolution, + voxelTable, + svf_height, + svf_array, + svfbu_array, + svfveg_array, + svfaveg_array, + svf_height_array, + feedback, + device=torch.device("cpu"), +): + """This function calculates sky view factor at all voxel levels""" + with torch.no_grad(): + dsm = _to_tensor(dsm, device) + dem = _to_tensor(dem, device) + vegdsm = _to_tensor(vegdsm, device) + vegdsm2 = _to_tensor(vegdsm2, device) + voxelTable = _to_tensor(voxelTable, device) + svf_array = _to_tensor(svf_array, device) + svfbu_array = _to_tensor(svfbu_array, device) + svfveg_array = _to_tensor(svfveg_array, device) + svfaveg_array = _to_tensor(svfaveg_array, device) + svf_height_array = _to_tensor(svf_height_array, device) + + # Calculate where there are buildings and not. Used to elevate dem. + ground = dsm - dem + # Ground == 1 = ground + ground[ground < 2] = 1.0 + # Ground == 0 = buildings + ground[ground >= 2] = 0.0 + + # Find maximum wall height, used to estimate how many iterations of svf_calc that are required + maxWallHeight = torch.max(voxelTable[:, 2]) - svf_height + + # Counter to feedback current iteration + counter = 1 + # How many iterations are required to calculate svf for all voxels + loop_range = torch.arange( + svf_height, maxWallHeight + svf_height, svf_height + ) + + # Loop for svf calculations of all voxel heights + for i in loop_range: + + feedback.setProgressText( + "SVF calculation number " + + str(int(counter)) + + " of " + + str(int(loop_range.shape[0])) + ) + + feedback.setProgressText( + "Increasing ground level with " + str(i) + " meters." + ) + + # Elevate ground in dsm + temp_dsm = ((dsm + i) * ground) + (dsm * (1 - ground)) + + if usevegdem == 1: + # Subtract from cdsm + temp_cdsm = vegdsm - i + temp_cdsm[temp_cdsm < 0] = 0 + # Subtract from tdsm + temp_cdsm2 = vegdsm2 - i + temp_cdsm2[temp_cdsm2 < 0] = 0 + else: + temp_cdsm = dsm * 0.0 + temp_cdsm2 = dsm * 0.0 + # Calculate svf. wallScheme set to 0 as only svf is estimated and nothing on the location of voxels, etc. + wallScheme = 0 + ret_ = svf.svfForProcessing153( + temp_dsm, + temp_cdsm, + temp_cdsm2, + scale, + usevegdem, + pixel_resolution, + wallScheme, + dem, + feedback, + device=device, + ) + svfbu = ret_["svf"] + + if usevegdem == 0: + svftotal = svfbu + svfveg = ret_["svfveg"] + svfaveg = ret_["svfaveg"] + else: + svfveg = ret_["svfveg"] + svfaveg = ret_["svfaveg"] + trans = transVeg / 100.0 + svftotal = svfbu - (1 - svfveg) * (1 - trans) + + # Get svf for each voxel + voxel_y = torch.where( + voxelTable[:, 1] == i + svf_height + ) # +svf_height) + for temp_y in voxel_y[0]: + svf_array[temp_y] = svftotal[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] + svfbu_array[temp_y] = svfbu[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ].double() + svfveg_array[temp_y] = svfveg[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] + svfaveg_array[temp_y] = svfaveg[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] + svf_height_array[temp_y] = i + svf_height # +svf_height + + if feedback.isCanceled(): + feedback.setProgressText("Calculation cancelled") + break + + counter += 1 + + svf_05 = svf_array.clone() + svf_05[svf_05 > 0.5] = 0.5 + + # Add svf arrays as volumns to voxelTable + voxelTable = torch.column_stack( + [ + voxelTable, + svf_height_array, + svf_array, + svf_05, + svfbu_array, + svfveg_array, + svfaveg_array, + ] + ) + + return voxelTable + + +def svf_kmeans( + dsm, + dem, + vegdsm, + vegdsm2, + wallHeights, + transVeg, + scale, + usevegdem, + pixel_resolution, + voxelTable, + clusters, + svf_height, + svf_array, + svfbu_array, + svfveg_array, + svfaveg_array, + svf_height_array, + feedback, + device=torch.device("cpu"), +): + with torch.no_grad(): + + dsm = _to_tensor(dsm, device) + dem = _to_tensor(dem, device) + vegdsm = _to_tensor(vegdsm, device) + vegdsm2 = _to_tensor(vegdsm2, device) + wallHeights = _to_tensor(wallHeights, device) + voxelTable = _to_tensor(voxelTable, device) + svf_array = _to_tensor(svf_array, device) + svfbu_array = _to_tensor(svfbu_array, device) + svfveg_array = _to_tensor(svfveg_array, device) + svfaveg_array = _to_tensor(svfaveg_array, device) + svf_height_array = _to_tensor(svf_height_array, device) + + # Calculate where there are buildings and not. Used to elevate dem. + ground = dsm - dem + # Ground == 1 = ground + ground[ground < 2] = 1.0 + # Ground == 0 = buildings + ground[ground >= 2] = 0.0 + + # building_heights = dsm - dem + + # Reshape data for clustering + # data_reshaped = building_heights.reshape(-1, 1) + data_reshaped = wallHeights.detach().cpu().numpy().reshape(-1, 1) + + # Apply K-means clustering + kmeans = KMeans(n_clusters=clusters, random_state=0) + labels = kmeans.fit_predict(data_reshaped) + + # Reshape the labels back into a torch tensor on the selected device + kmeans_clusters = torch.from_numpy( + labels.reshape(dsm.shape[0], dsm.shape[1]) + ).to(device) + + # Remove cluster representing ground areas, i.e. where dsm - dem = 0 + cluster_range = torch.arange(clusters, device=device) + # cluster_range = cluster_range[cluster_range != torch.unique(kmeans_clusters[ground == 1])] + + # Array to store mean heights of clusters + cluster_heights = torch.zeros((cluster_range.shape[0]), device=device) + + counter = 0 + for i in cluster_range: + # cluster_heights[counter] = torch.round(building_heights[kmeans_clusters == i].mean()) + cluster_heights[counter] = ( + torch.round(wallHeights[kmeans_clusters == i].mean()) - svf_height + ) # Remove svf_height which is the voxel size to be below the top of the wall + counter += 1 + + # Unique heights based on mean height of clusters, sorted from min to max + cluster_heights = torch.unique(cluster_heights) + cluster_heights = cluster_heights[cluster_heights > 0] + + # Counter to feedback current iteration + counter = 0 + + for i in cluster_heights: + if cluster_heights.shape[0] > 1: + feedback.setProgressText( + "SVF calculation based on K-means. Calculation " + + str(int(counter + 1)) + + " of " + + str(int(cluster_heights.shape[0])) + + " clusters." + ) + feedback.setProgressText( + "Mean wall height of cluster is " + + str(int(i + svf_height)) + + " meters. Increasing ground level with " + + str(int(i)) + + " meters." + ) + + # Elevate ground in dsm + temp_dsm = ((dsm + i) * ground) + (dsm * (1 - ground)) + # temp_dsm = dsm[ground == 1] + temp_mean + + if usevegdem == 1: + # Subtract from cdsm + temp_cdsm = vegdsm - i + temp_cdsm[temp_cdsm < 0] = 0 + # Subtract from tdsm + temp_cdsm2 = vegdsm2 - i + temp_cdsm2[temp_cdsm2 < 0] = 0 + else: + temp_cdsm = dsm * 0.0 + temp_cdsm2 = dsm * 0.0 + + # Calculate svf. wallScheme set to 0 as only svf is estimated and nothing on the location of voxels, etc. + wallScheme = 0 + ret_ = svf.svfForProcessing153( + temp_dsm, + temp_cdsm, + temp_cdsm2, + scale, + usevegdem, + pixel_resolution, + wallScheme, + dem, + feedback, + device=device, + ) + + svfbu = ret_["svf"] + + if usevegdem == 0: + svftotal = svfbu + svfveg = ret_["svfveg"] + svfaveg = ret_["svfaveg"] + else: + svfveg = ret_["svfveg"] + svfaveg = ret_["svfaveg"] + trans = transVeg / 100.0 + svftotal = svfbu - (1 - svfveg) * (1 - trans) + + # Get svf for each voxel + voxel_y = torch.where( + voxelTable[:, 1] == i + svf_height + ) # +svf_height) + for temp_y in voxel_y[0]: + svf_array[temp_y] = svftotal[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] + svfbu_array[temp_y] = svfbu[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ].double() + svfveg_array[temp_y] = svfveg[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] + svfaveg_array[temp_y] = svfaveg[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] + svf_height_array[temp_y] = i + svf_height + + if i == cluster_heights[-1]: + temp_data = voxelTable[ + voxelTable[:, 2] > i, : + ] # Get all walls that are taller than the mean of the lowest cluster + unique_walls = torch.unique( + temp_data[:, 4] + ) # Get their unique wall ids for slicing + for ( + unique_wall + ) in ( + unique_walls + ): # Loop over all unique walls lower than lowest cluster + temp_wall = temp_data[temp_data[:, 4] == unique_wall, :][ + :, 1 + ].max() # Max height of highest voxel in unique_wall + temp_y = torch.where( + (voxelTable[:, 4] == unique_wall) + & (voxelTable[:, 1] == temp_wall) + )[ + 0 + ] # Get row of unique_wall and highest voxel in voxelTable + + svf_array[temp_y] = ( + 0.5 # Set svf to 0.5 as these are the highest voxels and nothing or little should obstruct it, i.e. svf = 0.5 + ) + svfbu_array[temp_y] = svfbu[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ].double() # save svfbu for current wall pixel + svfveg_array[temp_y] = svfveg[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] # save svfveg for current wall pixel + svfaveg_array[temp_y] = svfaveg[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] # save svfaveg for current wall pixel + svf_height_array[temp_y] = ( + temp_wall # set svf_height to highest voxel for current wall + ) + # Add 0.5 to highest voxel on walls that are lower than cluster with lowest mean height + else: + if counter == 0: + temp_data = voxelTable[ + voxelTable[:, 2] < i, : + ] # Get all walls that are lower than the mean of the lowest cluster + else: + temp_data = voxelTable[ + (voxelTable[:, 2] > cluster_heights[counter - 1]) + & (voxelTable[:, 2] < i), + :, + ] + unique_walls = torch.unique( + temp_data[:, 4] + ) # Get their unique wall ids for slicing + for ( + unique_wall + ) in ( + unique_walls + ): # Loop over all unique walls lower than lowest cluster + temp_wall = temp_data[temp_data[:, 4] == unique_wall, :][ + :, 1 + ].max() # Max height of highest voxel in unique_wall + temp_y = torch.where( + (voxelTable[:, 4] == unique_wall) + & (voxelTable[:, 1] == temp_wall) + )[ + 0 + ] # Get row of unique_wall and highest voxel in voxelTable + temp_svf = svftotal[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] # get the calculated svf for the wall pixel to check if it is higher or lower than 0.5 + if ( + temp_svf < 0.5 + ): # if current wall pixel is lower than 0.5, although it is estimated above the wall, save it at the highest voxel and use for interpolation + svf_array[temp_y] = temp_svf + else: # else, give highest voxel a value of 0.5 + svf_array[temp_y] = 0.5 + svfbu_array[temp_y] = svfbu[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ].double() # save svfbu for current wall pixel + svfveg_array[temp_y] = svfveg[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] # save svfveg for current wall pixel + svfaveg_array[temp_y] = svfaveg[ + int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) + ] # save svfaveg for current wall pixel + svf_height_array[temp_y] = ( + temp_wall # set svf_height to highest voxel for current wall + ) + + if feedback.isCanceled(): + feedback.setProgressText("Calculation cancelled") + break + + counter += 1 + + svf_05 = svf_array.clone() + svf_05[svf_05 > 0.5] = 0.5 + + # Add svf arrays as volumns to voxelTable + voxelTable = torch.column_stack( + [ + voxelTable, + svf_height_array, + svf_array, + svf_05, + svfbu_array, + svfveg_array, + svfaveg_array, + ] + ) + + return voxelTable, cluster_heights + + +def interpolate_svf(voxelTable): + with torch.no_grad(): + device = voxelTable.device + voxelTable_np = voxelTable.cpu().numpy() + + unique_wall_pixels = np.unique(voxelTable_np[:, 4]) + unique_wall_pixels = unique_wall_pixels[unique_wall_pixels != 0] + + for unique_wall in unique_wall_pixels: + # Mask for the current wall pixel + wall_mask = voxelTable_np[:, 4] == unique_wall + temp_data = voxelTable_np[wall_mask, :] # All data for current wall pixel + + # Mask for valid SVF calculation heights + valid_svf_mask = temp_data[:, -1] != 0 + temp_heights = temp_data[valid_svf_mask, 1] # Voxel heights + temp_svf = temp_data[valid_svf_mask, -4] # SVF values + + if len(temp_heights) == 1: + calculated_svf = temp_svf[0] if len(temp_svf) > 0 else 0.0 + new_svf = np.full(temp_data.shape[0], calculated_svf) + + elif len(temp_heights) > 1: # Interpolate + new_svf = np.interp( + temp_data[:, 1], temp_heights, temp_svf + ) + else: + continue + + voxelTable_np[wall_mask, -4] = new_svf + + return torch.from_numpy(voxelTable_np).to(device) diff --git a/functions/svf_functions.py b/functions/svf_functions.py index c245218..b947cdf 100644 --- a/functions/svf_functions.py +++ b/functions/svf_functions.py @@ -1,23 +1,8 @@ -import torch +import numpy as np from ..util import shadowingfunctions as shadow from ..util.SEBESOLWEIGCommonFiles.create_patches import create_patches - -def _to_tensor(x, device, dtype=torch.float32): - if isinstance(x, torch.Tensor): - return x.to(device) - if x is None: - return None - return torch.tensor(x, dtype=dtype, device=device) - - -def _sync_if_cuda(device): - if device is not None and device.type == "cuda": - torch.cuda.synchronize() - - # from ..functions.wallalgorithms import findwalls -from ..functions import wallalgorithms as wa from ..functions import svf_for_voxels as svfv from ..util.SEBESOLWEIGCommonFiles import ( shadowingfunction_wallheight_13 as shb, @@ -27,48 +12,45 @@ def _sync_if_cuda(device): ) # remove -from ..util.misc import saveraster -from osgeo.gdalconst import * -from osgeo import gdal, osr -def annulus_weight(altitude, aziinterval, device): - n = torch.tensor(90.0, device=device) - steprad = (360.0 / aziinterval) * (torch.pi / 180.0) +def annulus_weight(altitude, aziinterval): + n = 90.0 + steprad = (360.0 / aziinterval) * (np.pi / 180.0) annulus = 91.0 - altitude w = ( - (1.0 / (2.0 * torch.pi)) - * torch.sin(torch.pi / (2.0 * n)) - * torch.sin((torch.pi * (2.0 * annulus - 1.0)) / (2.0 * n)) + (1.0 / (2.0 * np.pi)) + * np.sin(np.pi / (2.0 * n)) + * np.sin((np.pi * (2.0 * annulus - 1.0)) / (2.0 * n)) ) weight = steprad * w return weight -def svf_angles_100121(device): - azi1 = torch.arange(1.0, 360.0, 360.0 / 16.0) # %22.5 - azi2 = torch.arange(12.0, 360.0, 360.0 / 16.0) # %22.5 - azi3 = torch.arange(5.0, 360.0, 360.0 / 32.0) # %11.25 - azi4 = torch.arange(2.0, 360.0, 360.0 / 32.0) # %11.25 - azi5 = torch.arange(4.0, 360.0, 360.0 / 40.0) # %9 - azi6 = torch.arange(7.0, 360.0, 360.0 / 48.0) # %7.50 - azi7 = torch.arange(6.0, 360.0, 360.0 / 48.0) # %7.50 - azi8 = torch.arange(1.0, 360.0, 360.0 / 48.0) # %7.50 - azi9 = torch.arange(4.0, 359.0, 360.0 / 52.0) # %6.9231 - azi10 = torch.arange(5.0, 360.0, 360.0 / 52.0) # %6.9231 - azi11 = torch.arange(1.0, 360.0, 360.0 / 48.0) # %7.50 - azi12 = torch.arange(0.0, 359.0, 360.0 / 44.0) # %8.1818 - azi13 = torch.arange(3.0, 360.0, 360.0 / 44.0) # %8.1818 - azi14 = torch.arange(2.0, 360.0, 360.0 / 40.0) # %9 - azi15 = torch.arange(7.0, 360.0, 360.0 / 32.0) # %10 - azi16 = torch.arange(3.0, 360.0, 360.0 / 24.0) # %11.25 - azi17 = torch.arange(10.0, 360.0, 360.0 / 16.0) # %15 - azi18 = torch.arange(19.0, 360.0, 360.0 / 12.0) # %22.5 - azi19 = torch.arange(17.0, 360.0, 360.0 / 8.0) # %45 +def svf_angles_100121(): + azi1 = np.arange(1.0, 360.0, 360.0 / 16.0) # %22.5 + azi2 = np.arange(12.0, 360.0, 360.0 / 16.0) # %22.5 + azi3 = np.arange(5.0, 360.0, 360.0 / 32.0) # %11.25 + azi4 = np.arange(2.0, 360.0, 360.0 / 32.0) # %11.25 + azi5 = np.arange(4.0, 360.0, 360.0 / 40.0) # %9 + azi6 = np.arange(7.0, 360.0, 360.0 / 48.0) # %7.50 + azi7 = np.arange(6.0, 360.0, 360.0 / 48.0) # %7.50 + azi8 = np.arange(1.0, 360.0, 360.0 / 48.0) # %7.50 + azi9 = np.arange(4.0, 359.0, 360.0 / 52.0) # %6.9231 + azi10 = np.arange(5.0, 360.0, 360.0 / 52.0) # %6.9231 + azi11 = np.arange(1.0, 360.0, 360.0 / 48.0) # %7.50 + azi12 = np.arange(0.0, 359.0, 360.0 / 44.0) # %8.1818 + azi13 = np.arange(3.0, 360.0, 360.0 / 44.0) # %8.1818 + azi14 = np.arange(2.0, 360.0, 360.0 / 40.0) # %9 + azi15 = np.arange(7.0, 360.0, 360.0 / 32.0) # %10 + azi16 = np.arange(3.0, 360.0, 360.0 / 24.0) # %11.25 + azi17 = np.arange(10.0, 360.0, 360.0 / 16.0) # %15 + azi18 = np.arange(19.0, 360.0, 360.0 / 12.0) # %22.5 + azi19 = np.arange(17.0, 360.0, 360.0 / 8.0) # %45 azi20 = 0.0 # %360 - iazimuth = torch.tensor( - torch.hstack( + iazimuth = np.array( + np.hstack( ( azi1, azi2, @@ -91,11 +73,10 @@ def svf_angles_100121(device): azi19, azi20, ) - ), - device=device, + ) ) - aziinterval = torch.tensor( - torch.hstack( + aziinterval = np.array( + np.hstack( ( 16.0, 16.0, @@ -118,8 +99,7 @@ def svf_angles_100121(device): 8.0, 1.0, ) - ), - device=device, + ) ) angleresult = {"iazimuth": iazimuth, "aziinterval": aziinterval} @@ -136,42 +116,29 @@ def svfForProcessing153( wallScheme, demlayer, feedback, - device=torch.device("cpu"), ): - if device is None: - device = ( - torch.device("cuda") - if torch.cuda.is_available() - else torch.device("cpu") - ) - - dsm = _to_tensor(dsm, device) - vegdem = _to_tensor(vegdem, device) - vegdem2 = _to_tensor(vegdem2, device) - demlayer = _to_tensor(demlayer, device) - rows = dsm.shape[0] cols = dsm.shape[1] - svf = torch.zeros([rows, cols], device=device) - svfE = torch.zeros([rows, cols], device=device) - svfS = torch.zeros([rows, cols], device=device) - svfW = torch.zeros([rows, cols], device=device) - svfN = torch.zeros([rows, cols], device=device) - svfveg = torch.zeros((rows, cols), device=device) - svfEveg = torch.zeros((rows, cols), device=device) - svfSveg = torch.zeros((rows, cols), device=device) - svfWveg = torch.zeros((rows, cols), device=device) - svfNveg = torch.zeros((rows, cols), device=device) - svfaveg = torch.zeros((rows, cols), device=device) - svfEaveg = torch.zeros((rows, cols), device=device) - svfSaveg = torch.zeros((rows, cols), device=device) - svfWaveg = torch.zeros((rows, cols), device=device) - svfNaveg = torch.zeros((rows, cols), device=device) + svf = np.zeros([rows, cols]) + svfE = np.zeros([rows, cols]) + svfS = np.zeros([rows, cols]) + svfW = np.zeros([rows, cols]) + svfN = np.zeros([rows, cols]) + svfveg = np.zeros((rows, cols)) + svfEveg = np.zeros((rows, cols)) + svfSveg = np.zeros((rows, cols)) + svfWveg = np.zeros((rows, cols)) + svfNveg = np.zeros((rows, cols)) + svfaveg = np.zeros((rows, cols)) + svfEaveg = np.zeros((rows, cols)) + svfSaveg = np.zeros((rows, cols)) + svfWaveg = np.zeros((rows, cols)) + svfNaveg = np.zeros((rows, cols)) # % amaxvalue vegmax = vegdem.max() amaxvalue = dsm.max() - amaxvalue = torch.maximum(amaxvalue, vegmax) + amaxvalue = np.maximum(amaxvalue, vegmax) # % Elevation vegdems if buildingDSM inclused ground heights vegdem = vegdem + dsm @@ -179,7 +146,7 @@ def svfForProcessing153( vegdem2 = vegdem2 + dsm vegdem2[vegdem2 == dsm] = 0 # % Bush separation - bush = torch.logical_not((vegdem2 * vegdem)) * vegdem + bush = np.logical_not((vegdem2 * vegdem)) * vegdem # index = int(0) @@ -197,30 +164,14 @@ def svfForProcessing153( aziinterval, skyvaultaziint, azistart, - ) = create_patches(patch_option, device) - - skyvaultalt = _to_tensor(skyvaultalt, device) - skyvaultazi = _to_tensor(skyvaultazi, device) - annulino = _to_tensor(annulino, device) - skyvaultaltint = _to_tensor(skyvaultaltint, device) - aziinterval = _to_tensor(aziinterval, device) - skyvaultaziint = _to_tensor(skyvaultaziint, device) - azistart = _to_tensor(azistart, device) - - skyvaultaziint = torch.tensor( - [360 / patches for patches in aziinterval], device=device - ) - iazimuth = torch.zeros(int(torch.sum(aziinterval).item()), device=device) + ) = create_patches(patch_option) - shmat = torch.zeros( - (rows, cols, int(torch.sum(aziinterval).item())), device=device - ) - vegshmat = torch.zeros( - (rows, cols, int(torch.sum(aziinterval).item())), device=device - ) - vbshvegshmat = torch.zeros( - (rows, cols, int(torch.sum(aziinterval).item())), device=device - ) + skyvaultaziint = np.array([360 / patches for patches in aziinterval]) + iazimuth = np.hstack(np.zeros((1, np.sum(aziinterval)))) # Nils + + shmat = np.zeros((rows, cols, np.sum(aziinterval))) + vegshmat = np.zeros((rows, cols, np.sum(aziinterval))) + vbshvegshmat = np.zeros((rows, cols, np.sum(aziinterval))) # Preparations for wall temperature scheme if wallScheme: @@ -234,20 +185,12 @@ def svfForProcessing153( uniqueWallIDs, wall2d_id, voxel_height, - ) = svfv.wallscheme_prepare( - dsm, scale, pixel_resolution, feedback, device=device - ) + ) = svfv.wallscheme_prepare(dsm, scale, pixel_resolution, feedback) # Rasters to fill with values in loop - all_buildIDSeen = torch.zeros( - (rows, cols, skyvaultalt.shape[0]), device=device - ) - all_voxelHeight = torch.zeros( - (rows, cols, skyvaultalt.shape[0]), device=device - ) - all_voxelId = torch.zeros( - (rows, cols, skyvaultalt.shape[0]), device=device - ) + all_buildIDSeen = np.zeros((rows, cols, skyvaultalt.shape[0])) + all_voxelHeight = np.zeros((rows, cols, skyvaultalt.shape[0])) + all_voxelId = np.zeros((rows, cols, skyvaultalt.shape[0])) else: voxelTable = 0 allbuildIDSeen = 0 @@ -257,24 +200,20 @@ def svfForProcessing153( index = 0 for j in range(0, skyvaultaltint.shape[0]): - for k in range(0, int(360 / skyvaultaziint[j].item())): + for k in range(0, int(360 / skyvaultaziint[j])): iazimuth[index] = k * skyvaultaziint[j] + azistart[j] if iazimuth[index] > 360.0: iazimuth[index] = iazimuth[index] - 360.0 index = index + 1 - aziintervalaniso = torch.ceil(aziinterval / 2.0) - index = 0 + aziintervalaniso = np.ceil(aziinterval / 2.0) + index = int(0) for i in range(0, skyvaultaltint.shape[0]): - for j in range(0, int(aziinterval[int(i)].item())): + for j in np.arange(0, (aziinterval[int(i)])): if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") break - altitude = torch.tensor( - float(skyvaultaltint[int(i)].item()), device=device - ) - azimuth = torch.tensor( - float(iazimuth[int(index)].item()), device=device - ) + altitude = skyvaultaltint[int(i)] + azimuth = iazimuth[int(index)] # Casting shadow if wallScheme: @@ -298,7 +237,7 @@ def svfForProcessing153( amaxvalue, bush, walls, - aspect * torch.pi / 180, + aspect * np.pi / 180, ) vegshmat[:, :, index] = vegsh vbshvegshmat[:, :, index] = vbshvegsh @@ -310,18 +249,12 @@ def svfForProcessing153( altitude, scale, walls, - aspect * torch.pi / 180.0, + aspect * np.pi / 180.0, ) ) - vegsh = torch.ones( - (sh.shape[0], sh.shape[1]), - device=device, - dtype=torch.float32, - ) - vbshvegsh = torch.ones( - (sh.shape[0], sh.shape[1]), - device=device, - dtype=torch.float32, + vegsh = np.ones((sh.shape[0], sh.shape[1])).astype(float) + vbshvegsh = np.ones((sh.shape[0], sh.shape[1])).astype( + float ) vegshmat[:, :, index] = vegsh vbshvegshmat[:, :, index] = vbshvegsh @@ -338,24 +271,21 @@ def svfForProcessing153( bush, feedback, 1, - device, - ) - - vegsh = torch.tensor(shadowresult["vegsh"], device=device) - vbshvegsh = torch.tensor( - shadowresult["vbshvegsh"], device=device ) + vegsh = shadowresult["vegsh"] + vbshvegsh = shadowresult["vbshvegsh"] vegshmat[:, :, index] = vegsh vbshvegshmat[:, :, index] = vbshvegsh - sh = torch.tensor(shadowresult["sh"], device=device) + sh = shadowresult["sh"] else: sh = shadow.shadowingfunctionglobalradiation( - dsm, azimuth, altitude, scale, feedback, 1, device + dsm, azimuth, altitude, scale, feedback, 1 ) shmat[:, :, index] = sh - # Wall temperature scheme, i.e. finding out which voxel is seen from each pixel, where direction is patch azimuth and altitude + # Wall temperature scheme, i.e. finding out which voxel is seen + # from each pixel, where direction is patch azimuth and altitude if wallScheme: ( all_buildIDSeen[:, :, index], @@ -375,18 +305,15 @@ def svfForProcessing153( facesh, wall_dict, sh, - device, ) # Calculate svfs - for k in range( - int(annulino[int(i)].item()) + 1, - int(annulino[int(i + 1.0)].item()) + 1, + for k in np.arange( + annulino[int(i)] + 1, (annulino[int(i + 1.0)]) + 1 ): - - weight = annulus_weight(k, aziinterval[i], device) * sh + weight = annulus_weight(k, aziinterval[i]) * sh svf = svf + weight - weight = annulus_weight(k, aziintervalaniso[i], device) * sh + weight = annulus_weight(k, aziintervalaniso[i]) * sh if (azimuth >= 0) and (azimuth < 180): svfE = svfE + weight if (azimuth >= 90) and (azimuth < 270): @@ -397,14 +324,14 @@ def svfForProcessing153( svfN = svfN + weight if usevegdem == 1: - for k in torch.arange( + for k in np.arange( annulino[int(i)] + 1, (annulino[int(i + 1.0)]) + 1 ): # % changed to include 90 - weight = annulus_weight(k, aziinterval[i], device) + weight = annulus_weight(k, aziinterval[i]) svfveg = svfveg + weight * vegsh svfaveg = svfaveg + weight * vbshvegsh - weight = annulus_weight(k, aziintervalaniso[i], device) + weight = annulus_weight(k, aziintervalaniso[i]) if (azimuth >= 0) and (azimuth < 180): svfEveg = svfEveg + weight * vegsh svfEaveg = svfEaveg + weight * vbshvegsh @@ -419,7 +346,7 @@ def svfForProcessing153( svfNaveg = svfNaveg + weight * vbshvegsh index += 1 - feedback.setProgress(int(index * (100.0 / torch.sum(aziinterval)))) + feedback.setProgress(int(index * (100.0 / np.sum(aziinterval)))) svfS = svfS + 3.0459e-004 svfW = svfW + 3.0459e-004 @@ -432,7 +359,7 @@ def svfForProcessing153( svfN[(svfN > 1.0)] = 1.0 if usevegdem == 1: - last = torch.zeros((rows, cols), device=device) + last = np.zeros((rows, cols)) last[(vegdem2 == 0.0)] = 3.0459e-004 svfSveg = svfSveg + last svfWveg = svfWveg + last @@ -473,54 +400,35 @@ def svfForProcessing153( "voxelTable": voxelTable, "walls": walls, } - # , # 'vbshvegshmat': vbshvegshmat, 'wallshmat': wallshmat, 'wallsunmat': wallsunmat, # 'wallshvemat': wallshvemat, 'facesunmat': facesunmat} return svfresult -def svfForProcessing655( - dsm, - vegdem, - vegdem2, - scale, - usevegdem, - feedback, - device=torch.device("cpu"), -): - if device is None: - device = ( - torch.device("cuda") - if torch.cuda.is_available() - else torch.device("cpu") - ) - dsm = _to_tensor(dsm, device) - vegdem = _to_tensor(vegdem, device) - vegdem2 = _to_tensor(vegdem2, device) - +def svfForProcessing655(dsm, vegdem, vegdem2, scale, usevegdem, feedback): rows = dsm.shape[0] cols = dsm.shape[1] - svf = torch.zeros([rows, cols], device=device) - svfE = torch.zeros([rows, cols], device=device) - svfS = torch.zeros([rows, cols], device=device) - svfW = torch.zeros([rows, cols], device=device) - svfN = torch.zeros([rows, cols], device=device) - svfveg = torch.zeros((rows, cols), device=device) - svfEveg = torch.zeros((rows, cols), device=device) - svfSveg = torch.zeros((rows, cols), device=device) - svfWveg = torch.zeros((rows, cols), device=device) - svfNveg = torch.zeros((rows, cols), device=device) - svfaveg = torch.zeros((rows, cols), device=device) - svfEaveg = torch.zeros((rows, cols), device=device) - svfSaveg = torch.zeros((rows, cols), device=device) - svfWaveg = torch.zeros((rows, cols), device=device) - svfNaveg = torch.zeros((rows, cols), device=device) + svf = np.zeros([rows, cols]) + svfE = np.zeros([rows, cols]) + svfS = np.zeros([rows, cols]) + svfW = np.zeros([rows, cols]) + svfN = np.zeros([rows, cols]) + svfveg = np.zeros((rows, cols)) + svfEveg = np.zeros((rows, cols)) + svfSveg = np.zeros((rows, cols)) + svfWveg = np.zeros((rows, cols)) + svfNveg = np.zeros((rows, cols)) + svfaveg = np.zeros((rows, cols)) + svfEaveg = np.zeros((rows, cols)) + svfSaveg = np.zeros((rows, cols)) + svfWaveg = np.zeros((rows, cols)) + svfNaveg = np.zeros((rows, cols)) # % amaxvalue vegmax = vegdem.max() amaxvalue = dsm.max() - amaxvalue = torch.maximum(amaxvalue, vegmax) + amaxvalue = np.maximum(amaxvalue, vegmax) # % Elevation vegdems if buildingDSM inclused ground heights vegdem = vegdem + dsm @@ -528,32 +436,31 @@ def svfForProcessing655( vegdem2 = vegdem2 + dsm vegdem2[vegdem2 == dsm] = 0 # % Bush separation - bush = torch.logical_not((vegdem2 * vegdem)) * vegdem + bush = np.logical_not((vegdem2 * vegdem)) * vegdem + + # shmat = np.zeros((rows, cols, 145)) + # vegshmat = np.zeros((rows, cols, 145)) - noa = torch.tensor(19.0, device=device) + noa = 19.0 # % No. of anglesteps minus 1 step = 89.0 / noa - iangle = torch.tensor( - torch.hstack((torch.arange(step / 2.0, 89.0, step), 90.0)), - device=device, + iangle = np.array(np.hstack((np.arange(step / 2.0, 89.0, step), 90.0))) + annulino = np.array( + np.hstack((np.round(np.arange(0.0, 89.0, step)), 90.0)) ) - annulino = torch.tensor( - torch.hstack((torch.round(torch.arange(0.0, 89.0, step)), 90.0)), - device=device, - ) - angleresult = svf_angles_100121(device) - aziinterval = angleresult["aziinterval"].to(device) - iazimuth = angleresult["iazimuth"].to(device) - aziintervalaniso = torch.ceil((aziinterval / 2.0)) - index = 1 - - for i in range(0, iangle.shape[0] - 1): - for j in range(0, int(aziinterval[int(i)].item())): + angleresult = svf_angles_100121() + aziinterval = angleresult["aziinterval"] + iazimuth = angleresult["iazimuth"] + aziintervalaniso = np.ceil((aziinterval / 2.0)) + index = 1.0 + + for i in np.arange(0, iangle.shape[0] - 1): + for j in np.arange(0, (aziinterval[int(i)])): if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") break - altitude = float(iangle[int(i)].item()) - azimuth = float(iazimuth[int(index) - 1].item()) + altitude = iangle[int(i)] + azimuth = iazimuth[int(index) - 1] # Casting shadow if usevegdem == 1: @@ -568,27 +475,22 @@ def svfForProcessing655( bush, feedback, 1, - device, - ) - - vegsh = torch.tensor(shadowresult["vegsh"], device=device) - vbshvegsh = torch.tensor( - shadowresult["vbshvegsh"], device=device ) - sh = torch.tensor(shadowresult["sh"], device=device) + vegsh = shadowresult["vegsh"] + vbshvegsh = shadowresult["vbshvegsh"] + sh = shadowresult["sh"] else: sh = shadow.shadowingfunctionglobalradiation( - dsm, azimuth, altitude, scale, feedback, 1, device + dsm, azimuth, altitude, scale, feedback, 1 ) # Calculate svfs - for k in range( - int(annulino[int(i)].item()) + 1, - int(annulino[int(i + 1.0)].item()) + 1, + for k in np.arange( + annulino[int(i)] + 1, (annulino[int(i + 1.0)]) + 1 ): - weight = annulus_weight(k, aziinterval[i], device) * sh + weight = annulus_weight(k, aziinterval[i]) * sh svf = svf + weight - weight = annulus_weight(k, aziintervalaniso[i], device) * sh + weight = annulus_weight(k, aziintervalaniso[i]) * sh if (azimuth >= 0) and (azimuth < 180): svfE = svfE + weight if (azimuth >= 90) and (azimuth < 270): @@ -599,14 +501,14 @@ def svfForProcessing655( svfN = svfN + weight if usevegdem == 1: - for k in torch.arange( + for k in np.arange( annulino[int(i)] + 1, (annulino[int(i + 1.0)]) + 1 ): # % changed to include 90 - weight = annulus_weight(k, aziinterval[i], device) + weight = annulus_weight(k, aziinterval[i]) svfveg = svfveg + weight * vegsh svfaveg = svfaveg + weight * vbshvegsh - weight = annulus_weight(k, aziintervalaniso[i], device) + weight = annulus_weight(k, aziintervalaniso[i]) if (azimuth >= 0) and (azimuth < 180): svfEveg = svfEveg + weight * vegsh svfEaveg = svfEaveg + weight * vbshvegsh @@ -634,7 +536,7 @@ def svfForProcessing655( svfN[(svfN > 1.0)] = 1.0 if usevegdem == 1: - last = torch.zeros((rows, cols), device=device) + last = np.zeros((rows, cols)) last[(vegdem2 == 0.0)] = 3.0459e-004 svfSveg = svfSveg + last svfWveg = svfWveg + last @@ -669,4 +571,5 @@ def svfForProcessing655( "svfWaveg": svfWaveg, "svfNaveg": svfNaveg, } - return svfresult + + return svfresult \ No newline at end of file diff --git a/functions/svf_functions_torch.py b/functions/svf_functions_torch.py new file mode 100644 index 0000000..1141a47 --- /dev/null +++ b/functions/svf_functions_torch.py @@ -0,0 +1,671 @@ +try: + import torch +except: + pass + +from ..util import shadowingfunctions_torch as shadow +from ..util.SEBESOLWEIGCommonFiles.create_patches_torch import create_patches + + +def _to_tensor(x, device, dtype=torch.float32): + if isinstance(x, torch.Tensor): + return x.to(device) + if x is None: + return None + return torch.tensor(x, dtype=dtype, device=device) + + +def _sync_if_cuda(device): + if device is not None and device.type == "cuda": + torch.cuda.synchronize() + + +# from ..functions.wallalgorithms import findwalls +from . import wallalgorithms_torch as wa +from . import svf_for_voxels_torch as svfv +from ..util.SEBESOLWEIGCommonFiles import ( + shadowingfunction_wallheight_13_torch as shb, +) +from ..util.SEBESOLWEIGCommonFiles import ( + shadowingfunction_wallheight_23_torch as shbv, +) + +# remove +from ..util.misc import saveraster +from osgeo.gdalconst import * +from osgeo import gdal, osr + + +def annulus_weight(altitude, aziinterval, device): + n = torch.tensor(90.0, device=device) + steprad = (360.0 / aziinterval) * (torch.pi / 180.0) + annulus = 91.0 - altitude + w = ( + (1.0 / (2.0 * torch.pi)) + * torch.sin(torch.pi / (2.0 * n)) + * torch.sin((torch.pi * (2.0 * annulus - 1.0)) / (2.0 * n)) + ) + weight = steprad * w + + return weight + + +def svf_angles_100121(device): + azi1 = torch.arange(1.0, 360.0, 360.0 / 16.0) # %22.5 + azi2 = torch.arange(12.0, 360.0, 360.0 / 16.0) # %22.5 + azi3 = torch.arange(5.0, 360.0, 360.0 / 32.0) # %11.25 + azi4 = torch.arange(2.0, 360.0, 360.0 / 32.0) # %11.25 + azi5 = torch.arange(4.0, 360.0, 360.0 / 40.0) # %9 + azi6 = torch.arange(7.0, 360.0, 360.0 / 48.0) # %7.50 + azi7 = torch.arange(6.0, 360.0, 360.0 / 48.0) # %7.50 + azi8 = torch.arange(1.0, 360.0, 360.0 / 48.0) # %7.50 + azi9 = torch.arange(4.0, 359.0, 360.0 / 52.0) # %6.9231 + azi10 = torch.arange(5.0, 360.0, 360.0 / 52.0) # %6.9231 + azi11 = torch.arange(1.0, 360.0, 360.0 / 48.0) # %7.50 + azi12 = torch.arange(0.0, 359.0, 360.0 / 44.0) # %8.1818 + azi13 = torch.arange(3.0, 360.0, 360.0 / 44.0) # %8.1818 + azi14 = torch.arange(2.0, 360.0, 360.0 / 40.0) # %9 + azi15 = torch.arange(7.0, 360.0, 360.0 / 32.0) # %10 + azi16 = torch.arange(3.0, 360.0, 360.0 / 24.0) # %11.25 + azi17 = torch.arange(10.0, 360.0, 360.0 / 16.0) # %15 + azi18 = torch.arange(19.0, 360.0, 360.0 / 12.0) # %22.5 + azi19 = torch.arange(17.0, 360.0, 360.0 / 8.0) # %45 + azi20 = 0.0 # %360 + iazimuth = torch.tensor( + torch.hstack( + ( + azi1, + azi2, + azi3, + azi4, + azi5, + azi6, + azi7, + azi8, + azi9, + azi10, + azi11, + azi12, + azi13, + azi14, + azi15, + azi16, + azi17, + azi18, + azi19, + azi20, + ) + ), + device=device, + ) + aziinterval = torch.tensor( + torch.hstack( + ( + 16.0, + 16.0, + 32.0, + 32.0, + 40.0, + 48.0, + 48.0, + 48.0, + 52.0, + 52.0, + 48.0, + 44.0, + 44.0, + 40.0, + 32.0, + 24.0, + 16.0, + 12.0, + 8.0, + 1.0, + ) + ), + device=device, + ) + angleresult = {"iazimuth": iazimuth, "aziinterval": aziinterval} + + return angleresult + + +def svfForProcessing153( + dsm, + vegdem, + vegdem2, + scale, + usevegdem, + pixel_resolution, + wallScheme, + demlayer, + feedback, + device=torch.device("cpu"), +): + + with torch.no_grad(): + + + dsm = _to_tensor(dsm, device) + vegdem = _to_tensor(vegdem, device) + vegdem2 = _to_tensor(vegdem2, device) + demlayer = _to_tensor(demlayer, device) + + rows = dsm.shape[0] + cols = dsm.shape[1] + svf = torch.zeros([rows, cols], device=device) + svfE = torch.zeros([rows, cols], device=device) + svfS = torch.zeros([rows, cols], device=device) + svfW = torch.zeros([rows, cols], device=device) + svfN = torch.zeros([rows, cols], device=device) + svfveg = torch.zeros((rows, cols), device=device) + svfEveg = torch.zeros((rows, cols), device=device) + svfSveg = torch.zeros((rows, cols), device=device) + svfWveg = torch.zeros((rows, cols), device=device) + svfNveg = torch.zeros((rows, cols), device=device) + svfaveg = torch.zeros((rows, cols), device=device) + svfEaveg = torch.zeros((rows, cols), device=device) + svfSaveg = torch.zeros((rows, cols), device=device) + svfWaveg = torch.zeros((rows, cols), device=device) + svfNaveg = torch.zeros((rows, cols), device=device) + + # % amaxvalue + vegmax = vegdem.max() + amaxvalue = dsm.max() + amaxvalue = torch.maximum(amaxvalue, vegmax) + + # % Elevation vegdems if buildingDSM inclused ground heights + vegdem = vegdem + dsm + vegdem[vegdem == dsm] = 0 + vegdem2 = vegdem2 + dsm + vegdem2[vegdem2 == dsm] = 0 + # % Bush separation + bush = torch.logical_not((vegdem2 * vegdem)) * vegdem + + # index = int(0) + + # patch_option = 1 # 145 patches + patch_option = 2 # 153 patches + # patch_option = 3 # 306 patches + # patch_option = 4 # 612 patches + + # Create patches based on patch_option + ( + skyvaultalt, + skyvaultazi, + annulino, + skyvaultaltint, + aziinterval, + skyvaultaziint, + azistart, + ) = create_patches(patch_option, device) + + skyvaultalt = _to_tensor(skyvaultalt, device) + skyvaultazi = _to_tensor(skyvaultazi, device) + annulino = _to_tensor(annulino, device) + skyvaultaltint = _to_tensor(skyvaultaltint, device) + aziinterval = _to_tensor(aziinterval, device) + skyvaultaziint = _to_tensor(skyvaultaziint, device) + azistart = _to_tensor(azistart, device) + + skyvaultaziint = torch.tensor( + [360 / patches for patches in aziinterval], device=device + ) + iazimuth = torch.zeros(int(torch.sum(aziinterval).item()), device=device) + + shmat = torch.zeros( + (rows, cols, int(torch.sum(aziinterval).item())), device=device + ) + vegshmat = torch.zeros( + (rows, cols, int(torch.sum(aziinterval).item())), device=device + ) + vbshvegshmat = torch.zeros( + (rows, cols, int(torch.sum(aziinterval).item())), device=device + ) + + # Preparations for wall temperature scheme + if wallScheme: + feedback.setProgressText("Estimating view factors for wall voxels") + ( + voxelTable, + voxelId_list, + wall_dict, + walls, + aspect, + uniqueWallIDs, + wall2d_id, + voxel_height, + ) = svfv.wallscheme_prepare( + dsm, scale, pixel_resolution, feedback, device=device + ) + print("-2 device :" + str(device)) + + # Rasters to fill with values in loop + all_buildIDSeen = torch.zeros( + (rows, cols, skyvaultalt.shape[0]), device=device + ) + all_voxelHeight = torch.zeros( + (rows, cols, skyvaultalt.shape[0]), device=device + ) + all_voxelId = torch.zeros( + (rows, cols, skyvaultalt.shape[0]), device=device + ) + else: + voxelTable = 0 + allbuildIDSeen = 0 + allvoxelHeight = 0 + all_voxelId = 0 + walls = 0 + + index = 0 + for j in range(0, skyvaultaltint.shape[0]): + for k in range(0, int(360 / skyvaultaziint[j].item())): + iazimuth[index] = k * skyvaultaziint[j] + azistart[j] + if iazimuth[index] > 360.0: + iazimuth[index] = iazimuth[index] - 360.0 + index = index + 1 + aziintervalaniso = torch.ceil(aziinterval / 2.0) + index = 0 + for i in range(0, skyvaultaltint.shape[0]): + for j in range(0, int(aziinterval[int(i)].item())): + if feedback.isCanceled(): + feedback.setProgressText("Calculation cancelled") + break + altitude = torch.tensor( + float(skyvaultaltint[int(i)].item()), device=device + ) + azimuth = torch.tensor( + float(iazimuth[int(index)].item()), device=device + ) + + # Casting shadow + if wallScheme: + if usevegdem == 1: + ( + vegsh, + sh, + vbshvegsh, + wallsh, + wallsun, + wallshve, + facesh, + facesun, + ) = shbv.shadowingfunction_wallheight_23( + dsm, + vegdem, + vegdem2, + azimuth, + altitude, + scale, + amaxvalue, + bush, + walls, + aspect * torch.pi / 180, + ) + vegshmat[:, :, index] = vegsh + vbshvegshmat[:, :, index] = vbshvegsh + else: + sh, wallsh, wallsun, facesh, facesun = ( + shb.shadowingfunction_wallheight_13( + dsm, + azimuth, + altitude, + scale, + walls, + aspect * torch.pi / 180.0, + ) + ) + vegsh = torch.ones( + (sh.shape[0], sh.shape[1]), + device=device, + dtype=torch.float32, + ) + vbshvegsh = torch.ones( + (sh.shape[0], sh.shape[1]), + device=device, + dtype=torch.float32, + ) + vegshmat[:, :, index] = vegsh + vbshvegshmat[:, :, index] = vbshvegsh + else: + if usevegdem == 1: + shadowresult = shadow.shadowingfunction_20( + dsm, + vegdem, + vegdem2, + azimuth, + altitude, + scale, + amaxvalue, + bush, + feedback, + 1, + device, + ) + + vegsh = torch.tensor(shadowresult["vegsh"], device=device) + vbshvegsh = torch.tensor( + shadowresult["vbshvegsh"], device=device + ) + vegshmat[:, :, index] = vegsh + vbshvegshmat[:, :, index] = vbshvegsh + sh = torch.tensor(shadowresult["sh"], device=device) + else: + sh = shadow.shadowingfunctionglobalradiation( + dsm, azimuth, altitude, scale, feedback, 1, device + ) + + shmat[:, :, index] = sh + + # Wall temperature scheme, i.e. finding out which voxel is seen from each pixel, where direction is patch azimuth and altitude + if wallScheme: + ( + all_buildIDSeen[:, :, index], + all_voxelHeight[:, :, index], + all_voxelId[:, :, index], + ) = shadow.shadowingfunction_findwallID( + dsm, + azimuth, + altitude, + scale, + walls, + uniqueWallIDs, + demlayer, + wall2d_id, + voxel_height, + voxelId_list, + facesh, + wall_dict, + sh, + device, + ) + + # Calculate svfs + for k in range( + int(annulino[int(i)].item()) + 1, + int(annulino[int(i + 1.0)].item()) + 1, + ): + + weight = annulus_weight(k, aziinterval[i], device) * sh + svf = svf + weight + weight = annulus_weight(k, aziintervalaniso[i], device) * sh + if (azimuth >= 0) and (azimuth < 180): + svfE = svfE + weight + if (azimuth >= 90) and (azimuth < 270): + svfS = svfS + weight + if (azimuth >= 180) and (azimuth < 360): + svfW = svfW + weight + if (azimuth >= 270) or (azimuth < 90): + svfN = svfN + weight + + if usevegdem == 1: + for k in torch.arange( + annulino[int(i)] + 1, (annulino[int(i + 1.0)]) + 1 + ): + # % changed to include 90 + weight = annulus_weight(k, aziinterval[i], device) + svfveg = svfveg + weight * vegsh + svfaveg = svfaveg + weight * vbshvegsh + weight = annulus_weight(k, aziintervalaniso[i], device) + if (azimuth >= 0) and (azimuth < 180): + svfEveg = svfEveg + weight * vegsh + svfEaveg = svfEaveg + weight * vbshvegsh + if (azimuth >= 90) and (azimuth < 270): + svfSveg = svfSveg + weight * vegsh + svfSaveg = svfSaveg + weight * vbshvegsh + if (azimuth >= 180) and (azimuth < 360): + svfWveg = svfWveg + weight * vegsh + svfWaveg = svfWaveg + weight * vbshvegsh + if (azimuth >= 270) or (azimuth < 90): + svfNveg = svfNveg + weight * vegsh + svfNaveg = svfNaveg + weight * vbshvegsh + + index += 1 + feedback.setProgress(int(index * (100.0 / torch.sum(aziinterval)))) + + svfS = svfS + 3.0459e-004 + svfW = svfW + 3.0459e-004 + # % Last azimuth is 90. Hence, manual add of last annuli for svfS and SVFW + # %Forcing svf not be greater than 1 (some MATLAB crazyness) + svf[(svf > 1.0)] = 1.0 + svfE[(svfE > 1.0)] = 1.0 + svfS[(svfS > 1.0)] = 1.0 + svfW[(svfW > 1.0)] = 1.0 + svfN[(svfN > 1.0)] = 1.0 + + if usevegdem == 1: + last = torch.zeros((rows, cols), device=device) + last[(vegdem2 == 0.0)] = 3.0459e-004 + svfSveg = svfSveg + last + svfWveg = svfWveg + last + svfSaveg = svfSaveg + last + svfWaveg = svfWaveg + last + # %Forcing svf not be greater than 1 (some MATLAB crazyness) + svfveg[(svfveg > 1.0)] = 1.0 + svfEveg[(svfEveg > 1.0)] = 1.0 + svfSveg[(svfSveg > 1.0)] = 1.0 + svfWveg[(svfWveg > 1.0)] = 1.0 + svfNveg[(svfNveg > 1.0)] = 1.0 + svfaveg[(svfaveg > 1.0)] = 1.0 + svfEaveg[(svfEaveg > 1.0)] = 1.0 + svfSaveg[(svfSaveg > 1.0)] = 1.0 + svfWaveg[(svfWaveg > 1.0)] = 1.0 + svfNaveg[(svfNaveg > 1.0)] = 1.0 + + svfresult = { + "svf": svf, + "svfE": svfE, + "svfS": svfS, + "svfW": svfW, + "svfN": svfN, + "svfveg": svfveg, + "svfEveg": svfEveg, + "svfSveg": svfSveg, + "svfWveg": svfWveg, + "svfNveg": svfNveg, + "svfaveg": svfaveg, + "svfEaveg": svfEaveg, + "svfSaveg": svfSaveg, + "svfWaveg": svfWaveg, + "svfNaveg": svfNaveg, + "shmat": shmat, + "vegshmat": vegshmat, + "vbshvegshmat": vbshvegshmat, + "voxelIds": all_voxelId, + "voxelTable": voxelTable, + "walls": walls, + } + + # , + # 'vbshvegshmat': vbshvegshmat, 'wallshmat': wallshmat, 'wallsunmat': wallsunmat, + # 'wallshvemat': wallshvemat, 'facesunmat': facesunmat} + return svfresult + + +def svfForProcessing655( + dsm, + vegdem, + vegdem2, + scale, + usevegdem, + feedback, + device=torch.device("cpu"), +): + + with torch.no_grad(): + + dsm = _to_tensor(dsm, device) + vegdem = _to_tensor(vegdem, device) + vegdem2 = _to_tensor(vegdem2, device) + + rows = dsm.shape[0] + cols = dsm.shape[1] + svf = torch.zeros([rows, cols], device=device) + svfE = torch.zeros([rows, cols], device=device) + svfS = torch.zeros([rows, cols], device=device) + svfW = torch.zeros([rows, cols], device=device) + svfN = torch.zeros([rows, cols], device=device) + svfveg = torch.zeros((rows, cols), device=device) + svfEveg = torch.zeros((rows, cols), device=device) + svfSveg = torch.zeros((rows, cols), device=device) + svfWveg = torch.zeros((rows, cols), device=device) + svfNveg = torch.zeros((rows, cols), device=device) + svfaveg = torch.zeros((rows, cols), device=device) + svfEaveg = torch.zeros((rows, cols), device=device) + svfSaveg = torch.zeros((rows, cols), device=device) + svfWaveg = torch.zeros((rows, cols), device=device) + svfNaveg = torch.zeros((rows, cols), device=device) + + # % amaxvalue + vegmax = vegdem.max() + amaxvalue = dsm.max() + amaxvalue = torch.maximum(amaxvalue, vegmax) + + # % Elevation vegdems if buildingDSM inclused ground heights + vegdem = vegdem + dsm + vegdem[vegdem == dsm] = 0 + vegdem2 = vegdem2 + dsm + vegdem2[vegdem2 == dsm] = 0 + # % Bush separation + bush = torch.logical_not((vegdem2 * vegdem)) * vegdem + + noa = torch.tensor(19.0, device=device) + # % No. of anglesteps minus 1 + step = 89.0 / noa + iangle = torch.tensor( + torch.hstack((torch.arange(step / 2.0, 89.0, step), 90.0)), + device=device, + ) + annulino = torch.tensor( + torch.hstack((torch.round(torch.arange(0.0, 89.0, step)), 90.0)), + device=device, + ) + angleresult = svf_angles_100121(device) + aziinterval = angleresult["aziinterval"].to(device) + iazimuth = angleresult["iazimuth"].to(device) + aziintervalaniso = torch.ceil((aziinterval / 2.0)) + index = 1 + + for i in range(0, iangle.shape[0] - 1): + for j in range(0, int(aziinterval[int(i)].item())): + if feedback.isCanceled(): + feedback.setProgressText("Calculation cancelled") + break + altitude = float(iangle[int(i)].item()) + azimuth = float(iazimuth[int(index) - 1].item()) + + # Casting shadow + if usevegdem == 1: + shadowresult = shadow.shadowingfunction_20( + dsm, + vegdem, + vegdem2, + azimuth, + altitude, + scale, + amaxvalue, + bush, + feedback, + 1, + device, + ) + + vegsh = torch.tensor(shadowresult["vegsh"], device=device) + vbshvegsh = torch.tensor( + shadowresult["vbshvegsh"], device=device + ) + sh = torch.tensor(shadowresult["sh"], device=device) + else: + sh = shadow.shadowingfunctionglobalradiation( + dsm, azimuth, altitude, scale, feedback, 1, device + ) + + # Calculate svfs + for k in range( + int(annulino[int(i)].item()) + 1, + int(annulino[int(i + 1.0)].item()) + 1, + ): + weight = annulus_weight(k, aziinterval[i], device) * sh + svf = svf + weight + weight = annulus_weight(k, aziintervalaniso[i], device) * sh + if (azimuth >= 0) and (azimuth < 180): + svfE = svfE + weight + if (azimuth >= 90) and (azimuth < 270): + svfS = svfS + weight + if (azimuth >= 180) and (azimuth < 360): + svfW = svfW + weight + if (azimuth >= 270) or (azimuth < 90): + svfN = svfN + weight + + if usevegdem == 1: + for k in torch.arange( + annulino[int(i)] + 1, (annulino[int(i + 1.0)]) + 1 + ): + # % changed to include 90 + weight = annulus_weight(k, aziinterval[i], device) + svfveg = svfveg + weight * vegsh + svfaveg = svfaveg + weight * vbshvegsh + weight = annulus_weight(k, aziintervalaniso[i], device) + if (azimuth >= 0) and (azimuth < 180): + svfEveg = svfEveg + weight * vegsh + svfEaveg = svfEaveg + weight * vbshvegsh + if (azimuth >= 90) and (azimuth < 270): + svfSveg = svfSveg + weight * vegsh + svfSaveg = svfSaveg + weight * vbshvegsh + if (azimuth >= 180) and (azimuth < 360): + svfWveg = svfWveg + weight * vegsh + svfWaveg = svfWaveg + weight * vbshvegsh + if (azimuth >= 270) or (azimuth < 90): + svfNveg = svfNveg + weight * vegsh + svfNaveg = svfNaveg + weight * vbshvegsh + + index += 1 + feedback.setProgress(int(index * (100.0 / 655.0))) + + svfS = svfS + 3.0459e-004 + svfW = svfW + 3.0459e-004 + # % Last azimuth is 90. Hence, manual add of last annuli for svfS and SVFW + # %Forcing svf not be greater than 1 (some MATLAB crazyness) + svf[(svf > 1.0)] = 1.0 + svfE[(svfE > 1.0)] = 1.0 + svfS[(svfS > 1.0)] = 1.0 + svfW[(svfW > 1.0)] = 1.0 + svfN[(svfN > 1.0)] = 1.0 + + if usevegdem == 1: + last = torch.zeros((rows, cols), device=device) + last[(vegdem2 == 0.0)] = 3.0459e-004 + svfSveg = svfSveg + last + svfWveg = svfWveg + last + svfSaveg = svfSaveg + last + svfWaveg = svfWaveg + last + # %Forcing svf not be greater than 1 (some MATLAB crazyness) + svfveg[(svfveg > 1.0)] = 1.0 + svfEveg[(svfEveg > 1.0)] = 1.0 + svfSveg[(svfSveg > 1.0)] = 1.0 + svfWveg[(svfWveg > 1.0)] = 1.0 + svfNveg[(svfNveg > 1.0)] = 1.0 + svfaveg[(svfaveg > 1.0)] = 1.0 + svfEaveg[(svfEaveg > 1.0)] = 1.0 + svfSaveg[(svfSaveg > 1.0)] = 1.0 + svfWaveg[(svfWaveg > 1.0)] = 1.0 + svfNaveg[(svfNaveg > 1.0)] = 1.0 + + svfresult = { + "svf": svf, + "svfE": svfE, + "svfS": svfS, + "svfW": svfW, + "svfN": svfN, + "svfveg": svfveg, + "svfEveg": svfEveg, + "svfSveg": svfSveg, + "svfWveg": svfWveg, + "svfNveg": svfNveg, + "svfaveg": svfaveg, + "svfEaveg": svfEaveg, + "svfSaveg": svfSaveg, + "svfWaveg": svfWaveg, + "svfNaveg": svfNaveg, + } + return svfresult diff --git a/functions/wallalgorithms.py b/functions/wallalgorithms.py index 210914c..2db9812 100644 --- a/functions/wallalgorithms.py +++ b/functions/wallalgorithms.py @@ -4,53 +4,48 @@ __author__ = "xlinfr" import math -import torch -import torch.nn.functional as F +import numpy as np # import scipy.misc as sc import scipy.ndimage as sc from scipy.ndimage import maximum_filter -def findwalls_sp(arr_dsm, walllimit, device, footprint=None): - if isinstance(arr_dsm, torch.Tensor): - dsm_tensor = arr_dsm - else: - dsm_tensor = torch.tensor(arr_dsm, device=device) - - if footprint is None or footprint is False: - footprint = torch.tensor( - [[0, 1, 0], [1, 1, 1], [0, 1, 0]], device=device - ) - else: - footprint = footprint.to(device=device) +def findwalls_sp(arr_dsm, walllimit, footprint=False): + # This function identifies walls based on a DSM and a wall height limit. + # arr_dsm = DSM + # walllimit = wall height limit + # footprint = footprint for maximum filter, default = np.array([[0, 1, 0], + # [1, 0, 1], [0, 1, 0]]) - fh, fw = footprint.shape - pad_h, pad_w = fh // 2, fw // 2 - - padded_a = dsm_tensor.unsqueeze(0).unsqueeze(0) - padded_a = F.pad( - padded_a, pad=(pad_w, pad_w, pad_h, pad_h), mode="replicate" - ) - padded_a = padded_a.squeeze(0).squeeze(0) + # Get the shape of the input array + col, row = arr_dsm.shape + walls = np.zeros((col, row)) - max_neighbors = torch.full_like(dsm_tensor, float("-inf")) + # Create a padded version of the array + padded_a = np.pad(arr_dsm, pad_width=1, mode="edge") - y_indices, x_indices = torch.where(footprint == 1) + # Default footprint for cardinal points + if footprint is False: + footprint = np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) - H, W = dsm_tensor.shape - for dy, dx in zip(y_indices, x_indices): - shifted_view = padded_a[dy : dy + H, dx : dx + W] - max_neighbors = torch.maximum(max_neighbors, shifted_view) + # Use maximum_filter with the custom footprint + max_neighbors = maximum_filter( + padded_a, footprint=footprint, mode="constant", cval=0 + ) - walls = max_neighbors - dsm_tensor + # Identify wall pixels: walls are where the max neighbors are greater than + # the original DSM + walls = max_neighbors[1:-1, 1:-1] - arr_dsm + # Apply wall height limit walls[walls < walllimit] = 0 - walls[0, :] = 0 - walls[-1, :] = 0 - walls[:, 0] = 0 - walls[:, -1] = 0 + # Set the edges to zero + walls[0 : walls.shape[0], 0] = 0 + walls[0 : walls.shape[0], walls.shape[1] - 1] = 0 + walls[0, 0 : walls.shape[1]] = 0 + walls[walls.shape[0] - 1, 0 : walls.shape[1]] = 0 return walls @@ -63,23 +58,22 @@ def findwalls(a, walllimit, feedback, total): # fredrikl@gvc.gu.se # 20150625 - col, row = a.shape - walls = torch.zeros((col, row)) - domain = torch.tensor([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) + col = a.shape[0] + row = a.shape[1] + walls = np.zeros((col, row)) + domain = np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) index = 0 - for i in torch.arange(1, row - 1): + for i in np.arange(1, row - 1): if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") break - for j in torch.arange(1, col - 1): + for j in np.arange(1, col - 1): dom = a[j - 1 : j + 2, i - 1 : i + 2] - walls[j, i] = torch.max( - dom[torch.where(domain == 1)] - ) # new 20171006 + walls[j, i] = np.max(dom[np.where(domain == 1)]) # new 20171006 index = index + 1 feedback.setProgress(int(index * total)) - walls = torch.clone(walls - a) # new 20171006 + walls = np.copy(walls - a) # new 20171006 walls[(walls < walllimit)] = 0 walls[0 : walls.shape[0], 0] = 0 @@ -90,279 +84,131 @@ def findwalls(a, walllimit, feedback, total): return walls -def filter1Goodwin_as_aspect_v3( - walls_for_dir, scale, a, feedback, total, device, tile_size=2048 -): - """Applies the Goodwin et al. +def filter1Goodwin_as_aspect_v3(walls_for_dir, scale, a, feedback, total): + """ + tThis function applies the filter processing presented in Goodwin et al (2010) but instead for removing + linear fetures it calculates wall aspect based on a wall pixels grid, a dsm (a) and a scale factor - (2010) filter processing to calculate wall aspect based on a wall pixel - grid, a DSM (a), and a scale factor. + Fredrik Lindberg, 2012-02-14 + fredrikl@gvc.gu.se - Optimized for GPU using 2D Convolutions to eliminate nested pixel loops - and CPU-GPU transfer bottlenecks. + Translated: 2015-09-15 - :param walls_for_dir: Tensor (H, W), Binary grid of walls - :param scale: Float, Scale factor determining filter size - :param a: Tensor (H, W), Digital Surface Model (DSM) - :param feedback: Optional, QGIS/Processing feedback object for progress - :param total: Float, Progress step multiplier - :return: Tensor (H, W), Wall aspect directions + :param walls: + :param scale: + :param a: + :return: dirwalls """ - row, col = a.shape - # 1. Compute kernel footprint based on scale factor - filtersize = torch.floor((scale + 0.0000000001) * 9) + walls = walls_for_dir.copy() + + row = a.shape[0] + col = a.shape[1] + + filtersize = np.floor((scale + 0.0000000001) * 9) if filtersize <= 2: filtersize = 3 - elif filtersize != 9 and filtersize % 2 == 0: - filtersize = filtersize + 1 + else: + if filtersize != 9: + if filtersize % 2 == 0: + filtersize = filtersize + 1 - filtersize = int(filtersize) - filthalveceil = int(torch.ceil(torch.tensor(filtersize) / 2.0)) - filthalvefloor = int(torch.floor(torch.tensor(filtersize) / 2.0)) - n = filtersize - 1 + filthalveceil = int(np.ceil(filtersize / 2.0)) + filthalvefloor = int(np.floor(filtersize / 2.0)) - # Initialize base structural elements on CPU for rotation - filtmatrix = torch.zeros((filtersize, filtersize)) - buildfilt = torch.zeros((filtersize, filtersize)) + filtmatrix = np.zeros((int(filtersize), int(filtersize))) + buildfilt = np.zeros((int(filtersize), int(filtersize))) filtmatrix[:, filthalveceil - 1] = 1 + n = filtmatrix.shape[0] - 1 buildfilt[filthalveceil - 1, 0:filthalvefloor] = 1 - buildfilt[filthalveceil - 1, filthalveceil:filtersize] = 2 - - filtmatrix_list = [] - buildfilt1_list = [] - buildfilt2_list = [] - - # 2. Pre-calculate all 180 directional filters on CPU or GPU - with torch.no_grad(): - for h in range(180): - filtmatrix1temp = sc.rotate( - filtmatrix.numpy(), h, order=1, reshape=False, mode="nearest" - ) - filtmatrix1 = torch.round(torch.from_numpy(filtmatrix1temp)) - - filtmatrixbuildtemp = sc.rotate( - buildfilt.numpy(), h, order=0, reshape=False, mode="nearest" - ) - filtmatrixbuild = torch.round( - torch.from_numpy(filtmatrixbuildtemp) - ) - - index = 270 - h - if h in (150, 30): - filtmatrixbuild[:, n] = 0 - if index == 225: - filtmatrix1[0, 0] = 1 - filtmatrix1[n, n] = 1 - if index == 135: - filtmatrix1[0, n] = 1 - filtmatrix1[n, 0] = 1 - - filtmatrix_list.append(filtmatrix1.unsqueeze(0).unsqueeze(0)) - buildfilt1_list.append( - (filtmatrixbuild == 1).float().unsqueeze(0).unsqueeze(0) - ) - buildfilt2_list.append( - (filtmatrixbuild == 2).float().unsqueeze(0).unsqueeze(0) - ) - - # Stacking kernels on CPU first - Now perfectly defined! - all_kernels_walls = torch.cat(filtmatrix_list, dim=0) - all_kernels_dsm1 = torch.cat(buildfilt1_list, dim=0) - all_kernels_dsm2 = torch.cat(buildfilt2_list, dim=0) - - # 3. Setup global output allocation arrays - final_y = torch.zeros((row, col), device=device) - final_x = torch.zeros((row, col), device=device) - - walls_binary = (walls_for_dir > 0).float().to(device) - a_device = a.float().to(device) - - # Calculate total tiles for feedback tracking - num_tiles_y = math.ceil(row / tile_size) - num_tiles_x = math.ceil(col / tile_size) - total_tiles = num_tiles_y * num_tiles_x - tile_count = 0 - - # 4. Loop through spatial tiles - for r_start in range(0, row, tile_size): - r_end = min(r_start + tile_size, row) - - for c_start in range(0, col, tile_size): - c_end = min(c_start + tile_size, col) - - if feedback is not None and feedback.isCanceled(): - return final_y - - # Determine padded coordinates (the halo zone) - pad_top = min(r_start, filthalvefloor) - pad_bottom = min(row - r_end, filthalvefloor) - pad_left = min(c_start, filthalvefloor) - pad_right = min(col - c_end, filthalvefloor) - - # Slice tile out with padding included - tile_a = ( - a_device[ - (r_start - pad_top) : (r_end + pad_bottom), - (c_start - pad_left) : (c_end + pad_right), - ] - .unsqueeze(0) - .unsqueeze(0) - ) - - tile_walls = ( - walls_binary[ - (r_start - pad_top) : (r_end + pad_bottom), - (c_start - pad_left) : (c_end + pad_right), - ] - .unsqueeze(0) - .unsqueeze(0) - ) - - # Local allocation sizes for this specific tile (including pads) - tile_rows, tile_cols = tile_a.shape[2], tile_a.shape[3] - - # Running Maximum Setup for this tile - z_max = torch.full((tile_rows, tile_cols), -1.0, device=device) - h_best = torch.zeros( - (tile_rows, tile_cols), dtype=torch.long, device=device - ) - dsm_best1 = torch.zeros((tile_rows, tile_cols), device=device) - dsm_best2 = torch.zeros((tile_rows, tile_cols), device=device) - - # 5. Process angles in small chunks inside this single spatial tile - chunk_size = 10 - for idx in range(0, 180, chunk_size): - end_idx = min(idx + chunk_size, 180) - - k_walls = all_kernels_walls[idx:end_idx].to(device) - k_dsm1 = all_kernels_dsm1[idx:end_idx].to(device) - k_dsm2 = all_kernels_dsm2[idx:end_idx].to(device) - - # Notice: padding=0 because we manually padded our spatial tiles! - walls_conv = F.conv2d(tile_walls, k_walls, padding=0).squeeze( - 0 - ) - dsm_conv1 = F.conv2d(tile_a, k_dsm1, padding=0).squeeze(0) - dsm_conv2 = F.conv2d(tile_a, k_dsm2, padding=0).squeeze(0) - - # Account for spatial shrinkage since F.conv2d with padding=0 trims edges - c_rows, c_cols = walls_conv.shape[1], walls_conv.shape[2] - - # Align running arrays dynamically to conv output window - z_max_crop = z_max[ - filthalvefloor : filthalvefloor + c_rows, - filthalvefloor : filthalvefloor + c_cols, - ] - - chunk_max, chunk_h_local = torch.max(walls_conv, dim=0) - is_new_max = chunk_max >= z_max_crop - - if is_new_max.any(): - # Update local trackers - z_max[ - filthalvefloor : filthalvefloor + c_rows, - filthalvefloor : filthalvefloor + c_cols, - ] = torch.where(is_new_max, chunk_max, z_max_crop) - - chunk_h_absolute = chunk_h_local + idx - h_best_crop = h_best[ - filthalvefloor : filthalvefloor + c_rows, - filthalvefloor : filthalvefloor + c_cols, - ] - h_best[ - filthalvefloor : filthalvefloor + c_rows, - filthalvefloor : filthalvefloor + c_cols, - ] = torch.where(is_new_max, chunk_h_absolute, h_best_crop) - - h_local_unsqueeze = chunk_h_local.unsqueeze(0) - chunk_dsm1 = torch.gather( - dsm_conv1, dim=0, index=h_local_unsqueeze - ).squeeze(0) - chunk_dsm2 = torch.gather( - dsm_conv2, dim=0, index=h_local_unsqueeze - ).squeeze(0) - - dsm_best1_crop = dsm_best1[ - filthalvefloor : filthalvefloor + c_rows, - filthalvefloor : filthalvefloor + c_cols, - ] - dsm_best2_crop = dsm_best2[ - filthalvefloor : filthalvefloor + c_rows, - filthalvefloor : filthalvefloor + c_cols, + buildfilt[filthalveceil - 1, filthalveceil : int(filtersize)] = 2 + + y = np.zeros((row, col)) # final direction + z = np.zeros((row, col)) # temporary direction + x = np.zeros((row, col)) # building side + walls[walls > 0] = 1 + + for h in range( + 0, 180 + ): # =0:1:180 #%increased resolution to 1 deg 20140911 + if feedback is not None: + feedback.setProgress(int(h * total)) + if feedback.isCanceled(): + feedback.setProgressText("Calculation cancelled") + break + filtmatrix1temp = sc.rotate( + filtmatrix, h, order=1, reshape=False, mode="nearest" + ) # bilinear + filtmatrix1 = np.round(filtmatrix1temp) + # filtmatrix1temp = sc.imrotate(filtmatrix, h, 'bilinear') + # filtmatrix1 = np.round(filtmatrix1temp / 255.) + # filtmatrixbuildtemp = sc.imrotate(buildfilt, h, 'nearest') + filtmatrixbuildtemp = sc.rotate( + buildfilt, h, order=0, reshape=False, mode="nearest" + ) # Nearest neighbor + # filtmatrixbuild = np.round(filtmatrixbuildtemp / 127.) + filtmatrixbuild = np.round(filtmatrixbuildtemp) + index = 270 - h + if h == 150: + filtmatrixbuild[:, n] = 0 + if h == 30: + filtmatrixbuild[:, n] = 0 + if index == 225: + # n = filtmatrix.shape[0] - 1 # length(filtmatrix); + filtmatrix1[0, 0] = 1 + filtmatrix1[n, n] = 1 + if index == 135: + # n = filtmatrix.shape[0] - 1 # length(filtmatrix); + filtmatrix1[0, n] = 1 + filtmatrix1[n, 0] = 1 + + # i=filthalveceil:sizey-filthalveceil + for i in range(int(filthalveceil) - 1, row - int(filthalveceil) - 1): + # (j=filthalveceil:sizex-filthalveceil + for j in range( + int(filthalveceil) - 1, col - int(filthalveceil) - 1 + ): + if walls[i, j] == 1: + wallscut = ( + walls[ + i - filthalvefloor : i + filthalvefloor + 1, + j - filthalvefloor : j + filthalvefloor + 1, + ] + * filtmatrix1 + ) + dsmcut = a[ + i - filthalvefloor : i + filthalvefloor + 1, + j - filthalvefloor : j + filthalvefloor + 1, ] + if z[i, j] < wallscut.sum(): # sum(sum(wallscut)) + z[i, j] = wallscut.sum() # sum(sum(wallscut)); + if np.sum(dsmcut[filtmatrixbuild == 1]) > np.sum( + dsmcut[filtmatrixbuild == 2] + ): + x[i, j] = 1 + else: + x[i, j] = 2 + + y[i, j] = index + + y[(x == 1)] = y[(x == 1)] - 180 + y[(y < 0)] = y[(y < 0)] + 360 - dsm_best1[ - filthalvefloor : filthalvefloor + c_rows, - filthalvefloor : filthalvefloor + c_cols, - ] = torch.where(is_new_max, chunk_dsm1, dsm_best1_crop) - dsm_best2[ - filthalvefloor : filthalvefloor + c_rows, - filthalvefloor : filthalvefloor + c_cols, - ] = torch.where(is_new_max, chunk_dsm2, dsm_best2_crop) - - # Un-pad results to extract the pure valid window of this tile - tile_y = 270.0 - h_best.float() - tile_x = torch.where(dsm_best1 > dsm_best2, 1, 2) - - valid_tile_y = tile_y[ - pad_top : tile_rows - pad_bottom, - pad_left : tile_cols - pad_right, - ] - valid_tile_x = tile_x[ - pad_top : tile_rows - pad_bottom, - pad_left : tile_cols - pad_right, - ] - - # Write back cleanly into the global array without overlap seams - final_y[r_start:r_end, c_start:c_end] = valid_tile_y - final_x[r_start:r_end, c_start:c_end] = valid_tile_x - - # Progress handling - tile_count += 1 - if feedback is not None: - feedback.setProgress( - int((tile_count / total_tiles) * total * 0.9) - ) - - # 6. Global Post-processing calculations - border_mask = torch.zeros((row, col), dtype=torch.bool, device=device) - start = filthalveceil - 1 - end_row = row - filthalveceil - 1 - end_col = col - filthalveceil - 1 - border_mask[start:end_row, start:end_col] = True - - valid_mask = (walls_binary == 1) & border_mask - final_y = torch.where(valid_mask, final_y, torch.zeros_like(final_y)) - final_x = torch.where(valid_mask, final_x, torch.zeros_like(final_x)) - - final_y[final_x == 1] = final_y[final_x == 1] - 180 - final_y[final_y < 0] = final_y[final_y < 0] + 360 - - # Incorporate derivative fallback values for flat results grad, asp = get_ders(a, scale) - asp_device = ( - torch.from_numpy(asp).to(device) - if not isinstance(asp, torch.Tensor) - else asp.to(device) - ) - final_y = final_y + ((walls_binary == 1) * 1) * ((final_y == 0) * 1) * ( - asp_device / (math.pi / 180.0) - ) + y = y + ((walls == 1) * 1) * ((y == 0) * 1) * (asp / (math.pi / 180.0)) - if feedback is not None: - feedback.setProgress(int(total)) + dirwalls = y - return final_y + return dirwalls def cart2pol(x, y, units="deg"): - radius = torch.sqrt(x**2 + y**2) - theta = torch.arctan2(y, x) + radius = np.sqrt(x**2 + y**2) + theta = np.arctan2(y, x) if units in ["deg", "degs"]: - theta = theta * 180 / torch.pi + theta = theta * 180 / np.pi return theta, radius @@ -370,9 +216,9 @@ def get_ders(dsm, scale): # dem,_,_=read_dem_grid(dem_file) dx = 1 / scale # dx=0.5 - fy, fx = torch.gradient(dsm, spacing=dx) + fy, fx = np.gradient(dsm, dx, dx) asp, grad = cart2pol(fy, fx, "rad") - grad = torch.arctan(grad) + grad = np.arctan(grad) asp = asp * -1 - asp = asp + (asp < 0) * (torch.pi * 2) - return grad, asp + asp = asp + (asp < 0) * (np.pi * 2) + return grad, asp \ No newline at end of file diff --git a/functions/wallalgorithms_torch.py b/functions/wallalgorithms_torch.py new file mode 100644 index 0000000..5669a36 --- /dev/null +++ b/functions/wallalgorithms_torch.py @@ -0,0 +1,383 @@ +from builtins import range + +# -*- coding: utf-8 -*- +__author__ = "xlinfr" + +import math +try: + import torch + import torch.nn.functional as F +except: + pass + +# import scipy.misc as sc +import scipy.ndimage as sc +from scipy.ndimage import maximum_filter + + +def findwalls_sp(arr_dsm, walllimit, device, footprint=None): + if isinstance(arr_dsm, torch.Tensor): + dsm_tensor = arr_dsm + else: + dsm_tensor = torch.tensor(arr_dsm, device=device) + + if footprint is None or footprint is False: + footprint = torch.tensor( + [[0, 1, 0], [1, 1, 1], [0, 1, 0]], device=device + ) + else: + footprint = footprint.to(device=device) + + fh, fw = footprint.shape + pad_h, pad_w = fh // 2, fw // 2 + + padded_a = dsm_tensor.unsqueeze(0).unsqueeze(0) + padded_a = F.pad( + padded_a, pad=(pad_w, pad_w, pad_h, pad_h), mode="replicate" + ) + padded_a = padded_a.squeeze(0).squeeze(0) + + max_neighbors = torch.full_like(dsm_tensor, float("-inf")) + + y_indices, x_indices = torch.where(footprint == 1) + + H, W = dsm_tensor.shape + for dy, dx in zip(y_indices, x_indices): + shifted_view = padded_a[dy : dy + H, dx : dx + W] + max_neighbors = torch.maximum(max_neighbors, shifted_view) + + walls = max_neighbors - dsm_tensor + + walls[walls < walllimit] = 0 + + walls[0, :] = 0 + walls[-1, :] = 0 + walls[:, 0] = 0 + walls[:, -1] = 0 + + return walls + + +def findwalls(a, walllimit, feedback, total): + # This function identifies walls based on a DSM and a wall-height limit + # Walls are represented by outer pixels within building footprints + # + # Fredrik Lindberg, Goteborg Urban Climate Group + # fredrikl@gvc.gu.se + # 20150625 + + col, row = a.shape + walls = torch.zeros((col, row)) + domain = torch.tensor([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) + index = 0 + for i in torch.arange(1, row - 1): + if feedback.isCanceled(): + feedback.setProgressText("Calculation cancelled") + break + for j in torch.arange(1, col - 1): + dom = a[j - 1 : j + 2, i - 1 : i + 2] + walls[j, i] = torch.max( + dom[torch.where(domain == 1)] + ) # new 20171006 + index = index + 1 + feedback.setProgress(int(index * total)) + + walls = torch.clone(walls - a) # new 20171006 + walls[(walls < walllimit)] = 0 + + walls[0 : walls.shape[0], 0] = 0 + walls[0 : walls.shape[0], walls.shape[1] - 1] = 0 + walls[0, 0 : walls.shape[0]] = 0 + walls[walls.shape[0] - 1, 0 : walls.shape[1]] = 0 + + return walls + + +def filter1Goodwin_as_aspect_v3( + walls_for_dir, scale, a, feedback, total, device, tile_size=2048 +): + """Applies the Goodwin et al. + + (2010) filter processing to calculate wall aspect based on a wall pixel + grid, a DSM (a), and a scale factor. + + Optimized for GPU using 2D Convolutions to eliminate nested pixel loops + and CPU-GPU transfer bottlenecks. + + :param walls_for_dir: Tensor (H, W), Binary grid of walls + :param scale: Float, Scale factor determining filter size + :param a: Tensor (H, W), Digital Surface Model (DSM) + :param feedback: Optional, QGIS/Processing feedback object for progress + :param total: Float, Progress step multiplier + :return: Tensor (H, W), Wall aspect directions + """ + row, col = a.shape + + # 1. Compute kernel footprint based on scale factor + filtersize = torch.floor((scale + torch.tensor(0.0000000001, device=device)) * torch.tensor(9, device=device)) + if filtersize <= 2: + filtersize = 3 + elif filtersize != 9 and filtersize % 2 == 0: + filtersize = filtersize + 1 + + filtersize = int(filtersize) + filthalveceil = int(torch.ceil(torch.tensor(filtersize) / 2.0)) + filthalvefloor = int(torch.floor(torch.tensor(filtersize) / 2.0)) + n = filtersize - 1 + + # Initialize base structural elements on CPU for rotation + filtmatrix = torch.zeros((filtersize, filtersize)) + buildfilt = torch.zeros((filtersize, filtersize)) + + filtmatrix[:, filthalveceil - 1] = 1 + buildfilt[filthalveceil - 1, 0:filthalvefloor] = 1 + buildfilt[filthalveceil - 1, filthalveceil:filtersize] = 2 + + filtmatrix_list = [] + buildfilt1_list = [] + buildfilt2_list = [] + print("-0 device :" + str(device)) + + + # 2. Pre-calculate all 180 directional filters on CPU or GPU + with torch.no_grad(): + for h in range(180): + filtmatrix1temp = sc.rotate( + filtmatrix.numpy(), h, order=1, reshape=False, mode="nearest" + ) + filtmatrix1 = torch.round(torch.from_numpy(filtmatrix1temp)) + + filtmatrixbuildtemp = sc.rotate( + buildfilt.numpy(), h, order=0, reshape=False, mode="nearest" + ) + filtmatrixbuild = torch.round( + torch.from_numpy(filtmatrixbuildtemp) + ) + + index = 270 - h + if h in (150, 30): + filtmatrixbuild[:, n] = 0 + if index == 225: + filtmatrix1[0, 0] = 1 + filtmatrix1[n, n] = 1 + if index == 135: + filtmatrix1[0, n] = 1 + filtmatrix1[n, 0] = 1 + + filtmatrix_list.append(filtmatrix1.unsqueeze(0).unsqueeze(0)) + buildfilt1_list.append( + (filtmatrixbuild == 1).float().unsqueeze(0).unsqueeze(0) + ) + buildfilt2_list.append( + (filtmatrixbuild == 2).float().unsqueeze(0).unsqueeze(0) + ) + + # Stacking kernels on CPU first - Now perfectly defined! + all_kernels_walls = torch.cat(filtmatrix_list, dim=0) + all_kernels_dsm1 = torch.cat(buildfilt1_list, dim=0) + all_kernels_dsm2 = torch.cat(buildfilt2_list, dim=0) + + # 3. Setup global output allocation arrays + final_y = torch.zeros((row, col), device=device) + final_x = torch.zeros((row, col), device=device) + + walls_binary = (walls_for_dir > 0).float().to(device) + a_device = a.float().to(device) + + # Calculate total tiles for feedback tracking + num_tiles_y = math.ceil(row / tile_size) + num_tiles_x = math.ceil(col / tile_size) + total_tiles = num_tiles_y * num_tiles_x + tile_count = 0 + + # 4. Loop through spatial tiles + for r_start in range(0, row, tile_size): + r_end = min(r_start + tile_size, row) + + for c_start in range(0, col, tile_size): + c_end = min(c_start + tile_size, col) + + if feedback is not None and feedback.isCanceled(): + return final_y + + # Determine padded coordinates (the halo zone) + pad_top = min(r_start, filthalvefloor) + pad_bottom = min(row - r_end, filthalvefloor) + pad_left = min(c_start, filthalvefloor) + pad_right = min(col - c_end, filthalvefloor) + + # Slice tile out with padding included + tile_a = ( + a_device[ + (r_start - pad_top) : (r_end + pad_bottom), + (c_start - pad_left) : (c_end + pad_right), + ] + .unsqueeze(0) + .unsqueeze(0) + ) + + tile_walls = ( + walls_binary[ + (r_start - pad_top) : (r_end + pad_bottom), + (c_start - pad_left) : (c_end + pad_right), + ] + .unsqueeze(0) + .unsqueeze(0) + ) + + # Local allocation sizes for this specific tile (including pads) + tile_rows, tile_cols = tile_a.shape[2], tile_a.shape[3] + + # Running Maximum Setup for this tile + z_max = torch.full((tile_rows, tile_cols), -1.0, device=device) + h_best = torch.zeros( + (tile_rows, tile_cols), dtype=torch.long, device=device + ) + dsm_best1 = torch.zeros((tile_rows, tile_cols), device=device) + dsm_best2 = torch.zeros((tile_rows, tile_cols), device=device) + + # 5. Process angles in small chunks inside this single spatial tile + chunk_size = 10 + for idx in range(0, 180, chunk_size): + end_idx = min(idx + chunk_size, 180) + + k_walls = all_kernels_walls[idx:end_idx].to(device) + k_dsm1 = all_kernels_dsm1[idx:end_idx].to(device) + k_dsm2 = all_kernels_dsm2[idx:end_idx].to(device) + + # Notice: padding=0 because we manually padded our spatial tiles! + walls_conv = F.conv2d(tile_walls, k_walls, padding=0).squeeze( + 0 + ) + dsm_conv1 = F.conv2d(tile_a, k_dsm1, padding=0).squeeze(0) + dsm_conv2 = F.conv2d(tile_a, k_dsm2, padding=0).squeeze(0) + + # Account for spatial shrinkage since F.conv2d with padding=0 trims edges + c_rows, c_cols = walls_conv.shape[1], walls_conv.shape[2] + + # Align running arrays dynamically to conv output window + z_max_crop = z_max[ + filthalvefloor : filthalvefloor + c_rows, + filthalvefloor : filthalvefloor + c_cols, + ] + + chunk_max, chunk_h_local = torch.max(walls_conv, dim=0) + is_new_max = chunk_max >= z_max_crop + + if is_new_max.any(): + # Update local trackers + z_max[ + filthalvefloor : filthalvefloor + c_rows, + filthalvefloor : filthalvefloor + c_cols, + ] = torch.where(is_new_max, chunk_max, z_max_crop) + + chunk_h_absolute = chunk_h_local + idx + h_best_crop = h_best[ + filthalvefloor : filthalvefloor + c_rows, + filthalvefloor : filthalvefloor + c_cols, + ] + h_best[ + filthalvefloor : filthalvefloor + c_rows, + filthalvefloor : filthalvefloor + c_cols, + ] = torch.where(is_new_max, chunk_h_absolute, h_best_crop) + + h_local_unsqueeze = chunk_h_local.unsqueeze(0) + chunk_dsm1 = torch.gather( + dsm_conv1, dim=0, index=h_local_unsqueeze + ).squeeze(0) + chunk_dsm2 = torch.gather( + dsm_conv2, dim=0, index=h_local_unsqueeze + ).squeeze(0) + + dsm_best1_crop = dsm_best1[ + filthalvefloor : filthalvefloor + c_rows, + filthalvefloor : filthalvefloor + c_cols, + ] + dsm_best2_crop = dsm_best2[ + filthalvefloor : filthalvefloor + c_rows, + filthalvefloor : filthalvefloor + c_cols, + ] + + dsm_best1[ + filthalvefloor : filthalvefloor + c_rows, + filthalvefloor : filthalvefloor + c_cols, + ] = torch.where(is_new_max, chunk_dsm1, dsm_best1_crop) + dsm_best2[ + filthalvefloor : filthalvefloor + c_rows, + filthalvefloor : filthalvefloor + c_cols, + ] = torch.where(is_new_max, chunk_dsm2, dsm_best2_crop) + + # Un-pad results to extract the pure valid window of this tile + tile_y = 270.0 - h_best.float() + tile_x = torch.where(dsm_best1 > dsm_best2, 1, 2) + + valid_tile_y = tile_y[ + pad_top : tile_rows - pad_bottom, + pad_left : tile_cols - pad_right, + ] + valid_tile_x = tile_x[ + pad_top : tile_rows - pad_bottom, + pad_left : tile_cols - pad_right, + ] + + # Write back cleanly into the global array without overlap seams + final_y[r_start:r_end, c_start:c_end] = valid_tile_y + final_x[r_start:r_end, c_start:c_end] = valid_tile_x + + # Progress handling + tile_count += 1 + if feedback is not None: + feedback.setProgress( + int((tile_count / total_tiles) * total * 0.9) + ) + + # 6. Global Post-processing calculations + border_mask = torch.zeros((row, col), dtype=torch.bool, device=device) + start = filthalveceil - 1 + end_row = row - filthalveceil - 1 + end_col = col - filthalveceil - 1 + border_mask[start:end_row, start:end_col] = True + + valid_mask = (walls_binary == 1) & border_mask + final_y = torch.where(valid_mask, final_y, torch.zeros_like(final_y)) + final_x = torch.where(valid_mask, final_x, torch.zeros_like(final_x)) + + final_y[final_x == 1] = final_y[final_x == 1] - 180 + final_y[final_y < 0] = final_y[final_y < 0] + 360 + + # Incorporate derivative fallback values for flat results + grad, asp = get_ders(a, scale) + asp_device = ( + torch.from_numpy(asp).to(device) + if not isinstance(asp, torch.Tensor) + else asp.to(device) + ) + + final_y = final_y + ((walls_binary == 1) * 1) * ((final_y == 0) * 1) * ( + asp_device / (math.pi / 180.0) + ) + + if feedback is not None: + feedback.setProgress(int(total)) + + return final_y + + +def cart2pol(x, y, units="deg"): + radius = torch.sqrt(x**2 + y**2) + theta = torch.arctan2(y, x) + if units in ["deg", "degs"]: + theta = theta * 180 / torch.pi + return theta, radius + + +def get_ders(dsm, scale): + # dem,_,_=read_dem_grid(dem_file) + dx = 1 / scale + # dx=0.5 + fy, fx = torch.gradient(dsm, spacing=dx) + asp, grad = cart2pol(fy, fx, "rad") + grad = torch.arctan(grad) + asp = asp * -1 + asp = asp + (asp < 0) * (torch.pi * 2) + return grad, asp diff --git a/lc_update.py b/lc_update.py new file mode 100644 index 0000000..f642367 --- /dev/null +++ b/lc_update.py @@ -0,0 +1,17 @@ +import re +from PIL import Image +import rasterio +import numpy as np + +# Open the landcover file +lc_filepath = r'/home/lemap/Documents/suede/datasets/gotenburg/cut2/lc_cut.tif' +lc_filepathout = r'/home/lemap/Documents/suede/datasets/gotenburg/cut2/lc_cut_out.tif' +landcover = rasterio.open(lc_filepath) +crs = landcover.profile +lc_data = landcover.read() + +lc_data[lc_data == 4] = 2 +lc_data[lc_data == 3] = 2 + +new_lc = rasterio.open(lc_filepathout, 'w', **crs) +new_lc.write(lc_data) \ No newline at end of file diff --git a/preprocessor/skyviewfactor_algorithm.py b/preprocessor/skyviewfactor_algorithm.py index eccf9d2..5ca3c10 100644 --- a/preprocessor/skyviewfactor_algorithm.py +++ b/preprocessor/skyviewfactor_algorithm.py @@ -49,15 +49,20 @@ import os import numpy as np import inspect -import torch + +try: + import torch +except: + pass from pathlib import Path import zipfile import sys from ..util import misc +from ..functions import svf_functions_torch as svf_torch +from ..functions import svf_for_voxels_torch as svfv_torch from ..functions import svf_functions as svf from ..functions import svf_for_voxels as svfv - class ProcessingSkyViewFactorAlgorithm(QgsProcessingAlgorithm): """ This algorithm is a processing version of SkyViewFactor @@ -283,10 +288,23 @@ def processAlgorithm(self, parameters, context, feedback): if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): os.mkdir(outputDir) + + device = None + + if use_gpu: + # Check if torch is the fake version from torch_fallback.py then tells the + # user to install the real pip module + if type(torch).__name__ == "MetaMock" or hasattr(torch, "__getattr__"): + raise ImportError( + "\n[UMEP Error] PyTorch is required to run gpu mode.\n" + "Please install it using: pip install torch in your venv or using osgeo4w" + ) + if torch.cuda.is_available(): + device = torch.device("cuda") + else: + device = torch.device("cpu") + - device = torch.device("cpu") - if use_gpu and torch.cuda.is_available(): - device = torch.device("cuda") provider = dsmlayer.dataProvider() filepath_dsm = str(provider.dataSourceUri()) @@ -304,7 +322,10 @@ def processAlgorithm(self, parameters, context, feedback): geotransform = gdal_dsm.GetGeoTransform() pixel_resolution = geotransform[1] - scale = torch.tensor(1 / pixel_resolution, device=device) + scale = 1 / pixel_resolution + + if use_gpu: + dsm = torch.from_numpy(dsm).to(device) if wallScheme: # Load DEM layer if calculating exact SVFs for wall surface temperature scheme @@ -377,15 +398,33 @@ def processAlgorithm(self, parameters, context, feedback): else: rows = dsm.shape[0] cols = dsm.shape[1] - vegdsm = torch.zeros([rows, cols], device=device) + vegdsm = np.zeros([rows, cols]) + if use_gpu: + vegdsm = torch.from_numpy(vegdsm).to(device) vegdsm2 = 0.0 usevegdem = 0 if aniso == 1: feedback.setProgressText("Calculating SVF using 153 iterations") - with torch.no_grad(): + + if use_gpu: + print("gpu version") - ret = svf.svfForProcessing153( + ret = svf_torch.svfForProcessing153( + dsm, + vegdsm, + vegdsm2, + scale, + usevegdem, + pixel_resolution, + wallScheme, + dem, + feedback, + device, + ) + else: + print("cpu version") + ret = svf.svfForProcessing153( dsm, vegdsm, vegdsm2, @@ -395,14 +434,15 @@ def processAlgorithm(self, parameters, context, feedback): wallScheme, dem, feedback, - device=device, ) else: feedback.setProgressText("Calculating SVF using 655 iterations") - with torch.no_grad(): + + if use_gpu: + print("gpu version") - ret = svf.svfForProcessing655( + ret = svf_torch.svfForProcessing655( dsm, vegdsm, vegdsm2, @@ -411,6 +451,16 @@ def processAlgorithm(self, parameters, context, feedback): feedback, device=device, ) + else: + print("cpu version") + ret = svf.svfForProcessing655( + dsm, + vegdsm, + vegdsm2, + scale, + usevegdem, + feedback, + ) # print('Time to finish first SVF calculation = ' + str(run_time)) if wallScheme == 1: @@ -430,14 +480,27 @@ def processAlgorithm(self, parameters, context, feedback): trans = transVeg / 100.0 svftotal = svfbu - (1 - svfveg) * (1 - trans) # Lägg till loop för att lägga till i tabellen - svf_array = torch.zeros((voxelTable.shape[0]), device=device) - svf_height_array = torch.zeros( - (voxelTable.shape[0]), device=device + svf_array = np.zeros((voxelTable.shape[0])) + svf_height_array = np.zeros( + (voxelTable.shape[0]) ) - svfbu_array = torch.zeros((voxelTable.shape[0]), device=device) - svfveg_array = torch.zeros((voxelTable.shape[0]), device=device) - svfaveg_array = torch.zeros((voxelTable.shape[0]), device=device) - voxel_y = torch.where(voxelTable[:, 1] == svf_height) + svfbu_array = np.zeros((voxelTable.shape[0])) + svfveg_array = np.zeros((voxelTable.shape[0])) + svfaveg_array = np.zeros((voxelTable.shape[0])) + + if use_gpu: + svf_array = torch.from_numpy(svf_array).to(device) + svf_height_array = torch.from_numpy(svf_height_array).to(device) + svfbu_array = torch.from_numpy(svfbu_array).to(device) + svfveg_array = torch.from_numpy(svfveg_array).to(device) + svfaveg_array = torch.from_numpy(svfaveg_array).to(device) + + voxel_y = None + if use_gpu: + voxel_y = torch.where(voxelTable[:, 2] != 0) + else: + voxel_y = np.where(voxelTable[:, 2] != 0) + for temp_y in voxel_y[0]: svf_array[temp_y] = svftotal[ int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) @@ -454,9 +517,11 @@ def processAlgorithm(self, parameters, context, feedback): svf_height_array[temp_y] = svf_height if kmeans: - with torch.no_grad(): + + if use_gpu: + print("gpu version") - voxelTable, cluster_heights = svfv.svf_kmeans( + voxelTable, cluster_heights = svfv_torch.svf_kmeans( dsm, dem, vegdsm, @@ -478,9 +543,36 @@ def processAlgorithm(self, parameters, context, feedback): device=device, ) + # Interpolate for voxels where SVF has not been calculated + voxelTable = svfv_torch.interpolate_svf( + voxelTable + ) + else: + + voxelTable, cluster_heights = svfv.svf_kmeans( + dsm, + dem, + vegdsm, + vegdsm2, + wallHeights, + transVeg, + scale, + usevegdem, + pixel_resolution, + voxelTable, + clusters, + svf_height, + svf_array, + svfbu_array, + svfveg_array, + svfaveg_array, + svf_height_array, + feedback, + ) + # Interpolate for voxels where SVF has not been calculated voxelTable = svfv.interpolate_svf( - voxelTable, cluster_heights, kmeans + voxelTable ) # Loop for exact SVF at heights (increase DEM) @@ -490,9 +582,9 @@ def processAlgorithm(self, parameters, context, feedback): "Calculating SVF for wall surface temperature parameterization" ) - with torch.no_grad(): - - voxelTable = svfv.svf_for_voxels( + if use_gpu: + print("gpu version") + voxelTable = svfv_torch.svf_for_voxels( dsm, dem, vegdsm, @@ -511,7 +603,27 @@ def processAlgorithm(self, parameters, context, feedback): feedback, device=device, ) - + else: + print("cpu version") + voxelTable = svfv.svf_for_voxels( + dsm, + dem, + vegdsm, + vegdsm2, + transVeg, + scale, + usevegdem, + pixel_resolution, + voxelTable, + svf_height, + svf_array, + svfbu_array, + svfveg_array, + svfaveg_array, + svf_height_array, + feedback, + ) + # Remove rows where svfbu, sfveg and svfaveg is zero if usevegdem == 1: voxelTable = voxelTable[ @@ -542,32 +654,61 @@ def processAlgorithm(self, parameters, context, feedback): svfbuS = ret["svfS"] svfbuW = ret["svfW"] svfbuN = ret["svfN"] + + if use_gpu: - misc.saveraster( - gdal_dsm, - outputDir + "/" + "svf.tif", - svfbu.cpu().detach().numpy(), - ) - misc.saveraster( - gdal_dsm, - outputDir + "/" + "svfE.tif", - svfbuE.cpu().detach().numpy(), - ) - misc.saveraster( - gdal_dsm, - outputDir + "/" + "svfS.tif", - svfbuS.cpu().detach().numpy(), - ) - misc.saveraster( - gdal_dsm, - outputDir + "/" + "svfW.tif", - svfbuW.cpu().detach().numpy(), - ) - misc.saveraster( - gdal_dsm, - outputDir + "/" + "svfN.tif", - svfbuN.cpu().detach().numpy(), - ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svf.tif", + svfbu.cpu().detach().numpy(), + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfE.tif", + svfbuE.cpu().detach().numpy(), + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfS.tif", + svfbuS.cpu().detach().numpy(), + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfW.tif", + svfbuW.cpu().detach().numpy(), + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfN.tif", + svfbuN.cpu().detach().numpy(), + ) + + else: + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svf.tif", + svfbu, + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfE.tif", + svfbuE, + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfS.tif", + svfbuS, + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfW.tif", + svfbuW, + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfN.tif", + svfbuN, + ) if os.path.isfile(outputDir + "/" + "svfs.zip"): os.remove(outputDir + "/" + "svfs.zip") @@ -600,57 +741,110 @@ def processAlgorithm(self, parameters, context, feedback): svfSaveg = ret["svfSaveg"] svfWaveg = ret["svfWaveg"] svfNaveg = ret["svfNaveg"] + + if use_gpu: - misc.saveraster( - gdal_dsm, - outputDir + "/" + "svfveg.tif", - svfveg.cpu().detach().numpy(), - ) - misc.saveraster( - gdal_dsm, - outputDir + "/" + "svfEveg.tif", - svfEveg.cpu().detach().numpy(), - ) - misc.saveraster( - gdal_dsm, - outputDir + "/" + "svfSveg.tif", - svfSveg.cpu().detach().numpy(), - ) - misc.saveraster( - gdal_dsm, - outputDir + "/" + "svfWveg.tif", - svfWveg.cpu().detach().numpy(), - ) - misc.saveraster( - gdal_dsm, - outputDir + "/" + "svfNveg.tif", - svfNveg.cpu().detach().numpy(), - ) - misc.saveraster( - gdal_dsm, - outputDir + "/" + "svfaveg.tif", - svfaveg.cpu().detach().numpy(), - ) - misc.saveraster( - gdal_dsm, - outputDir + "/" + "svfEaveg.tif", - svfEaveg.cpu().detach().numpy(), - ) - misc.saveraster( - gdal_dsm, - outputDir + "/" + "svfSaveg.tif", - svfSaveg.cpu().detach().numpy(), - ) - misc.saveraster( - gdal_dsm, - outputDir + "/" + "svfWaveg.tif", - svfWaveg.cpu().detach().numpy(), - ) - misc.saveraster( - gdal_dsm, - outputDir + "/" + "svfNaveg.tif", - svfNaveg.cpu().detach().numpy(), - ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfveg.tif", + svfveg.cpu().detach().numpy(), + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfEveg.tif", + svfEveg.cpu().detach().numpy(), + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfSveg.tif", + svfSveg.cpu().detach().numpy(), + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfWveg.tif", + svfWveg.cpu().detach().numpy(), + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfNveg.tif", + svfNveg.cpu().detach().numpy(), + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfaveg.tif", + svfaveg.cpu().detach().numpy(), + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfEaveg.tif", + svfEaveg.cpu().detach().numpy(), + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfSaveg.tif", + svfSaveg.cpu().detach().numpy(), + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfWaveg.tif", + svfWaveg.cpu().detach().numpy(), + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfNaveg.tif", + svfNaveg.cpu().detach().numpy(), + ) + else: + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfveg.tif", + svfveg, + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfEveg.tif", + svfEveg, + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfSveg.tif", + svfSveg, + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfWveg.tif", + svfWveg, + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfNveg.tif", + svfNveg, + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfaveg.tif", + svfaveg, + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfEaveg.tif", + svfEaveg, + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfSaveg.tif", + svfSaveg, + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfWaveg.tif", + svfWaveg, + ) + misc.saveraster( + gdal_dsm, + outputDir + "/" + "svfNaveg.tif", + svfNaveg, + ) zippo = zipfile.ZipFile(outputDir + "/" + "svfs.zip", "a") zippo.write(outputDir + "/" + "svfveg.tif", "svfveg.tif") @@ -679,38 +873,53 @@ def processAlgorithm(self, parameters, context, feedback): trans = transVeg / 100.0 svftotal = svfbu - (1 - svfveg) * (1 - trans) - misc.saveraster( - gdal_dsm, filename, svftotal.cpu().detach().numpy() - ) + if use_gpu: + misc.saveraster( + gdal_dsm, filename, svftotal.cpu().detach().numpy() + ) + else: + misc.saveraster( + gdal_dsm, filename, svftotal + ) # Save shadow images for SOLWEIG 2019a if aniso == 1: shmat = ret["shmat"] vegshmat = ret["vegshmat"] vbshvegshmat = ret["vbshvegshmat"] - # wallshmat = ret["wallshmat"] - # wallsunmat = ret["wallsunmat"] - # wallshvemat = ret["wallshvemat"] - # facesunmat = ret["facesunmat"] - - np.savez_compressed( - outputDir + "/" + "shadowmats.npz", - shadowmat=shmat.cpu().detach().numpy(), - vegshadowmat=vegshmat.cpu().detach().numpy(), - vbshmat=vbshvegshmat.cpu().detach().numpy(), - ) # , - # vbshvegshmat=vbshvegshmat, wallshmat=wallshmat, wallsunmat=wallsunmat, - # facesunmat=facesunmat, wallshvemat=wallshvemat) + + if use_gpu: + np.savez_compressed( + outputDir + "/" + "shadowmats.npz", + shadowmat=shmat.cpu().detach().numpy(), + vegshadowmat=vegshmat.cpu().detach().numpy(), + vbshmat=vbshvegshmat.cpu().detach().numpy(), + ) # , + else: + np.savez_compressed( + outputDir + "/" + "shadowmats.npz", + shadowmat=shmat, + vegshadowmat=vegshmat, + vbshmat=vbshvegshmat, + ) # , + if wallScheme == 1: voxelId = ret["voxelIds"] voxelTable = ret["voxelTable"] - np.savez_compressed( - outputDir + "/" + "wallScheme.npz", - voxelId=voxelId.cpu().detach().numpy(), - voxelTable=voxelTable.cpu().detach().numpy(), - ) + if use_gpu: + np.savez_compressed( + outputDir + "/" + "wallScheme.npz", + voxelId=voxelId.cpu().detach().numpy(), + voxelTable=voxelTable.cpu().detach().numpy(), + ) + else: + np.savez_compressed( + outputDir + "/" + "wallScheme.npz", + voxelId=voxelId, + voxelTable=voxelTable, + ) feedback.setProgressText( "Sky View Factor: SVF grid(s) successfully generated" diff --git a/preprocessor/wall_heightaspect_algorithm.py b/preprocessor/wall_heightaspect_algorithm.py index 4f7472e..0543938 100644 --- a/preprocessor/wall_heightaspect_algorithm.py +++ b/preprocessor/wall_heightaspect_algorithm.py @@ -41,9 +41,15 @@ from osgeo import gdal from osgeo.gdalconst import * -import torch + +try: + import torch +except: + pass import os +from ..functions import wallalgorithms_torch as wa_torch from ..functions import wallalgorithms as wa + from qgis.PyQt.QtGui import QIcon import inspect from pathlib import Path @@ -106,8 +112,7 @@ def initAlgorithm(self, config): ) def processAlgorithm(self, parameters, context, feedback): - torch.set_num_threads(max(1, os.cpu_count())) - torch.set_num_interop_threads(max(1, os.cpu_count())) + outputFileHeight = self.parameterAsOutputLayer( parameters, self.OUTPUT_HEIGHT, context ) @@ -126,13 +131,32 @@ def processAlgorithm(self, parameters, context, feedback): ) feedback.setProgressText(str(cmd_folder)) feedback.setProgressText(str(cmd_folder.parent)) - - device = torch.device("cpu") - if use_gpu and torch.cuda.is_available(): - device = torch.device("cuda") - feedback.setProgressText( - "GPU detected and will be used for calculations." + device = None + if use_gpu: + + # Check if torch is the fake version from torch_fallback.py then tells the + # user to install the real pip module + if type(torch).__name__ == "MetaMock" or hasattr(torch, "__getattr__"): + raise ImportError( + "\n[UMEP Error] PyTorch is required to run gpu mode.\n" + "Please install it using: pip install torch in your venv or using osgeo4w" ) + try: + torch.set_num_threads(max(1, os.cpu_count())) + torch.set_num_interop_threads(max(1, os.cpu_count())) + except: + pass + + if torch.cuda.is_available(): + device = torch.device("cuda") + feedback.setProgressText( + "GPU detected and will be used for calculations." + ) + else: + device = torch.device("cpu") + feedback.setProgressText( + "No GPU detected.CPU will be used for calculations." + ) provider = dsmin.dataProvider() filepath_dsm = str(provider.dataSourceUri()) @@ -141,29 +165,60 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText("Calculating wall height") total = 100.0 / (int(dsm.shape[0] * dsm.shape[1])) - walls = wa.findwalls_sp(dsm, walllimit, device, False) + + if use_gpu: + walls = wa_torch.findwalls_sp(dsm, walllimit, device, False) + else: + walls = wa.findwalls_sp(dsm, walllimit, False) wallssave = walls - # feedback.setProgressText(outputFileHeight) + + if use_gpu: + wallssave = wallssave.cpu().detach().numpy() saverasternd( - gdal_dsm, outputFileHeight, wallssave.cpu().detach().numpy() + gdal_dsm, outputFileHeight, wallssave ) if outputFileAspect: total = 100.0 / 180.0 # outputFileAspect = self.parameterAsOutputLayer(parameters, self.OUTPUT_ASPECT, context) feedback.setProgressText("Calculating wall aspect") - dirwalls = wa.filter1Goodwin_as_aspect_v3( - walls, - torch.tensor(1, device=device), - torch.tensor(dsm, device=device), - feedback, - torch.tensor(total, device=device), - device, - ) - saverasternd( - gdal_dsm, outputFileAspect, dirwalls.cpu().detach().numpy() - ) + if use_gpu: + dirwalls = wa_torch.filter1Goodwin_as_aspect_v3( + walls, + torch.tensor(1, device=device), + torch.tensor(dsm, device=device), + feedback, + torch.tensor(total, device=device), + device, + ) + + if use_gpu: + dirwalls = dirwalls.cpu().detach().numpy() + saverasternd( + gdal_dsm, outputFileAspect, dirwalls + ) + else: + + if use_gpu: + dirwalls = wa_torch.filter1Goodwin_as_aspect_v3( + walls, + 1, + dsm, + feedback, + total, + ) + else: + dirwalls = wa.filter1Goodwin_as_aspect_v3( + walls, + 1, + dsm, + feedback, + total, + ) + saverasternd( + gdal_dsm, outputFileAspect, dirwalls + ) else: feedback.setProgressText("Wall aspect not calculated") diff --git a/processor/configsolweig.ini b/processor/configsolweig.ini index 96cd205..af24585 100644 --- a/processor/configsolweig.ini +++ b/processor/configsolweig.ini @@ -71,7 +71,7 @@ aniso=0 # use OHM for ground surface temperature modeling groundmodel=1 # compute the outgoing longwave rad using solid angles -outgoingLW=1 +outgoinglongwave=1 # use wall surface temperature scheme (Wallenberg et al. 2025, GMD) wallscheme=0 # If building materials is not included in lc, then this is used for all buildings (Wood, Brick or Concrete) diff --git a/processor/sebe_algorithm.py b/processor/sebe_algorithm.py index a402363..6be6c58 100644 --- a/processor/sebe_algorithm.py +++ b/processor/sebe_algorithm.py @@ -30,7 +30,6 @@ __revision__ = "$Format:%H$" - from qgis.PyQt.QtCore import QCoreApplication, QVariant from qgis.core import ( QgsProcessingAlgorithm, @@ -44,11 +43,8 @@ QgsProcessingException, QgsProcessingParameterRasterLayer, ) -from processing.gui.wrappers import WidgetWrapper import numpy as np -import torch from osgeo import gdal, osr -from osgeo.gdalconst import * import os from qgis.PyQt.QtGui import QIcon import inspect @@ -86,7 +82,6 @@ class ProcessingSEBEAlgorithm(QgsProcessingAlgorithm): IRR_FILE = "IRR_FILE" OUTPUT_DIR = "OUTPUT_DIR" OUTPUT_ROOF = "OUTPUT_ROOF" - USE_GPU = "USE8GPU" def initAlgorithm(self, config): self.addParameter( @@ -185,13 +180,6 @@ def initAlgorithm(self, config): defaultValue=False, ) ) - self.addParameter( - QgsProcessingParameterBoolean( - self.USE_GPU, - self.tr("Use GPU"), - defaultValue=False, - ) - ) self.addParameter( QgsProcessingParameterFileDestination( self.IRR_FILE, @@ -242,8 +230,6 @@ def processAlgorithm(self, parameters, context, feedback): albedo = self.parameterAsDouble(parameters, self.ALBEDO, context) inputMet = self.parameterAsString(parameters, self.INPUT_MET, context) saveskyirr = self.parameterAsBool(parameters, self.SAVESKYIRR, context) - use_gpu = self.parameterAsBool(parameters, self.USE_GPU, context) - irrFile = self.parameterAsFileOutput( parameters, self.IRR_FILE, context ) @@ -255,10 +241,6 @@ def processAlgorithm(self, parameters, context, feedback): if not (os.path.isdir(outputDir)): os.mkdir(outputDir) - device = torch.device("cpu") - if use_gpu and torch.cuda.is_available(): - device = torch.device("cuda") - provider = dsmlayer.dataProvider() filepath_dsm = str(provider.dataSourceUri()) self.gdal_dsm = gdal.Open(filepath_dsm) @@ -270,7 +252,7 @@ def processAlgorithm(self, parameters, context, feedback): nd = self.gdal_dsm.GetRasterBand(1).GetNoDataValue() self.dsm[self.dsm == nd] = 0.0 if self.dsm.min() < 0: - self.dsm = self.dsm + torch.abs(self.dsm.min()) + self.dsm = self.dsm + np.abs(self.dsm.min()) # response to issue #104 self.sorted_utclist @@ -326,9 +308,6 @@ def processAlgorithm(self, parameters, context, feedback): if vegdsm: usevegdem = 1 feedback.setProgressText("Vegetation scheme activated") - # vegdsm = self.parameterAsRasterLayer(parameters, self.INPUT_CDSM, context) - # if vegdsm is None: - # raise QgsProcessingException("Error: No valid vegetation DSM selected") # load raster gdal.AllRegister() @@ -346,10 +325,6 @@ def processAlgorithm(self, parameters, context, feedback): ) if vegdsm2: - # vegdsm2 = self.parameterAsRasterLayer(parameters, self.INPUT_TDSM, context) - - # if vegdsm2 is None: - # raise QgsProcessingException("Error: No valid Trunk zone DSM selected") # load raster gdal.AllRegister() @@ -378,8 +353,6 @@ def processAlgorithm(self, parameters, context, feedback): filePath_tdsm = None # wall height layer - # if whlayer is None: - # raise QgsProcessingException("Error: No valid wall height raster layer is selected") provider = whlayer.dataProvider() filepath_wh = str(provider.dataSourceUri()) self.gdal_wh = gdal.Open(filepath_wh) @@ -396,8 +369,6 @@ def processAlgorithm(self, parameters, context, feedback): )[1] # wall aspectlayer - # if walayer is None: - # raise QgsProcessingException("Error: No valid wall aspect raster layer is selected") provider = walayer.dataProvider() filepath_wa = str(provider.dataSourceUri()) self.gdal_wa = gdal.Open(filepath_wa) @@ -416,18 +387,17 @@ def processAlgorithm(self, parameters, context, feedback): delim = " " try: - self.metdata = torch.from_numpy( - np.loadtxt(inputMet, skiprows=headernum, delimiter=delim), - device=device, + self.metdata = np.loadtxt( + inputMet, skiprows=headernum, delimiter=delim ) - except: + except BaseException: QgsProcessingException( "Error: Make sure format of meteorological file is correct. You can" "prepare your data by using 'Prepare Existing Data' in " "the Pre-processor" ) - testwhere = torch.where( + testwhere = np.where( (self.metdata[:, 14] < 0.0) | (self.metdata[:, 14] > 1300.0) ) if testwhere[0].__len__() > 0: @@ -445,7 +415,7 @@ def processAlgorithm(self, parameters, context, feedback): "the Pre-processor" ) - alt = torch.median(self.dsm) + alt = np.median(self.dsm) if alt < 0: alt = 3 @@ -469,11 +439,10 @@ def processAlgorithm(self, parameters, context, feedback): albedo, location, zen, - device, ) if saveskyirr: - metout = torch.zeros((145, 4), device=device) + metout = np.zeros((145, 4)) metout[:, 0] = radmatI[:, 0] metout[:, 1] = radmatI[:, 1] metout[:, 2] = radmatI[:, 2] @@ -528,7 +497,6 @@ def processAlgorithm(self, parameters, context, feedback): usevegdem, feedback, wallmaxheight, - device, ) Energyyearroof = seberesult["Energyyearroof"] @@ -629,4 +597,4 @@ def icon(self): return icon def createInstance(self): - return ProcessingSEBEAlgorithm() + return ProcessingSEBEAlgorithm() \ No newline at end of file diff --git a/processor/solweig_algorithm.py b/processor/solweig_algorithm.py index aef489b..ab770c7 100644 --- a/processor/solweig_algorithm.py +++ b/processor/solweig_algorithm.py @@ -59,6 +59,7 @@ read_solweig_config, write_solweig_config, ) +from ..functions.SOLWEIGpython import Solweig_run_torch as sr_torch from ..functions.SOLWEIGpython import Solweig_run as sr import json @@ -89,6 +90,9 @@ class ProcessingSOLWEIGAlgorithm(QgsProcessingAlgorithm): INPUT_DEM = "INPUT_DEM" SAVE_BUILD = "SAVE_BUILD" INPUT_ANISO = "INPUT_ANISO" + USE_GROUNDSCHEME = "USE_GROUNDSCHEME" + USE_OUTGOINGLW = "USE_OUTGOINGLW" + INPUT_GROUNDSCHEME = "INPUT_GROUNDSCHEME" INPUT_WALLSCHEME = "INPUT_WALLSCHEME" WALLTEMP_NETCDF = "WALLTEMP_NETCDF" @@ -127,9 +131,6 @@ class ProcessingSOLWEIGAlgorithm(QgsProcessingAlgorithm): CYL = "CYL" USE_GPU = "USE_GPU" - # solweig - groundmodel = "groundmodel" - # Output OUTPUT_DIR = "OUTPUT_DIR" OUTPUT_TMRT = "OUTPUT_TMRT" @@ -271,7 +272,7 @@ def initAlgorithm(self, config): self.INPUT_DEM, self.tr("Digital Elevation Model (DEM)"), "", - optional=True, + optional=False, ) ) self.addParameter( @@ -292,6 +293,30 @@ def initAlgorithm(self, config): optional=True, ) ) + self.addParameter( + QgsProcessingParameterBoolean( + self.USE_GROUNDSCHEME, + self.tr("Use surface temperature parameterization v2026a"), + defaultValue=False, + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.USE_OUTGOINGLW, + self.tr("Use upwelling longwave radiation from v2026a"), + defaultValue=True, + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterFile( + self.INPUT_GROUNDSCHEME, + self.tr("Ground surface temperature data (.txt)"), + extension="txt", + optional=True, + ) + ) self.addParameter( QgsProcessingParameterFile( self.INPUT_WALLSCHEME, @@ -695,7 +720,15 @@ def processAlgorithm(self, parameters, context, feedback): walayer = self.parameterAsRasterLayer( parameters, self.INPUT_ASPECT, context ) - + useGroundScheme = self.parameterAsString( + parameters, self.USE_GROUNDSCHEME, context + ) + useOutgoingLW = self.parameterAsString( + parameters, self.USE_OUTGOINGLW, context + ) + groundTempFile = self.parameterAsString( + parameters, self.INPUT_GROUNDSCHEME, context + ) trunkr = self.parameterAsDouble( parameters, self.INPUT_THEIGHT, context ) @@ -1142,6 +1175,33 @@ def processAlgorithm(self, parameters, context, feedback): else: feedback.setProgressText("Isotropic sky") anisotropic_sky = 0 + + ### Ground cover scheme + # Surface temperature parameterization + if useGroundScheme: + groundscheme = 1 + feedback.setProgressText( + "The surface temperature parameterization from v2026 is activated" + ) + else: + groundscheme = 0 + feedback.setProgressText( + "The surface temperature scheme described in 2016 is activated" + ) + + # Outgoing longwave calculation + if useOutgoingLW: + feedback.setProgressText( + "The upwelling longwave radiation from v2026 is activated" + ) + outgoingLW = 1 + else: + feedback.setProgressText( + "The ground cover scheme described in 2016 is activated" + ) + outgoingLW = 0 + + # % Ts parameterisation maps if landcover == 1.0: @@ -1217,6 +1277,7 @@ def processAlgorithm(self, parameters, context, feedback): ), # 'C:\\Users\\xlinfr\\Desktop\\SOLWEIGdata\\shadowmats.npz', "poi_file": poi_file, "poi_field": poi_field, + "input_surf": groundTempFile, "input_wall": folderWallScheme, "woi_file": woi_file, "woi_field": woi_field, @@ -1237,8 +1298,8 @@ def processAlgorithm(self, parameters, context, feedback): "aniso": int(anisotropic_sky), "wallscheme": wallScheme, "walltype": wall_type, #'Brick_wall', #:TODO - "groundmodel": 1, - "input_surf": "", + "groundmodel": groundscheme, + "outgoingLW": outgoingLW, "outputtmrt": int(outputTmrt), "outputkup": int(outputKup), "outputkdown": int(outputKdown), @@ -1263,8 +1324,11 @@ def processAlgorithm(self, parameters, context, feedback): # Main function feedback.setProgressText("Executing main model") - - sr.solweig_run(outputDir + "/configsolweig.ini", feedback) + + if gpu_bool: + sr_torch.solweig_run(outputDir + "/configsolweig.ini", feedback) + else: + sr.solweig_run(outputDir + "/configsolweig.ini", feedback) feedback.setProgressText("SOLWEIG: Model calculation finished.") diff --git a/util/SEBESOLWEIGCommonFiles/Perez_v3.py b/util/SEBESOLWEIGCommonFiles/Perez_v3.py index e9ef8a2..bf0bd27 100644 --- a/util/SEBESOLWEIGCommonFiles/Perez_v3.py +++ b/util/SEBESOLWEIGCommonFiles/Perez_v3.py @@ -1,11 +1,9 @@ from __future__ import division +import numpy as np from ...util.SEBESOLWEIGCommonFiles.create_patches import create_patches -import torch -def Perez_v3( - zen, azimuth, radD, radI, jday, patchchoice, patch_option, device -): +def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice, patch_option): """ This function calculates distribution of luminance on the skyvault based on Perez luminince distribution model. @@ -80,54 +78,34 @@ def Perez_v3( :return: """ - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") - - m_a1 = torch.tensor( - [ - 1.3525, - -1.2219, - -1.1000, - -0.5484, - -0.6000, - -1.0156, - -1.0000, - -1.0500, - ], - device=device, + m_a1 = np.array( + [1.3525, -1.2219, -1.1000, -0.5484, -0.6000, -1.0156, -1.0000, -1.0500] ) - m_a2 = torch.tensor( - [-0.2576, -0.7730, -0.2515, -0.6654, -0.3566, -0.3670, 0.0211, 0.0289], - device=device, + m_a2 = np.array( + [-0.2576, -0.7730, -0.2515, -0.6654, -0.3566, -0.3670, 0.0211, 0.0289] ) - m_a3 = torch.tensor( - [-0.2690, 1.4148, 0.8952, -0.2672, -2.5000, 1.0078, 0.5025, 0.4260], - device=device, + m_a3 = np.array( + [-0.2690, 1.4148, 0.8952, -0.2672, -2.5000, 1.0078, 0.5025, 0.4260] ) - m_a4 = torch.tensor( - [-1.4366, 1.1016, 0.0156, 0.7117, 2.3250, 1.4051, -0.5119, 0.3590], - device=device, + m_a4 = np.array( + [-1.4366, 1.1016, 0.0156, 0.7117, 2.3250, 1.4051, -0.5119, 0.3590] ) - m_b1 = torch.tensor( - [-0.7670, -0.2054, 0.2782, 0.7234, 0.2937, 0.2875, -0.3000, -0.3250], - device=device, + m_b1 = np.array( + [-0.7670, -0.2054, 0.2782, 0.7234, 0.2937, 0.2875, -0.3000, -0.3250] ) - m_b2 = torch.tensor( - [0.0007, 0.0367, -0.1812, -0.6219, 0.0496, -0.5328, 0.1922, 0.1156], - device=device, + m_b2 = np.array( + [0.0007, 0.0367, -0.1812, -0.6219, 0.0496, -0.5328, 0.1922, 0.1156] ) - m_b3 = torch.tensor( - [1.2734, -3.9128, -4.5000, -5.6812, -5.6812, -3.8500, 0.7023, 0.7781], - device=device, + m_b3 = np.array( + [1.2734, -3.9128, -4.5000, -5.6812, -5.6812, -3.8500, 0.7023, 0.7781] ) - m_b4 = torch.tensor( - [-0.1233, 0.9156, 1.1766, 2.6297, 1.8415, 3.3750, -1.6317, 0.0025], - device=device, + m_b4 = np.array( + [-0.1233, 0.9156, 1.1766, 2.6297, 1.8415, 3.3750, -1.6317, 0.0025] ) - m_c1 = torch.tensor( - [2.8000, 6.9750, 24.7219, 33.3389, 21.0000, 14.0000, 19.0000, 31.0625], - device=device, + m_c1 = np.array( + [2.8000, 6.9750, 24.7219, 33.3389, 21.0000, 14.0000, 19.0000, 31.0625] ) - m_c2 = torch.tensor( + m_c2 = np.array( [ 0.6004, 0.1774, @@ -137,10 +115,9 @@ def Perez_v3( -0.9999, -5.0000, -14.5000, - ], - device=device, + ] ) - m_c3 = torch.tensor( + m_c3 = np.array( [ 1.2375, 6.4477, @@ -150,113 +127,108 @@ def Perez_v3( -7.1406, 1.2438, -46.1148, - ], - device=device, + ] ) - m_c4 = torch.tensor( - [1.0000, -0.1239, 34.8438, 52.0781, 7.2492, 7.5469, -1.9094, 55.3750], - device=device, + m_c4 = np.array( + [1.0000, -0.1239, 34.8438, 52.0781, 7.2492, 7.5469, -1.9094, 55.3750] ) - m_d1 = torch.tensor( - [ - 1.8734, - -1.5798, - -5.0000, - -3.5000, - -3.5000, - -3.4000, - -4.0000, - -7.2312, - ], - device=device, + m_d1 = np.array( + [1.8734, -1.5798, -5.0000, -3.5000, -3.5000, -3.4000, -4.0000, -7.2312] ) - m_d2 = torch.tensor( - [0.6297, -0.5081, 1.5218, 0.0016, -0.1554, -0.1078, 0.0250, 0.4050], - device=device, + m_d2 = np.array( + [0.6297, -0.5081, 1.5218, 0.0016, -0.1554, -0.1078, 0.0250, 0.4050] ) - m_d3 = torch.tensor( - [0.9738, -1.7812, 3.9229, 1.1477, 1.4062, -1.0750, 0.3844, 13.3500], - device=device, + m_d3 = np.array( + [0.9738, -1.7812, 3.9229, 1.1477, 1.4062, -1.0750, 0.3844, 13.3500] ) - m_d4 = torch.tensor( - [0.2809, 0.1080, -2.6204, 0.1062, 0.3988, 1.5702, 0.2656, 0.6234], - device=device, + m_d4 = np.array( + [0.2809, 0.1080, -2.6204, 0.1062, 0.3988, 1.5702, 0.2656, 0.6234] ) - m_e1 = torch.tensor( - [0.0356, 0.2624, -0.0156, 0.4659, 0.0032, -0.0672, 1.0468, 1.5000], - device=device, + m_e1 = np.array( + [0.0356, 0.2624, -0.0156, 0.4659, 0.0032, -0.0672, 1.0468, 1.5000] ) - m_e2 = torch.tensor( - [-0.1246, 0.0672, 0.1597, -0.3296, 0.0766, 0.4016, -0.3788, -0.6426], - device=device, + m_e2 = np.array( + [-0.1246, 0.0672, 0.1597, -0.3296, 0.0766, 0.4016, -0.3788, -0.6426] ) - m_e3 = torch.tensor( - [-0.5718, -0.2190, 0.4199, -0.0876, -0.0656, 0.3017, -2.4517, 1.8564], - device=device, + m_e3 = np.array( + [-0.5718, -0.2190, 0.4199, -0.0876, -0.0656, 0.3017, -2.4517, 1.8564] ) - m_e4 = torch.tensor( - [0.9938, -0.4285, -0.5562, -0.0329, -0.1294, -0.4844, 1.4656, 0.5636], - device=device, + m_e4 = np.array( + [0.9938, -0.4285, -0.5562, -0.0329, -0.1294, -0.4844, 1.4656, 0.5636] ) - acoeff = torch.stack([m_a1, m_a2, m_a3, m_a4], dim=1) - bcoeff = torch.stack([m_b1, m_b2, m_b3, m_b4], dim=1) - ccoeff = torch.stack([m_c1, m_c2, m_c3, m_c4], dim=1) - dcoeff = torch.stack([m_d1, m_d2, m_d3, m_d4], dim=1) - ecoeff = torch.stack([m_e1, m_e2, m_e3, m_e4], dim=1) + acoeff = np.transpose(np.atleast_2d([m_a1, m_a2, m_a3, m_a4])) + bcoeff = np.transpose(np.atleast_2d([m_b1, m_b2, m_b3, m_b4])) + ccoeff = np.transpose(np.atleast_2d([m_c1, m_c2, m_c3, m_c4])) + dcoeff = np.transpose(np.atleast_2d([m_d1, m_d2, m_d3, m_d4])) + ecoeff = np.transpose(np.atleast_2d([m_e1, m_e2, m_e3, m_e4])) - deg2rad = torch.tensor(torch.pi / 180, device=device).clone().detach() - rad2deg = torch.tensor(180 / torch.pi, device=device).clone().detach() + deg2rad = np.pi / 180 + rad2deg = 180 / np.pi altitude = 90 - zen - zen = torch.tensor(zen, device=device) * deg2rad - azimuth = torch.tensor(azimuth, device=device) * deg2rad - altitude = torch.tensor(altitude, device=device) * deg2rad + zen = zen * deg2rad + azimuth = azimuth * deg2rad + altitude = altitude * deg2rad Idh = radD + # Ibh = radI/sin(altitude) Ibn = radI - PerezClearness = ((Idh + Ibn) / (Idh + 1.041 * torch.pow(zen, 3))) / ( - 1 + 1.041 * torch.pow(zen, 3) + # Skyclearness + PerezClearness = ((Idh + Ibn) / (Idh + 1.041 * np.power(zen, 3))) / ( + 1 + 1.041 * np.power(zen, 3) ) - - day_angle = jday * 2 * torch.pi / 365 + # Extra terrestrial radiation + day_angle = jday * 2 * np.pi / 365 + # I0=1367*(1+0.033*np.cos((2*np.pi*jday)/365)) I0 = 1367 * ( 1.00011 - + 0.034221 * torch.cos(torch.tensor(day_angle)) - + 0.00128 * torch.sin(torch.tensor(day_angle)) - + 0.000719 * torch.cos(2 * torch.tensor(day_angle)) - + 0.000077 * torch.sin(2 * torch.tensor(day_angle)) + + 0.034221 * np.cos(day_angle) + + 0.00128 * np.sin(day_angle) + + 0.000719 * + # New from robinsson + np.cos(2 * day_angle) + + 0.000077 * np.sin(2 * day_angle) ) + # Optical air mass + # m=1/altitude; old if altitude >= 10 * deg2rad: - AirMass = 1 / torch.sin(altitude) - elif altitude < 0: - AirMass = 1 / torch.sin(altitude) + 0.50572 * torch.pow( - 180 * torch.complex(altitude, 0) / torch.pi + 6.07995, -1.6364 + AirMass = 1 / np.sin(altitude) + elif altitude < 0: # below equation becomes complex + AirMass = 1 / np.sin(altitude) + 0.50572 * np.power( + 180 * complex(altitude) / np.pi + 6.07995, -1.6364 ) else: - AirMass = 1 / torch.sin(altitude) + 0.50572 * torch.pow( - 180 * altitude / torch.pi + 6.07995, -1.6364 + AirMass = 1 / np.sin(altitude) + 0.50572 * np.power( + 180 * altitude / np.pi + 6.07995, -1.6364 ) - PerezBrightness = (AirMass * Idh) / I0 + # Skybrightness + # if altitude*rad2deg+6.07995>=0 + PerezBrightness = (AirMass * radD) / I0 if Idh <= 10: - PerezBrightness = torch.tensor(0.0, device=device) - + # m_a=0;m_b=0;m_c=0;m_d=0;m_e=0; + PerezBrightness = 0 + # if altitude < 0: + # print("Airmass") + # print(AirMass) + # print(PerezBrightness) + # sky clearness bins if PerezClearness < 1.065: intClearness = 0 - elif PerezClearness < 1.230: + if PerezClearness > 1.065 and PerezClearness < 1.230: intClearness = 1 - elif PerezClearness < 1.500: + if PerezClearness > 1.230 and PerezClearness < 1.500: intClearness = 2 - elif PerezClearness < 1.950: + if PerezClearness > 1.500 and PerezClearness < 1.950: intClearness = 3 - elif PerezClearness < 2.800: + if PerezClearness > 1.950 and PerezClearness < 2.800: intClearness = 4 - elif PerezClearness < 4.500: + if PerezClearness > 2.800 and PerezClearness < 4.500: intClearness = 5 - elif PerezClearness < 6.200: + if PerezClearness > 4.500 and PerezClearness < 6.200: intClearness = 6 - else: + if PerezClearness > 6.200: intClearness = 7 m_a = ( @@ -292,9 +264,10 @@ def Perez_v3( * (dcoeff[intClearness, 2] + dcoeff[intClearness, 3] * zen) ) else: + # different equations for c & d in clearness bin no. 1, from Robinsson m_c = ( - torch.exp( - torch.pow( + np.exp( + np.power( PerezBrightness * ( ccoeff[intClearness, 0] + ccoeff[intClearness, 1] * zen @@ -305,7 +278,7 @@ def Perez_v3( - 1 ) m_d = ( - -torch.exp( + -np.exp( PerezBrightness * (dcoeff[intClearness, 0] + dcoeff[intClearness, 1] * zen) ) @@ -313,39 +286,62 @@ def Perez_v3( + PerezBrightness * dcoeff[intClearness, 3] * PerezBrightness ) + # print 'a = ', m_a + # print 'b = ', m_b + # print 'e = ', m_e + # print 'c = ', m_c + # print 'd = ', m_d + if patchchoice == 2: - skyvaultalt = torch.zeros((90, 361), device=device) - skyvaultazi = torch.zeros((90, 361), device=device) + skyvaultalt = np.atleast_2d([]) + skyvaultazi = np.atleast_2d([]) + # Creating skyvault at one degree intervals + skyvaultalt = np.ones([90, 361]) * 90 + skyvaultazi = np.empty((90, 361)) for j in range(90): skyvaultalt[j, :] = 91 - j - skyvaultazi[j, :] = torch.arange(361) + skyvaultazi[j, :] = range(361) elif patchchoice == 1: - skyvaultalt, skyvaultazi, _, _, _, _, _ = create_patches( - patch_option, device, device - ) + # Creating skyvault of patches of constant radians (Tregeneza and + # Sharples, 1993) + skyvaultalt, skyvaultazi, _, _, _, _, _ = create_patches(patch_option) skyvaultzen = (90 - skyvaultalt) * deg2rad skyvaultalt = skyvaultalt * deg2rad skyvaultazi = skyvaultazi * deg2rad - cosSkySunAngle = torch.sin(skyvaultalt) * torch.sin(altitude) + torch.cos( + # Angular distance from the sun from Robinsson + cosSkySunAngle = np.sin(skyvaultalt) * np.sin(altitude) + np.cos( altitude - ) * torch.cos(skyvaultalt) * torch.cos(torch.abs(skyvaultazi - azimuth)) + ) * np.cos(skyvaultalt) * np.cos(np.abs(skyvaultazi - azimuth)) - lv = (1 + m_a * torch.exp(m_b / torch.cos(skyvaultzen))) * ( + # Main equation + lv = (1 + m_a * np.exp(m_b / np.cos(skyvaultzen))) * ( ( 1 - + m_c * torch.exp(m_d * torch.arccos(cosSkySunAngle)) + + m_c * np.exp(m_d * np.arccos(cosSkySunAngle)) + m_e * cosSkySunAngle * cosSkySunAngle ) ) - lv = lv / torch.sum(lv) + # Normalisation + lv = lv / np.sum(lv) + + # plotting + # axesm('stereo','Origin',[90 180],'MapLatLimit',[0 90],'Aspect','transverse') + # framem off; gridm on; mlabel off; plabel off;axis on; + # setm(gca,'MLabelParallel',-20) + # geoshow(skyvaultalt*rad2deg,skyvaultazi*rad2deg,lv,'DisplayType','texture'); + # colorbar + # set(gcf,'Color',[1 1 1]) + # pause(1) if patchchoice == 1: - x = torch.transpose(torch.unsqueeze(skyvaultalt * rad2deg, 0), 0, 1) - y = torch.transpose(torch.unsqueeze(skyvaultazi * rad2deg, 0), 0, 1) - z = torch.transpose(torch.unsqueeze(lv, 0), 0, 1) - lv = torch.cat((x, y, z), dim=1) + # x = np.atleast_2d([]) + # lv = np.transpose(np.append(np.append(np.append(x, skyvaultalt*rad2deg), skyvaultazi*rad2deg), lv)) + x = np.transpose(np.atleast_2d(skyvaultalt * rad2deg)) + y = np.transpose(np.atleast_2d(skyvaultazi * rad2deg)) + z = np.transpose(np.atleast_2d(lv)) + lv = np.append(np.append(x, y, axis=1), z, axis=1) return lv, PerezClearness, PerezBrightness diff --git a/util/SEBESOLWEIGCommonFiles/Perez_v3_torch.py b/util/SEBESOLWEIGCommonFiles/Perez_v3_torch.py new file mode 100644 index 0000000..3a2a27b --- /dev/null +++ b/util/SEBESOLWEIGCommonFiles/Perez_v3_torch.py @@ -0,0 +1,351 @@ +from __future__ import division +from .create_patches_torch import create_patches +try: + import torch +except: + pass + +def Perez_v3( + zen, azimuth, radD, radI, jday, patchchoice, patch_option, device +): + """ + This function calculates distribution of luminance on the skyvault based on + Perez luminince distribution model. + + Created by: + Fredrik Lindberg 20120527, fredrikl@gvc.gu.se + Gothenburg University, Sweden + Urban Climte Group + + Input parameters: + - zen: Zenith angle of the Sun (in degrees) + - azimuth: Azimuth angle of the Sun (in degrees) + - radD: Horizontal diffuse radiation (W m-2) + - radI: Direct radiation perpendicular to the Sun beam (W m-2) + - jday: Day of year + + Output parameters: + - lv: Relative luminance map (same dimensions as theta. gamma) + + + acoeff=[1.353 -0.258 -0.269 -1.437 + -1.222 -0.773 1.415 1.102 + -1.100 -0.252 0.895 0.016 + -0.585 -0.665 -0.267 0.712 + -0.600 -0.347 -2.500 2.323 + -1.016 -0.367 1.008 1.405 + -1.000 0.021 0.503 -0.512 + -1.050 0.029 0.426 0.359]; + + bcoeff=[-0.767 0.001 1.273 -0.123 + -0.205 0.037 -3.913 0.916 + 0.278 -0.181 -4.500 1.177 + 0.723 -0.622 -5.681 2.630 + 0.294 0.049 -5.681 1.842 + 0.288 -0.533 -3.850 3.375 + -0.300 0.192 0.702 -1.632 + -0.325 0.116 0.778 0.003]; + + ccoeff=[2.800 0.600 1.238 1.000 + 6.975 0.177 6.448 -0.124 + 24.22 -13.08 -37.70 34.84 + 33.34 -18.30 -62.25 52.08 + 21.00 -4.766 -21.59 7.249 + 14.00 -0.999 -7.14 7.547 + 19.00 -5.000 1.243 -1.91 + 31.06 -14.50 -46.11 55.37]; + + dcoeff=[1.874 0.630 0.974 0.281 + -1.580 -0.508 -1.781 0.108 + -5.00 1.522 3.923 -2.62 + -3.50 0.002 1.148 0.106 + -3.50 -0.155 1.406 0.399 + -3.40 -0.108 -1.075 1.57 + -4.00 0.025 0.384 0.266 + -7.23 0.405 13.35 0.623]; + + ecoeff=[0.035 -0.125 -0.572 0.994 + 0.262 0.067 -0.219 -0.428 + -0.016 0.160 0.420 -0.556 + 0.466 -0.33 -0.088 -0.033 + 0.003 0.077 -0.066 -0.129 + -0.067 0.402 0.302 -0.484 + 1.047 -0.379 -2.452 1.466 + 1.500 -0.643 1.856 0.564]; + + :param zen: + :param azimuth: + :param radD: + :param radI: + :param jday: + :param patchchoice: + :return: + """ + + m_a1 = torch.tensor( + [ + 1.3525, + -1.2219, + -1.1000, + -0.5484, + -0.6000, + -1.0156, + -1.0000, + -1.0500, + ], + device=device, + ) + m_a2 = torch.tensor( + [-0.2576, -0.7730, -0.2515, -0.6654, -0.3566, -0.3670, 0.0211, 0.0289], + device=device, + ) + m_a3 = torch.tensor( + [-0.2690, 1.4148, 0.8952, -0.2672, -2.5000, 1.0078, 0.5025, 0.4260], + device=device, + ) + m_a4 = torch.tensor( + [-1.4366, 1.1016, 0.0156, 0.7117, 2.3250, 1.4051, -0.5119, 0.3590], + device=device, + ) + m_b1 = torch.tensor( + [-0.7670, -0.2054, 0.2782, 0.7234, 0.2937, 0.2875, -0.3000, -0.3250], + device=device, + ) + m_b2 = torch.tensor( + [0.0007, 0.0367, -0.1812, -0.6219, 0.0496, -0.5328, 0.1922, 0.1156], + device=device, + ) + m_b3 = torch.tensor( + [1.2734, -3.9128, -4.5000, -5.6812, -5.6812, -3.8500, 0.7023, 0.7781], + device=device, + ) + m_b4 = torch.tensor( + [-0.1233, 0.9156, 1.1766, 2.6297, 1.8415, 3.3750, -1.6317, 0.0025], + device=device, + ) + m_c1 = torch.tensor( + [2.8000, 6.9750, 24.7219, 33.3389, 21.0000, 14.0000, 19.0000, 31.0625], + device=device, + ) + m_c2 = torch.tensor( + [ + 0.6004, + 0.1774, + -13.0812, + -18.3000, + -4.7656, + -0.9999, + -5.0000, + -14.5000, + ], + device=device, + ) + m_c3 = torch.tensor( + [ + 1.2375, + 6.4477, + -37.7000, + -62.2500, + -21.5906, + -7.1406, + 1.2438, + -46.1148, + ], + device=device, + ) + m_c4 = torch.tensor( + [1.0000, -0.1239, 34.8438, 52.0781, 7.2492, 7.5469, -1.9094, 55.3750], + device=device, + ) + m_d1 = torch.tensor( + [ + 1.8734, + -1.5798, + -5.0000, + -3.5000, + -3.5000, + -3.4000, + -4.0000, + -7.2312, + ], + device=device, + ) + m_d2 = torch.tensor( + [0.6297, -0.5081, 1.5218, 0.0016, -0.1554, -0.1078, 0.0250, 0.4050], + device=device, + ) + m_d3 = torch.tensor( + [0.9738, -1.7812, 3.9229, 1.1477, 1.4062, -1.0750, 0.3844, 13.3500], + device=device, + ) + m_d4 = torch.tensor( + [0.2809, 0.1080, -2.6204, 0.1062, 0.3988, 1.5702, 0.2656, 0.6234], + device=device, + ) + m_e1 = torch.tensor( + [0.0356, 0.2624, -0.0156, 0.4659, 0.0032, -0.0672, 1.0468, 1.5000], + device=device, + ) + m_e2 = torch.tensor( + [-0.1246, 0.0672, 0.1597, -0.3296, 0.0766, 0.4016, -0.3788, -0.6426], + device=device, + ) + m_e3 = torch.tensor( + [-0.5718, -0.2190, 0.4199, -0.0876, -0.0656, 0.3017, -2.4517, 1.8564], + device=device, + ) + m_e4 = torch.tensor( + [0.9938, -0.4285, -0.5562, -0.0329, -0.1294, -0.4844, 1.4656, 0.5636], + device=device, + ) + + acoeff = torch.stack([m_a1, m_a2, m_a3, m_a4], dim=1) + bcoeff = torch.stack([m_b1, m_b2, m_b3, m_b4], dim=1) + ccoeff = torch.stack([m_c1, m_c2, m_c3, m_c4], dim=1) + dcoeff = torch.stack([m_d1, m_d2, m_d3, m_d4], dim=1) + ecoeff = torch.stack([m_e1, m_e2, m_e3, m_e4], dim=1) + + deg2rad = torch.tensor(torch.pi / 180, device=device).clone().detach() + rad2deg = torch.tensor(180 / torch.pi, device=device).clone().detach() + altitude = 90 - zen + zen = torch.tensor(zen, device=device) * deg2rad + azimuth = torch.tensor(azimuth, device=device) * deg2rad + altitude = torch.tensor(altitude, device=device) * deg2rad + Idh = radD + Ibn = radI + + PerezClearness = ((Idh + Ibn) / (Idh + 1.041 * torch.pow(zen, 3))) / ( + 1 + 1.041 * torch.pow(zen, 3) + ) + + day_angle = jday * 2 * torch.pi / 365 + I0 = 1367 * ( + 1.00011 + + 0.034221 * torch.cos(torch.tensor(day_angle)) + + 0.00128 * torch.sin(torch.tensor(day_angle)) + + 0.000719 * torch.cos(2 * torch.tensor(day_angle)) + + 0.000077 * torch.sin(2 * torch.tensor(day_angle)) + ) + + if altitude >= 10 * deg2rad: + AirMass = 1 / torch.sin(altitude) + elif altitude < 0: + AirMass = 1 / torch.sin(altitude) + 0.50572 * torch.pow( + 180 * torch.complex(altitude, 0) / torch.pi + 6.07995, -1.6364 + ) + else: + AirMass = 1 / torch.sin(altitude) + 0.50572 * torch.pow( + 180 * altitude / torch.pi + 6.07995, -1.6364 + ) + + PerezBrightness = (AirMass * Idh) / I0 + if Idh <= 10: + PerezBrightness = torch.tensor(0.0, device=device) + + if PerezClearness < 1.065: + intClearness = 0 + elif PerezClearness < 1.230: + intClearness = 1 + elif PerezClearness < 1.500: + intClearness = 2 + elif PerezClearness < 1.950: + intClearness = 3 + elif PerezClearness < 2.800: + intClearness = 4 + elif PerezClearness < 4.500: + intClearness = 5 + elif PerezClearness < 6.200: + intClearness = 6 + else: + intClearness = 7 + + m_a = ( + acoeff[intClearness, 0] + + acoeff[intClearness, 1] * zen + + PerezBrightness + * (acoeff[intClearness, 2] + acoeff[intClearness, 3] * zen) + ) + m_b = ( + bcoeff[intClearness, 0] + + bcoeff[intClearness, 1] * zen + + PerezBrightness + * (bcoeff[intClearness, 2] + bcoeff[intClearness, 3] * zen) + ) + m_e = ( + ecoeff[intClearness, 0] + + ecoeff[intClearness, 1] * zen + + PerezBrightness + * (ecoeff[intClearness, 2] + ecoeff[intClearness, 3] * zen) + ) + + if intClearness > 0: + m_c = ( + ccoeff[intClearness, 0] + + ccoeff[intClearness, 1] * zen + + PerezBrightness + * (ccoeff[intClearness, 2] + ccoeff[intClearness, 3] * zen) + ) + m_d = ( + dcoeff[intClearness, 0] + + dcoeff[intClearness, 1] * zen + + PerezBrightness + * (dcoeff[intClearness, 2] + dcoeff[intClearness, 3] * zen) + ) + else: + m_c = ( + torch.exp( + torch.pow( + PerezBrightness + * ( + ccoeff[intClearness, 0] + ccoeff[intClearness, 1] * zen + ), + ccoeff[intClearness, 2], + ) + ) + - 1 + ) + m_d = ( + -torch.exp( + PerezBrightness + * (dcoeff[intClearness, 0] + dcoeff[intClearness, 1] * zen) + ) + + dcoeff[intClearness, 2] + + PerezBrightness * dcoeff[intClearness, 3] * PerezBrightness + ) + + if patchchoice == 2: + skyvaultalt = torch.zeros((90, 361), device=device) + skyvaultazi = torch.zeros((90, 361), device=device) + for j in range(90): + skyvaultalt[j, :] = 91 - j + skyvaultazi[j, :] = torch.arange(361) + + elif patchchoice == 1: + skyvaultalt, skyvaultazi, _, _, _, _, _ = create_patches( + patch_option, device + ) + + skyvaultzen = (90 - skyvaultalt) * deg2rad + skyvaultalt = skyvaultalt * deg2rad + skyvaultazi = skyvaultazi * deg2rad + + cosSkySunAngle = torch.sin(skyvaultalt) * torch.sin(altitude) + torch.cos( + altitude + ) * torch.cos(skyvaultalt) * torch.cos(torch.abs(skyvaultazi - azimuth)) + + lv = (1 + m_a * torch.exp(m_b / torch.cos(skyvaultzen))) * ( + ( + 1 + + m_c * torch.exp(m_d * torch.arccos(cosSkySunAngle)) + + m_e * cosSkySunAngle * cosSkySunAngle + ) + ) + + lv = lv / torch.sum(lv) + + if patchchoice == 1: + x = torch.transpose(torch.unsqueeze(skyvaultalt * rad2deg, 0), 0, 1) + y = torch.transpose(torch.unsqueeze(skyvaultazi * rad2deg, 0), 0, 1) + z = torch.transpose(torch.unsqueeze(lv, 0), 0, 1) + lv = torch.cat((x, y, z), dim=1) + return lv, PerezClearness, PerezBrightness diff --git a/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload.py b/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload.py index 83b72a9..57b3e2a 100644 --- a/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload.py +++ b/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload.py @@ -4,9 +4,9 @@ from . import sun_position as sp # import sun_position as sp +import numpy as np import datetime import calendar -import torch def Solweig_2015a_metdata_noload(inputdata, location, UTC): @@ -21,13 +21,8 @@ def Solweig_2015a_metdata_noload(inputdata, location, UTC): """ met = inputdata - device = ( - met.device if isinstance(met, torch.Tensor) else torch.device("cpu") - ) data_len = len(met[:, 0]) - dectime = torch.tensor( - met[:, 1] + met[:, 2] / 24 + met[:, 3] / (60 * 24.0) - ) + dectime = met[:, 1] + met[:, 2] / 24 + met[:, 3] / (60 * 24.0) dectimemin = met[:, 3] / (60 * 24.0) if data_len == 1: halftimestepdec = 0 @@ -41,13 +36,13 @@ def Solweig_2015a_metdata_noload(inputdata, location, UTC): leafoff1 = 300 # TODO this should change # initialize matrices - altitude = torch.empty(size=(1, data_len), device=device) - azimuth = torch.empty(size=(1, data_len), device=device) - zen = torch.empty(size=(1, data_len), device=device) - jday = torch.empty(size=(1, data_len), device=device) - YYYY = torch.empty(size=(1, data_len), device=device) - leafon = torch.empty(size=(1, data_len), device=device) - altmax = torch.empty(size=(1, data_len), device=device) + altitude = np.empty(shape=(1, data_len)) + azimuth = np.empty(shape=(1, data_len)) + zen = np.empty(shape=(1, data_len)) + jday = np.empty(shape=(1, data_len)) + YYYY = np.empty(shape=(1, data_len)) + leafon = np.empty(shape=(1, data_len)) + altmax = np.empty(shape=(1, data_len)) sunmax = dict() @@ -58,9 +53,7 @@ def Solweig_2015a_metdata_noload(inputdata, location, UTC): int(met[i, 1]) - 1 ) # Finding maximum altitude in 15 min intervals (20141027) - if (i == 0) or ( - torch.remainder(dectime[i], torch.floor(dectime[i])) == 0 - ): + if (i == 0) or (np.mod(dectime[i], np.floor(dectime[i])) == 0): fifteen = 0.0 sunmaximum = -90.0 sunmax["zenith"] = 90.0 @@ -77,9 +70,9 @@ def Solweig_2015a_metdata_noload(inputdata, location, UTC): sunmax = sp.sun_position(time, location) altmax[0, i] = sunmaximum - half = datetime.timedelta(days=float(halftimestepdec)) - H = datetime.timedelta(hours=int(met[i, 2])) - M = datetime.timedelta(minutes=int(met[i, 3])) + half = datetime.timedelta(days=halftimestepdec) + H = datetime.timedelta(hours=met[i, 2]) + M = datetime.timedelta(minutes=met[i, 3]) YMDHM = YMD + H + M - half time["year"] = YMDHM.year time["month"] = YMDHM.month @@ -87,32 +80,25 @@ def Solweig_2015a_metdata_noload(inputdata, location, UTC): time["hour"] = YMDHM.hour time["min"] = YMDHM.minute sun = sp.sun_position(time, location) - sun_zenith = sun["zenith"] - sun_azimuth = sun["azimuth"] - - if (sun_zenith > 89.0) & (sun_zenith <= 90.0): - sun_zenith = 89.0 - - altitude[0, i] = 90.0 - sun_zenith - zen[0, i] = sun_zenith * (torch.pi / 180.0) - azimuth[0, i] = sun_azimuth + # Hopefully fixes weird values in Perez et al. when altitude < 1.0, + # i.e. close to sunrise/sunset + if (sun["zenith"] > 89.0) & (sun["zenith"] <= 90.0): + sun["zenith"] = 89.0 + altitude[0, i] = 90.0 - sun["zenith"] + zen[0, i] = sun["zenith"] * (np.pi / 180.0) + azimuth[0, i] = sun["azimuth"] # day of year and check for leap year if calendar.isleap(time["year"]): - dayspermonth = torch.atleast_2d( - torch.tensor( - [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], - device=device, - ) + dayspermonth = np.atleast_2d( + [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] ) else: - dayspermonth = torch.atleast_2d( - torch.tensor( - [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], - device=device, - ) + dayspermonth = np.atleast_2d( + [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] ) - # jday[0, i] = torch.sum(dayspermonth[0, 0:time['month']-1]) + time['day'] # bug when a new day 20191015 + # jday[0, i] = np.sum(dayspermonth[0, 0:time['month']-1]) + time['day'] + # # bug when a new day 20191015 YYYY[0, i] = met[i, 0] doy = YMD.timetuple().tm_yday jday[0, i] = doy diff --git a/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload_torch.py b/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload_torch.py new file mode 100644 index 0000000..2ece78b --- /dev/null +++ b/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload_torch.py @@ -0,0 +1,126 @@ +from __future__ import absolute_import + +# from importdata import importdata +from . import sun_position_torch as sp + +# import sun_position as sp +import datetime +import calendar +try: + import torch +except: + pass + +def Solweig_2015a_metdata_noload(inputdata, location, UTC): + """ + This function is used to process the input meteorological file. + It also calculates Sun position based on the time specified in the met-file + + :param inputdata: + :param location: + :param UTC: + :return: + """ + + met = inputdata + device = ( + met.device if isinstance(met, torch.Tensor) else torch.device("cpu") + ) + data_len = len(met[:, 0]) + dectime = torch.tensor( + met[:, 1] + met[:, 2] / 24 + met[:, 3] / (60 * 24.0) + ) + dectimemin = met[:, 3] / (60 * 24.0) + if data_len == 1: + halftimestepdec = 0 + else: + halftimestepdec = (dectime[1] - dectime[0]) / 2.0 + time = dict() + time["sec"] = 0 + time["UTC"] = UTC + sunmaximum = 0.0 + leafon1 = 97 # TODO this should change + leafoff1 = 300 # TODO this should change + + # initialize matrices + altitude = torch.empty(size=(1, data_len), device=device) + azimuth = torch.empty(size=(1, data_len), device=device) + zen = torch.empty(size=(1, data_len), device=device) + jday = torch.empty(size=(1, data_len), device=device) + YYYY = torch.empty(size=(1, data_len), device=device) + leafon = torch.empty(size=(1, data_len), device=device) + altmax = torch.empty(size=(1, data_len), device=device) + + sunmax = dict() + + for i, row in enumerate(met[:, 0]): + if met[i, 1] == 221: + test = 4 + YMD = datetime.datetime(int(met[i, 0]), 1, 1) + datetime.timedelta( + int(met[i, 1]) - 1 + ) + # Finding maximum altitude in 15 min intervals (20141027) + if (i == 0) or ( + torch.remainder(dectime[i], torch.floor(dectime[i])) == 0 + ): + fifteen = 0.0 + sunmaximum = -90.0 + sunmax["zenith"] = 90.0 + while sunmaximum <= 90.0 - sunmax["zenith"]: + sunmaximum = 90.0 - sunmax["zenith"] + fifteen = fifteen + 15.0 / 1440.0 + HM = datetime.timedelta(days=(60 * 10) / 1440.0 + fifteen) + YMDHM = YMD + HM + time["year"] = YMDHM.year + time["month"] = YMDHM.month + time["day"] = YMDHM.day + time["hour"] = YMDHM.hour + time["min"] = YMDHM.minute + sunmax = sp.sun_position(time, location) + altmax[0, i] = sunmaximum + + half = datetime.timedelta(days=float(halftimestepdec)) + H = datetime.timedelta(hours=int(met[i, 2])) + M = datetime.timedelta(minutes=int(met[i, 3])) + YMDHM = YMD + H + M - half + time["year"] = YMDHM.year + time["month"] = YMDHM.month + time["day"] = YMDHM.day + time["hour"] = YMDHM.hour + time["min"] = YMDHM.minute + sun = sp.sun_position(time, location) + sun_zenith = sun["zenith"] + sun_azimuth = sun["azimuth"] + + if (sun_zenith > 89.0) & (sun_zenith <= 90.0): + sun_zenith = 89.0 + + altitude[0, i] = 90.0 - sun_zenith + zen[0, i] = sun_zenith * (torch.pi / 180.0) + azimuth[0, i] = sun_azimuth + + # day of year and check for leap year + if calendar.isleap(time["year"]): + dayspermonth = torch.atleast_2d( + torch.tensor( + [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], + device=device, + ) + ) + else: + dayspermonth = torch.atleast_2d( + torch.tensor( + [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], + device=device, + ) + ) + # jday[0, i] = torch.sum(dayspermonth[0, 0:time['month']-1]) + time['day'] # bug when a new day 20191015 + YYYY[0, i] = met[i, 0] + doy = YMD.timetuple().tm_yday + jday[0, i] = doy + if (doy > leafon1) | (doy < leafoff1): + leafon[0, i] = 1 + else: + leafon[0, i] = 0 + + return YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax diff --git a/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b.py b/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b.py index eca194f..575c88d 100644 --- a/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b.py +++ b/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b.py @@ -1,12 +1,10 @@ from __future__ import absolute_import +import math +import numpy as np +from . import sun_distance author = "xlinfr" -from . import sun_distance -import numpy as np -import math -import torch - def clearnessindex_2013b(zen, jday, Ta, RH, radG, location, P): """Clearness Index at the Earth's surface calculated from Crawford and Duchon 1999 @@ -27,17 +25,13 @@ def clearnessindex_2013b(zen, jday, Ta, RH, radG, location, P): p = P * 10.0 # Convert from hPa to millibars Itoa = 1370.0 # Effective solar constant - D = sun_distance.sun_distance( - jday - ) # irradiance differences due to Sun-Earth distances + # irradiance differences due to Sun-Earth distances + D = sun_distance.sun_distance(jday) m = ( - 35.0 - * torch.cos(zen) - * ((1224.0 * (torch.cos(zen) ** 2) + 1) ** (-1 / 2.0)) + 35.0 * np.cos(zen) * ((1224.0 * (np.cos(zen) ** 2) + 1) ** (-1 / 2.0)) ) # optical air mass at p=1013 - Trpg = ( - 1.021 - 0.084 * (m * (0.000949 * p + 0.051)) ** 0.5 - ) # Transmission coefficient for Rayliegh scattering and permanent gases + # Transmission coefficient for Rayliegh scattering and permanent gases + Trpg = 1.021 - 0.084 * (m * (0.000949 * p + 0.051)) ** 0.5 # empirical constant depending on latitude if location["latitude"] < 10.0: @@ -67,40 +61,34 @@ def clearnessindex_2013b(zen, jday, Ta, RH, radG, location, P): G = G[2] elif jday > 244 and jday <= 335: G = G[3] - device = ( - zen.device if isinstance(zen, torch.Tensor) else torch.device("cpu") - ) - G = torch.tensor(G, device=device) # dewpoint calculation a2 = 17.27 b2 = 237.7 - Td = (b2 * (((a2 * Ta) / (b2 + Ta)) + torch.log(RH))) / ( - a2 - (((a2 * Ta) / (b2 + Ta)) + torch.log(RH)) + Td = (b2 * (((a2 * Ta) / (b2 + Ta)) + np.log(RH))) / ( + a2 - (((a2 * Ta) / (b2 + Ta)) + np.log(RH)) ) Td = (Td * 1.8) + 32 # Dewpoint (F) - u = torch.exp( - 0.1133 - torch.log(G + 1) + 0.0393 * Td - ) # Precipitable water + u = np.exp(0.1133 - np.log(G + 1) + 0.0393 * Td) # Precipitable water Tw = 1 - 0.077 * ( (u * m) ** 0.3 ) # Transmission coefficient for water vapor Tar = 0.935**m # Transmission coefficient for aerosols - I0 = Itoa * torch.cos(zen) * Trpg * Tw * D * Tar - if abs(zen) > torch.pi / 2: + I0 = Itoa * np.cos(zen) * Trpg * Tw * D * Tar + if abs(zen) > np.pi / 2: I0 = 0 - # b=I0==abs(zen)>torch.pi/2 + # b=I0==abs(zen)>np.pi/2 # I0(b==1)=0 # clear b; - if not (torch.isreal(I0)): + if not (np.isreal(I0)): I0 = 0 - corr = 0.1473 * torch.log(90 - (zen / torch.pi * 180)) + 0.3454 # 20070329 + corr = 0.1473 * np.log(90 - (zen / np.pi * 180)) + 0.3454 # 20070329 CIuncorr = radG / I0 CI = CIuncorr + (1 - corr) - I0et = Itoa * torch.cos(zen) * D # extra terrestial solar radiation + I0et = Itoa * np.cos(zen) * D # extra terrestial solar radiation Kt = radG / I0et if math.isnan(CI): CI = float("Inf") diff --git a/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b_torch.py b/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b_torch.py new file mode 100644 index 0000000..49e9bb6 --- /dev/null +++ b/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b_torch.py @@ -0,0 +1,110 @@ +from __future__ import absolute_import + +author = "xlinfr" + +from . import sun_distance_torch +import math +try: + import torch +except: + pass + + +def clearnessindex_2013b(zen, jday, Ta, RH, radG, location, P): + """Clearness Index at the Earth's surface calculated from Crawford and Duchon 1999 + + :param zen: zenith angle in radians + :param jday: day of year + :param Ta: air temperature + :param RH: relative humidity + :param radG: global shortwave radiation + :param location: distionary including lat, lon and alt + :param P: pressure + :return: + """ + + if P == -999.0: + p = 1013.0 # Pressure in millibars + else: + p = P * 10.0 # Convert from hPa to millibars + + Itoa = 1370.0 # Effective solar constant + D = sun_distance_torch.sun_distance( + jday + ) # irradiance differences due to Sun-Earth distances + m = ( + 35.0 + * torch.cos(zen) + * ((1224.0 * (torch.cos(zen) ** 2) + 1) ** (-1 / 2.0)) + ) # optical air mass at p=1013 + Trpg = ( + 1.021 - 0.084 * (m * (0.000949 * p + 0.051)) ** 0.5 + ) # Transmission coefficient for Rayliegh scattering and permanent gases + + # empirical constant depending on latitude + if location["latitude"] < 10.0: + G = [3.37, 2.85, 2.80, 2.64] + elif location["latitude"] >= 10.0 and location["latitude"] < 20.0: + G = [2.99, 3.02, 2.70, 2.93] + elif location["latitude"] >= 20.0 and location["latitude"] < 30.0: + G = [3.60, 3.00, 2.98, 2.93] + elif location["latitude"] >= 30.0 and location["latitude"] < 40.0: + G = [3.04, 3.11, 2.92, 2.94] + elif location["latitude"] >= 40.0 and location["latitude"] < 50.0: + G = [2.70, 2.95, 2.77, 2.71] + elif location["latitude"] >= 50.0 and location["latitude"] < 60.0: + G = [2.52, 3.07, 2.67, 2.93] + elif location["latitude"] >= 60.0 and location["latitude"] < 70.0: + G = [1.76, 2.69, 2.61, 2.61] + elif location["latitude"] >= 70.0 and location["latitude"] < 80.0: + G = [1.60, 1.67, 2.24, 2.63] + elif location["latitude"] >= 80.0 and location["latitude"] < 90.0: + G = [1.11, 1.44, 1.94, 2.02] + + if jday > 335 or jday <= 60: + G = G[0] + elif jday > 60 and jday <= 152: + G = G[1] + elif jday > 152 and jday <= 244: + G = G[2] + elif jday > 244 and jday <= 335: + G = G[3] + device = ( + zen.device if isinstance(zen, torch.Tensor) else torch.device("cpu") + ) + G = torch.tensor(G, device=device) + + # dewpoint calculation + a2 = 17.27 + b2 = 237.7 + Td = (b2 * (((a2 * Ta) / (b2 + Ta)) + torch.log(RH))) / ( + a2 - (((a2 * Ta) / (b2 + Ta)) + torch.log(RH)) + ) + Td = (Td * 1.8) + 32 # Dewpoint (F) + u = torch.exp( + 0.1133 - torch.log(G + 1) + 0.0393 * Td + ) # Precipitable water + Tw = 1 - 0.077 * ( + (u * m) ** 0.3 + ) # Transmission coefficient for water vapor + Tar = 0.935**m # Transmission coefficient for aerosols + + I0 = Itoa * torch.cos(zen) * Trpg * Tw * D * Tar + if abs(zen) > torch.pi / 2: + I0 = 0 + # b=I0==abs(zen)>torch.pi/2 + # I0(b==1)=0 + # clear b; + if not (torch.isreal(I0)): + I0 = 0 + + corr = 0.1473 * torch.log(90 - (zen / torch.pi * 180)) + 0.3454 # 20070329 + + CIuncorr = radG / I0 + CI = CIuncorr + (1 - corr) + I0et = Itoa * torch.cos(zen) * D # extra terrestial solar radiation + Kt = radG / I0et + if math.isnan(CI): + CI = float("Inf") + + return I0, CI, Kt, I0et, CIuncorr diff --git a/util/SEBESOLWEIGCommonFiles/create_patches.py b/util/SEBESOLWEIGCommonFiles/create_patches.py index 02f0e0f..8f167d7 100644 --- a/util/SEBESOLWEIGCommonFiles/create_patches.py +++ b/util/SEBESOLWEIGCommonFiles/create_patches.py @@ -1,73 +1,54 @@ -import torch +import numpy as np -def create_patches(patch_option, device): +def create_patches(patch_option): - deg2rad = torch.pi / 180 + deg2rad = np.pi / 180 # patch_option = 1 = 145 patches (Robinson & Stone, 2004) # patch_option = 2 = 153 patches (Wallenberg et al., 2022) # patch_option = 3 = 306 patches -> test # patch_option = 4 = 612 patches -> test - skyvaultalt = torch.atleast_2d([]) - skyvaultazi = torch.atleast_2d([]) + skyvaultalt = np.atleast_2d([]) + skyvaultazi = np.atleast_2d([]) # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) - # Patch option 1, 145 patches, Original Robinson & Stone (2004) after Tregenza (1987)/Tregenza & Sharples (1993) + # Patch option 1, 145 patches, Original Robinson & Stone (2004) after + # Tregenza (1987)/Tregenza & Sharples (1993) if patch_option == 1: - annulino = torch.tensor( - [0, 12, 24, 36, 48, 60, 72, 84, 90], device=device - ) - skyvaultaltint = torch.tensor( - [6, 18, 30, 42, 54, 66, 78, 90], device=device - ) # Robinson & Stone (2004) - azistart = torch.tensor( - [0, 4, 2, 5, 8, 0, 10, 0], device=device - ) # Fredrik/Nils - patches_in_band = torch.tensor( - [30, 30, 24, 24, 18, 12, 6, 1], device=device - ) # Robinson & Stone (2004) + annulino = np.array([0, 12, 24, 36, 48, 60, 72, 84, 90]) + # Robinson & Stone (2004) + skyvaultaltint = np.array([6, 18, 30, 42, 54, 66, 78, 90]) + azistart = np.array([0, 4, 2, 5, 8, 0, 10, 0]) # Fredrik/Nils + # Robinson & Stone (2004) + patches_in_band = np.array([30, 30, 24, 24, 18, 12, 6, 1]) # Patch option 2, 153 patches, Wallenberg et al. (2022) elif patch_option == 2: - annulino = torch.tensor( - [0, 12, 24, 36, 48, 60, 72, 84, 90], device=device - ) - skyvaultaltint = torch.tensor( - [6, 18, 30, 42, 54, 66, 78, 90], device=device - ) # Robinson & Stone (2004) - azistart = torch.tensor( - [0, 4, 2, 5, 8, 0, 10, 0], device=device - ) # Fredrik/Nils - patches_in_band = torch.tensor( - [31, 30, 28, 24, 19, 13, 7, 1], device=device - ) # Nils + annulino = np.array([0, 12, 24, 36, 48, 60, 72, 84, 90]) + # Robinson & Stone (2004) + skyvaultaltint = np.array([6, 18, 30, 42, 54, 66, 78, 90]) + azistart = np.array([0, 4, 2, 5, 8, 0, 10, 0]) # Fredrik/Nils + patches_in_band = np.array([31, 30, 28, 24, 19, 13, 7, 1]) # Nils # Patch option 3, 306 patches, test elif patch_option == 3: - annulino = torch.tensor( - [0, 12, 24, 36, 48, 60, 72, 84, 90], device=device - ) - skyvaultaltint = torch.tensor( - [6, 18, 30, 42, 54, 66, 78, 90], device=device - ) # Robinson & Stone (2004) - azistart = torch.tensor( - [0, 4, 2, 5, 8, 0, 10, 0], device=device - ) # Fredrik/Nils - patches_in_band = torch.tensor( - [31 * 2, 30 * 2, 28 * 2, 24 * 2, 19 * 2, 13 * 2, 7 * 2, 1], - device=device, + annulino = np.array([0, 12, 24, 36, 48, 60, 72, 84, 90]) + # Robinson & Stone (2004) + skyvaultaltint = np.array([6, 18, 30, 42, 54, 66, 78, 90]) + azistart = np.array([0, 4, 2, 5, 8, 0, 10, 0]) # Fredrik/Nils + patches_in_band = np.array( + [31 * 2, 30 * 2, 28 * 2, 24 * 2, 19 * 2, 13 * 2, 7 * 2, 1] ) # Nils # Patch option 4, 612 patches, test elif patch_option == 4: - annulino = torch.tensor( - [0, 4.5, 9, 15, 21, 27, 33, 39, 45, 51, 57, 63, 69, 75, 81, 90], - device=device, + annulino = np.array( + [0, 4.5, 9, 15, 21, 27, 33, 39, 45, 51, 57, 63, 69, 75, 81, 90] ) # Nils - skyvaultaltint = torch.tensor( - [3, 9, 15, 21, 27, 33, 39, 45, 51, 57, 63, 69, 75, 81, 90], - device=device, + skyvaultaltint = np.array( + [3, 9, 15, 21, 27, 33, 39, 45, 51, 57, 63, 69, 75, 81, 90] ) # Nils - patches_in_band = torch.tensor( + patches_in_band = np.array( + # Nils [ 31 * 2, 31 * 2, @@ -84,21 +65,24 @@ def create_patches(patch_option, device): 7 * 2, 7 * 2, 1, - ], - device=device, - ) # Nils - azistart = torch.tensor( - [0, 0, 4, 4, 2, 2, 5, 5, 8, 8, 0, 0, 10, 10, 0], device=device + ] + ) + azistart = np.array( + [0, 0, 4, 4, 2, 2, 5, 5, 8, 8, 0, 0, 10, 10, 0] ) # Nils - skyvaultaziint = torch.tensor( - [360 / patches for patches in patches_in_band] - ) + skyvaultaziint = np.array([360 / patches for patches in patches_in_band]) for j in range(0, skyvaultaltint.shape[0]): for k in range(0, patches_in_band[j]): - skyvaultalt = skyvaultalt + (skyvaultaltint[j],) - skyvaultazi = skyvaultazi + (k * skyvaultaziint[j] + azistart[j],) + skyvaultalt = np.append(skyvaultalt, skyvaultaltint[j]) + skyvaultazi = np.append( + skyvaultazi, k * skyvaultaziint[j] + azistart[j] + ) + + # skyvaultzen = (90 - skyvaultalt) * deg2rad + # skyvaultalt = skyvaultalt * deg2rad + # skyvaultazi = skyvaultazi * deg2rad return ( skyvaultalt, diff --git a/util/SEBESOLWEIGCommonFiles/create_patches_torch.py b/util/SEBESOLWEIGCommonFiles/create_patches_torch.py new file mode 100644 index 0000000..4c0945c --- /dev/null +++ b/util/SEBESOLWEIGCommonFiles/create_patches_torch.py @@ -0,0 +1,113 @@ +try: + import torch +except: + pass + +def create_patches(patch_option, device): + + deg2rad = torch.pi / 180 + + # patch_option = 1 = 145 patches (Robinson & Stone, 2004) + # patch_option = 2 = 153 patches (Wallenberg et al., 2022) + # patch_option = 3 = 306 patches -> test + # patch_option = 4 = 612 patches -> test + + skyvaultalt = torch.atleast_2d([]) + skyvaultazi = torch.atleast_2d([]) + + # Creating skyvault of patches of constant radians (Tregeneza and Sharples, 1993) + # Patch option 1, 145 patches, Original Robinson & Stone (2004) after Tregenza (1987)/Tregenza & Sharples (1993) + if patch_option == 1: + annulino = torch.tensor( + [0, 12, 24, 36, 48, 60, 72, 84, 90], device=device + ) + skyvaultaltint = torch.tensor( + [6, 18, 30, 42, 54, 66, 78, 90], device=device + ) # Robinson & Stone (2004) + azistart = torch.tensor( + [0, 4, 2, 5, 8, 0, 10, 0], device=device + ) # Fredrik/Nils + patches_in_band = torch.tensor( + [30, 30, 24, 24, 18, 12, 6, 1], device=device + ) # Robinson & Stone (2004) + # Patch option 2, 153 patches, Wallenberg et al. (2022) + elif patch_option == 2: + annulino = torch.tensor( + [0, 12, 24, 36, 48, 60, 72, 84, 90], device=device + ) + skyvaultaltint = torch.tensor( + [6, 18, 30, 42, 54, 66, 78, 90], device=device + ) # Robinson & Stone (2004) + azistart = torch.tensor( + [0, 4, 2, 5, 8, 0, 10, 0], device=device + ) # Fredrik/Nils + patches_in_band = torch.tensor( + [31, 30, 28, 24, 19, 13, 7, 1], device=device + ) # Nils + # Patch option 3, 306 patches, test + elif patch_option == 3: + annulino = torch.tensor( + [0, 12, 24, 36, 48, 60, 72, 84, 90], device=device + ) + skyvaultaltint = torch.tensor( + [6, 18, 30, 42, 54, 66, 78, 90], device=device + ) # Robinson & Stone (2004) + azistart = torch.tensor( + [0, 4, 2, 5, 8, 0, 10, 0], device=device + ) # Fredrik/Nils + patches_in_band = torch.tensor( + [31 * 2, 30 * 2, 28 * 2, 24 * 2, 19 * 2, 13 * 2, 7 * 2, 1], + device=device, + ) # Nils + # Patch option 4, 612 patches, test + elif patch_option == 4: + annulino = torch.tensor( + [0, 4.5, 9, 15, 21, 27, 33, 39, 45, 51, 57, 63, 69, 75, 81, 90], + device=device, + ) # Nils + skyvaultaltint = torch.tensor( + [3, 9, 15, 21, 27, 33, 39, 45, 51, 57, 63, 69, 75, 81, 90], + device=device, + ) # Nils + patches_in_band = torch.tensor( + [ + 31 * 2, + 31 * 2, + 30 * 2, + 30 * 2, + 28 * 2, + 28 * 2, + 24 * 2, + 24 * 2, + 19 * 2, + 19 * 2, + 13 * 2, + 13 * 2, + 7 * 2, + 7 * 2, + 1, + ], + device=device, + ) # Nils + azistart = torch.tensor( + [0, 0, 4, 4, 2, 2, 5, 5, 8, 8, 0, 0, 10, 10, 0], device=device + ) # Nils + + skyvaultaziint = torch.tensor( + [360 / patches for patches in patches_in_band] + ) + + for j in range(0, skyvaultaltint.shape[0]): + for k in range(0, patches_in_band[j]): + skyvaultalt = skyvaultalt + (skyvaultaltint[j],) + skyvaultazi = skyvaultazi + (k * skyvaultaziint[j] + azistart[j],) + + return ( + skyvaultalt, + skyvaultazi, + annulino, + skyvaultaltint, + patches_in_band, + skyvaultaziint, + azistart, + ) diff --git a/util/SEBESOLWEIGCommonFiles/diffusefraction.py b/util/SEBESOLWEIGCommonFiles/diffusefraction.py index 5c7970d..93a31d5 100644 --- a/util/SEBESOLWEIGCommonFiles/diffusefraction.py +++ b/util/SEBESOLWEIGCommonFiles/diffusefraction.py @@ -1,5 +1,5 @@ from __future__ import division -import torch +import numpy as np def diffusefraction(radG, altitude, Kt, Ta, RH): @@ -15,9 +15,9 @@ def diffusefraction(radG, altitude, Kt, Ta, RH): :return: """ - alfa = altitude * (torch.pi / 180) + alfa = altitude * (np.pi / 180) - if Ta <= -999.00 or RH <= -999.00 or torch.isnan(Ta) or torch.isnan(RH): + if Ta <= -999.00 or RH <= -999.00 or np.isnan(Ta) or np.isnan(RH): if Kt <= 0.3: radD = radG * (1.020 - 0.248 * Kt) elif Kt > 0.3 and Kt < 0.78: @@ -30,7 +30,7 @@ def diffusefraction(radG, altitude, Kt, Ta, RH): radD = radG * ( 1 - 0.232 * Kt - + 0.0239 * torch.sin(alfa) + + 0.0239 * np.sin(alfa) - 0.000682 * Ta + 0.0195 * RH ) @@ -38,19 +38,16 @@ def diffusefraction(radG, altitude, Kt, Ta, RH): radD = radG * ( 1.329 - 1.716 * Kt - + 0.267 * torch.sin(alfa) + + 0.267 * np.sin(alfa) - 0.00357 * Ta + 0.106 * RH ) else: radD = radG * ( - 0.426 * Kt - - 0.256 * torch.sin(alfa) - + 0.00349 * Ta - + 0.0734 * RH + 0.426 * Kt - 0.256 * np.sin(alfa) + 0.00349 * Ta + 0.0734 * RH ) - radI = (radG - radD) / (torch.sin(alfa)) + radI = (radG - radD) / (np.sin(alfa)) # Corrections for low sun altitudes (20130307) if radI < 0: diff --git a/util/SEBESOLWEIGCommonFiles/diffusefraction_torch.py b/util/SEBESOLWEIGCommonFiles/diffusefraction_torch.py new file mode 100644 index 0000000..c352251 --- /dev/null +++ b/util/SEBESOLWEIGCommonFiles/diffusefraction_torch.py @@ -0,0 +1,67 @@ +from __future__ import division +try: + import torch +except: + pass + +def diffusefraction(radG, altitude, Kt, Ta, RH): + """ + This function estimates diffuse and directbeam radiation according to + Reindl et al (1990), Solar Energy 45:1 + + :param radG: + :param altitude: + :param Kt: # radiation at the top of the atmosphere + :param Ta: + :param RH: + :return: + """ + + alfa = altitude * (torch.pi / 180) + + if Ta <= -999.00 or RH <= -999.00 or torch.isnan(Ta) or torch.isnan(RH): + if Kt <= 0.3: + radD = radG * (1.020 - 0.248 * Kt) + elif Kt > 0.3 and Kt < 0.78: + radD = radG * (1.45 - 1.67 * Kt) + else: + radD = radG * 0.147 + else: + RH = RH / 100 + if Kt <= 0.3: + radD = radG * ( + 1 + - 0.232 * Kt + + 0.0239 * torch.sin(alfa) + - 0.000682 * Ta + + 0.0195 * RH + ) + elif Kt > 0.3 and Kt < 0.78: + radD = radG * ( + 1.329 + - 1.716 * Kt + + 0.267 * torch.sin(alfa) + - 0.00357 * Ta + + 0.106 * RH + ) + else: + radD = radG * ( + 0.426 * Kt + - 0.256 * torch.sin(alfa) + + 0.00349 * Ta + + 0.0734 * RH + ) + + radI = (radG - radD) / (torch.sin(alfa)) + + # Corrections for low sun altitudes (20130307) + if radI < 0: + radI = 0 + + if altitude < 1 and radI > radG: + radI = radG + + if radD > radG: + radD = radG + + return radI, radD diff --git a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py index 1d08ca6..db1d5b1 100644 --- a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py +++ b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13.py @@ -1,56 +1,46 @@ # -*- coding: utf-8 -*- from __future__ import division +import numpy as np from math import radians -import torch -import torch.nn.functional as F +# from scipy.ndimage.filters import median_filter -def shade_on_walls(azimuth, aspect, walls, dsm, f, device): - """ - Calculates shadow heights and lighting states on building facades using vector geometry. - - This function determines wall segments that are self-shadowed (facing away from the sun) - using the cosine of the angle difference, tracks the down-ray building shadow volume, - and isolates sections of the walls that remain illuminated. - - Args: - azimuth (float): Sun azimuth angle in radians. - aspect (torch.Tensor): Aspect orientation of the walls in radians. - walls (torch.Tensor): Height profile of the pixels representing building walls. - dsm (torch.Tensor): Digital Surface Model (ground + building heights). - f (torch.Tensor): Maximum building shadow volume tracked during the ray-tracing loop. - device (torch.device): The PyTorch device (CPU/GPU) where computations occur. - - Returns: - tuple: A tuple containing: - - sh (torch.Tensor): Binary ground and roof shadow mask (1 = sun, 0 = shadow). - - wallsh (torch.Tensor): Height of the wall that is in shadow (meters). - - wallsun (torch.Tensor): Height of the wall that is in direct sun (meters). - - facesh (torch.Tensor): Binary mask of walls in self-shadow (1 = shadowed by self). - - facesun (torch.Tensor): Binary mask of walls facing the sun and not occluded. - """ - # If the cosine of the angle difference is <= 0, the facade faces away from the sun (self-shadow) - cos_incidence = torch.cos(aspect - azimuth) - facesh = (cos_incidence <= 0).float() * (walls > 0).float() - sh = f - dsm # Shadow volume (height of cast shadow) +def shade_on_walls(azimuth, aspect, walls, dsm, f): + # wall shadows wall parameterization + wallbol = (walls > 0).astype(float) - # Facades facing the sun (facesh == 0) that are valid walls - facesun = ((facesh == 0) & (walls > 0)).float() - - # Calculate the height of the wall segment receiving direct sunlight - wallsun = walls - sh - wallsun = torch.clamp(wallsun, min=0.0) - wallsun = torch.where( - facesh == 1, torch.tensor(0.0, device=device), wallsun - ) + # Removing walls in shadow due to selfshadowing + azilow = azimuth - np.pi / 2 + azihigh = azimuth + np.pi / 2 - # The shadowed wall height is the total wall height minus the sunlit segment - wallsh = walls - wallsun - - # Transform the cast shadow volume into a binary mask (0 = shadow, 1 = sun) - sh = (sh > 0).float() - sh = 1.0 - sh + if azilow >= 0 and azihigh < 2 * np.pi: # 90 to 270 (SHADOW) + facesh = ( + np.logical_or(aspect < azilow, aspect >= azihigh).astype(float) + - wallbol + + 1 + ) + elif azilow < 0 and azihigh <= 2 * np.pi: # 0 to 90 + azilow = azilow + 2 * np.pi + # (SHADOW) # check for the -1 + facesh = np.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 + elif azilow > 0 and azihigh >= 2 * np.pi: # 270 to 360 + azihigh = azihigh - 2 * np.pi + facesh = ( + np.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 + ) # (SHADOW) + + sh = np.copy(f - dsm) # shadow volume + facesun = np.logical_and( + facesh + (walls > 0).astype(float) == 1, walls > 0 + ).astype(float) + wallsun = np.copy(walls - sh) + wallsun[wallsun < 0] = 0 + wallsun[facesh == 1] = 0 # Removing walls in "self"-shadow + wallsh = np.copy(walls - wallsun) + + sh = np.logical_not(np.logical_not(sh)).astype(float) + sh = sh * -1 + 1 return sh, wallsh, wallsun, facesh, facesun @@ -66,98 +56,152 @@ def shadowingfunction_wallheight_13( aspect_scheme=False, ): """ - Computes ground/roof shadows and shadow heights on building walls using a DSM. - - This function leverages hardware-accelerated grid sampling (`F.grid_sample`) in PyTorch - to simulate solar ray tracing. It avoids explicit multi-index pixel slicing or nested - loops, making it optimal for high-performance GPU execution. - - Args: - a (torch.Tensor): Digital Surface Model (DSM) matrix. - azimuth (float): Sun azimuth angle in degrees. - altitude (float): Sun altitude angle in degrees. - scale (float): Map scale modifier (e.g., 1 pixel = 1 meter -> 1.0; 2 meters -> 0.5). - walls (torch.Tensor): Extruded standalone wall pixel heights. - aspect (torch.Tensor): Surface normal orientation of the walls in radians. - walls_scheme (torch.Tensor, optional): Alternative building scheme layout. Defaults to False. - aspect_scheme (torch.Tensor, optional): Alternative building scheme wall orientation. Defaults to False. - - Returns: - tuple: Depending on whether `walls_scheme` is provided, returns: - - Standard: (sh, wallsh, wallsun, facesh, facesun) - - Scheme: (sh, wallsh, wallsun, facesh, facesun, shade_on_wall) + This m.file calculates shadows on a DSM and shadow height on building + walls. + + INPUTS: + a = DSM + azimuth and altitude = sun position + scale= scale of DSM (1 meter pixels=1, 2 meter pixels=0.5) + walls= pixel row 'outside' buildings. will be calculated if empty + aspect = normal aspect of buildings walls + + OUTPUT: + sh=ground and roof shadow + wallsh = height of wall that is in shadow + wallsun = hieght of wall that is in sun + + Fredrik Lindberg 2012-03-19 + fredrikl@gvc.gu.se + + Utdate 2013-03-13 - bugfix for walls alinged with sun azimuths + + :param a: + :param azimuth: + :param altitude: + :param scale: + :param walls: + :param aspect: + :return: """ - # Automatically detect the execution device (CPU or GPU) from the input tensor - device = a.device if isinstance(a, torch.Tensor) else torch.device("cpu") - # Convert angular sun coordinates to radians + if not walls.size: + """ + walls = ordfilt2(a,4,[0 1 0; 1 0 1; 0 1 0]) + walls = walls-a + walls[walls < 3]=0 + sizex = np.shape(a)[0] #might be wrong + sizey = np.shape(a)[1] + dirwalls = filter1Goodwin_as_aspect_v3(walls,sizex,sizey,scale,a); + aspect = dirwalls*np.pi/180 + """ + + # conversion + # degrees = np.pi/180 azimuth = radians(azimuth) altitude = radians(altitude) - # Extract map grid spatial dimensions - sizex, sizey = a.shape - - # Clone the DSM matrix to track shifting horizon shadows - f = a.clone() + # measure the size of the image + sizex = np.shape(a)[0] + sizey = np.shape(a)[1] + + # initialise parameters + f = np.copy(a) + dx = 0 + dy = 0 + dz = 0 + temp = np.zeros((sizex, sizey)) + wallbol = (walls > 0).astype(float) + + # other loop parameters + amaxvalue = np.max(a) + pibyfour = np.pi / 4 + threetimespibyfour = 3 * pibyfour + fivetimespibyfour = 5 * pibyfour + seventimespibyfour = 7 * pibyfour + sinazimuth = np.sin(azimuth) + cosazimuth = np.cos(azimuth) + tanazimuth = np.tan(azimuth) + signsinazimuth = np.sign(sinazimuth) + signcosazimuth = np.sign(cosazimuth) + dssin = np.abs(1 / sinazimuth) + dscos = np.abs(1 / cosazimuth) + tanaltitudebyscale = np.tan(altitude) / scale + + index = 1 + + # main loop + while (amaxvalue >= dz) and (np.abs(dx) < sizex) and (np.abs(dy) < sizey): + + if (pibyfour <= azimuth and azimuth < threetimespibyfour) or ( + fivetimespibyfour <= azimuth and azimuth < seventimespibyfour + ): + dy = signsinazimuth * index + dx = -1 * signcosazimuth * np.abs(np.round(index / tanazimuth)) + ds = dssin + else: + dy = signsinazimuth * np.abs(np.round(index * tanazimuth)) + dx = -1 * signcosazimuth * index + ds = dscos + + # note: dx and dy represent absolute values while ds is an incremental + # value + dz = ds * index * tanaltitudebyscale + temp[0:sizex, 0:sizey] = 0 + + absdx = np.abs(dx) + absdy = np.abs(dy) + + xc1 = int((dx + absdx) / 2) + xc2 = int(sizex + (dx - absdx) / 2) + yc1 = int((dy + absdy) / 2) + yc2 = int(sizey + (dy - absdy) / 2) + + xp1 = int(-((dx - absdx) / 2)) + xp2 = int(sizex - (dx + absdx) / 2) + yp1 = int(-((dy - absdy) / 2)) + yp2 = int(sizey - (dy + absdy) / 2) + + temp[xp1:xp2, yp1:yp2] = a[xc1:xc2, yc1:yc2] - dz + f = np.fmax(f, temp) # Moving building shadow + + index = index + 1 + + # # Removing walls in shadow due to selfshadowing + # azilow = azimuth-np.pi/2 + # azihigh = azimuth+np.pi/2 + + # if azilow >= 0 and azihigh < 2*np.pi: # 90 to 270 (SHADOW) + # facesh = (np.logical_or(aspect < azilow, aspect >= azihigh).astype(float)-wallbol+1) + # elif azilow < 0 and azihigh <= 2*np.pi: # 0 to 90 + # azilow = azilow + 2*np.pi + # facesh = np.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 # (SHADOW) # check for the -1 + # elif azilow > 0 and azihigh >= 2*np.pi: # 270 to 360 + # azihigh = azihigh-2*np.pi + # facesh = np.logical_or(aspect > azilow, aspect <= azihigh)*-1 + 1 # + # (SHADOW) + + # sh = np.copy(f-a) # shadow volume + # facesun = np.logical_and(facesh + (walls > 0).astype(float) == 1, walls > 0).astype(float) + # wallsun = np.copy(walls-sh) + # wallsun[wallsun < 0] = 0 + # wallsun[facesh == 1] = 0 # Removing walls in "self"-shadow + # wallsh = np.copy(walls-wallsun) + + # sh = np.logical_not(np.logical_not(sh)).astype(float) + # sh = sh * -1 + 1 - # --- COORDINATE GRID GENERATION FOR GLOBAL TRANSFORMATION --- - y, x = torch.meshgrid( - torch.linspace(-1, 1, sizex, device=device), - torch.linspace(-1, 1, sizey, device=device), - indexing="ij", - ) - # Reshape grid layout to PyTorch standard: (1, H, W, 2) with (X, Y) ordered at dim=-1 - grille_base = torch.stack((x, y), dim=-1).unsqueeze(0) - - # Prepare 4D-batched representation of the DSM for the sampling engine (1, 1, H, W) - a_4d = a.unsqueeze(0).unsqueeze(0) - - # Extract geometric limits and spatial sun vector components - amaxvalue = torch.max(a) - sinazimuth = torch.sin(azimuth) - cosazimuth = torch.cos(azimuth) - tanaltitudebyscale = torch.tan(altitude) / scale - - # Dynamically compute the maximum loop range needed based on terrain peak height - max_steps = int(amaxvalue / tanaltitudebyscale) + 2 - - # Core Ray-Tracing Loop (Accelerated using structural translation layers) - for index in range(1, max_steps): - # 1. Project physical shadow displacement on the horizontal plane (in pixels) - shift_x = index * sinazimuth / scale - shift_y = index * cosazimuth / scale - dz = index * tanaltitudebyscale - - # Early termination checkpoint if the casting rays clear the map canvas bounds - if abs(shift_x) >= sizey and abs(shift_y) >= sizex: - break - - # 2. Shift the normalized coordinate sheet (-1 to 1 space) - grille_deplacee = grille_base.clone() - grille_deplacee[..., 0] -= (2.0 * shift_x) / (sizey - 1) - grille_deplacee[..., 1] -= (2.0 * shift_y) / (sizex - 1) - - # 3. Retrieve the shifted layer map natively using bilinear interpolation - temp = ( - F.grid_sample( - a_4d, grille_deplacee, mode="bilinear", padding_mode="border" - ).squeeze() - - dz - ) - - # 4. Amalgamate shadows via upper envelope tracking - f = torch.maximum(f, temp) - - # --- FACADE EVALUATION & SHADOW METRICS POST-PROCESSING --- sh, wallsh, wallsun, facesh, facesun = shade_on_walls( - azimuth, aspect, walls, a, f, device + azimuth, aspect, walls, a, f ) - if walls_scheme is not False: - _, wallsh_, _, _, _ = shade_on_walls( - azimuth, aspect_scheme, walls_scheme, a, f, device + sh_, wallsh_, wallsun_, facesh_, facesun_ = shade_on_walls( + azimuth, aspect_scheme, walls_scheme, a, f ) - shade_on_wall = wallsh_.clone() - return (sh, wallsh, wallsun, facesh, facesun, shade_on_wall) + shade_on_wall = wallsh_.copy() - return (sh, wallsh, wallsun, facesh, facesun) + return ( + (sh, wallsh, wallsun, facesh, facesun, shade_on_wall) + if walls_scheme is not False + else (sh, wallsh, wallsun, facesh, facesun) + ) diff --git a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13_torch.py b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13_torch.py new file mode 100644 index 0000000..9826b43 --- /dev/null +++ b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13_torch.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- +from __future__ import division +from math import radians +try: + import torch + import torch.nn.functional as F +except: + pass + + +def shade_on_walls(azimuth, aspect, walls, dsm, f, device): + """ + Calculates shadow heights and lighting states on building facades using vector geometry. + + This function determines wall segments that are self-shadowed (facing away from the sun) + using the cosine of the angle difference, tracks the down-ray building shadow volume, + and isolates sections of the walls that remain illuminated. + + Args: + azimuth (float): Sun azimuth angle in radians. + aspect (torch.Tensor): Aspect orientation of the walls in radians. + walls (torch.Tensor): Height profile of the pixels representing building walls. + dsm (torch.Tensor): Digital Surface Model (ground + building heights). + f (torch.Tensor): Maximum building shadow volume tracked during the ray-tracing loop. + device (torch.device): The PyTorch device (CPU/GPU) where computations occur. + + Returns: + tuple: A tuple containing: + - sh (torch.Tensor): Binary ground and roof shadow mask (1 = sun, 0 = shadow). + - wallsh (torch.Tensor): Height of the wall that is in shadow (meters). + - wallsun (torch.Tensor): Height of the wall that is in direct sun (meters). + - facesh (torch.Tensor): Binary mask of walls in self-shadow (1 = shadowed by self). + - facesun (torch.Tensor): Binary mask of walls facing the sun and not occluded. + """ + # If the cosine of the angle difference is <= 0, the facade faces away from the sun (self-shadow) + cos_incidence = torch.cos(aspect - azimuth) + facesh = (cos_incidence <= 0).float() * (walls > 0).float() + + sh = f - dsm # Shadow volume (height of cast shadow) + + # Facades facing the sun (facesh == 0) that are valid walls + facesun = ((facesh == 0) & (walls > 0)).float() + + # Calculate the height of the wall segment receiving direct sunlight + wallsun = walls - sh + wallsun = torch.clamp(wallsun, min=0.0) + wallsun = torch.where( + facesh == 1, torch.tensor(0.0, device=device), wallsun + ) + + # The shadowed wall height is the total wall height minus the sunlit segment + wallsh = walls - wallsun + + # Transform the cast shadow volume into a binary mask (0 = shadow, 1 = sun) + sh = (sh > 0).float() + sh = 1.0 - sh + + return sh, wallsh, wallsun, facesh, facesun + + +def shadowingfunction_wallheight_13( + a, + azimuth, + altitude, + scale, + walls, + aspect, + walls_scheme=False, + aspect_scheme=False, +): + """ + Computes ground/roof shadows and shadow heights on building walls using a DSM. + + This function leverages hardware-accelerated grid sampling (`F.grid_sample`) in PyTorch + to simulate solar ray tracing. It avoids explicit multi-index pixel slicing or nested + loops, making it optimal for high-performance GPU execution. + + Args: + a (torch.Tensor): Digital Surface Model (DSM) matrix. + azimuth (float): Sun azimuth angle in degrees. + altitude (float): Sun altitude angle in degrees. + scale (float): Map scale modifier (e.g., 1 pixel = 1 meter -> 1.0; 2 meters -> 0.5). + walls (torch.Tensor): Extruded standalone wall pixel heights. + aspect (torch.Tensor): Surface normal orientation of the walls in radians. + walls_scheme (torch.Tensor, optional): Alternative building scheme layout. Defaults to False. + aspect_scheme (torch.Tensor, optional): Alternative building scheme wall orientation. Defaults to False. + + Returns: + tuple: Depending on whether `walls_scheme` is provided, returns: + - Standard: (sh, wallsh, wallsun, facesh, facesun) + - Scheme: (sh, wallsh, wallsun, facesh, facesun, shade_on_wall) + """ + # Automatically detect the execution device (CPU or GPU) from the input tensor + device = a.device if isinstance(a, torch.Tensor) else torch.device("cpu") + + # Convert angular sun coordinates to radians + azimuth = radians(azimuth) + altitude = radians(altitude) + + # Extract map grid spatial dimensions + sizex, sizey = a.shape + + # Clone the DSM matrix to track shifting horizon shadows + f = a.clone() + + # --- COORDINATE GRID GENERATION FOR GLOBAL TRANSFORMATION --- + y, x = torch.meshgrid( + torch.linspace(-1, 1, sizex, device=device), + torch.linspace(-1, 1, sizey, device=device), + indexing="ij", + ) + # Reshape grid layout to PyTorch standard: (1, H, W, 2) with (X, Y) ordered at dim=-1 + grille_base = torch.stack((x, y), dim=-1).unsqueeze(0) + + # Prepare 4D-batched representation of the DSM for the sampling engine (1, 1, H, W) + a_4d = a.unsqueeze(0).unsqueeze(0) + + # Extract geometric limits and spatial sun vector components + amaxvalue = torch.max(a) + sinazimuth = torch.sin(azimuth) + cosazimuth = torch.cos(azimuth) + tanaltitudebyscale = torch.tan(altitude) / scale + + # Dynamically compute the maximum loop range needed based on terrain peak height + max_steps = int(amaxvalue / tanaltitudebyscale) + 2 + + # Core Ray-Tracing Loop (Accelerated using structural translation layers) + for index in range(1, max_steps): + # 1. Project physical shadow displacement on the horizontal plane (in pixels) + shift_x = index * sinazimuth / scale + shift_y = index * cosazimuth / scale + dz = index * tanaltitudebyscale + + # Early termination checkpoint if the casting rays clear the map canvas bounds + if abs(shift_x) >= sizey and abs(shift_y) >= sizex: + break + + # 2. Shift the normalized coordinate sheet (-1 to 1 space) + grille_deplacee = grille_base.clone() + grille_deplacee[..., 0] -= (2.0 * shift_x) / (sizey - 1) + grille_deplacee[..., 1] -= (2.0 * shift_y) / (sizex - 1) + + # 3. Retrieve the shifted layer map natively using bilinear interpolation + temp = ( + F.grid_sample( + a_4d, grille_deplacee, mode="bilinear", padding_mode="border" + ).squeeze() + - dz + ) + + # 4. Amalgamate shadows via upper envelope tracking + f = torch.maximum(f, temp) + + # --- FACADE EVALUATION & SHADOW METRICS POST-PROCESSING --- + sh, wallsh, wallsun, facesh, facesun = shade_on_walls( + azimuth, aspect, walls, a, f, device + ) + + if walls_scheme is not False: + _, wallsh_, _, _, _ = shade_on_walls( + azimuth, aspect_scheme, walls_scheme, a, f, device + ) + shade_on_wall = wallsh_.clone() + return (sh, wallsh, wallsun, facesh, facesun, shade_on_wall) + + return (sh, wallsh, wallsun, facesh, facesun) diff --git a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py index 72c2c43..a8b102c 100644 --- a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py +++ b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py @@ -1,36 +1,53 @@ from __future__ import division -import torch -import torch.nn.functional as F +import numpy as np # import matplotlib.pylab as plt -def shade_on_walls(azimuth, aspect, walls, dsm, f, shvoveg, device): - cos_incidence = torch.cos(aspect - azimuth) - facesh = (cos_incidence <= 0).float() * (walls > 0).float() - - shvo = f - dsm - - facesun = ((facesh == 0) & (walls > 0)).float() - - wallsun = walls - shvo - wallsun = torch.clamp(wallsun, min=0.0) - wallsun = torch.where( - facesh == 1, torch.tensor(0.0, device=device), wallsun - ) - - wallsh = walls - wallsun - - wallshve = shvoveg * (walls > 0).float() - wallshve = torch.clamp(wallshve - wallsh, min=0.0) - - mask_clip = wallshve > walls - wallshve[mask_clip] = walls[mask_clip].to(dtype=wallshve.dtype) - - wallsun = wallsun - wallshve - mask_neg = wallsun < 0 - wallshve[mask_neg] = 0 - wallsun[mask_neg] = 0 +def shade_on_walls(azimuth, aspect, walls, dsm, f, shvoveg): + # wall shadows wall parameterization + wallbol = (walls > 0).astype(float) + + # Removing walls in shadow due to selfshadowing + azilow = azimuth - np.pi / 2 + azihigh = azimuth + np.pi / 2 + if azilow >= 0 and azihigh < 2 * np.pi: # 90 to 270 (SHADOW) + facesh = ( + np.logical_or(aspect < azilow, aspect >= azihigh).astype(float) + - wallbol + + 1 + ) # TODO check + elif azilow < 0 and azihigh <= 2 * np.pi: # 0 to 90 + azilow = azilow + 2 * np.pi + facesh = ( + np.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 + ) # (SHADOW) + elif azilow > 0 and azihigh >= 2 * np.pi: # 270 to 360 + azihigh -= 2 * np.pi + facesh = ( + np.logical_or(aspect > azilow, aspect <= azihigh) * -1 + 1 + ) # (SHADOW) + + shvo = f - dsm # building shadow volume + facesun = np.logical_and( + facesh + (walls > 0).astype(float) == 1, walls > 0 + ).astype(float) + wallsun = np.copy(walls - shvo) + wallsun[wallsun < 0] = 0 + wallsun[facesh == 1] = 0 # Removing walls in "self"-shadow + wallsh = np.copy(walls - wallsun) + + wallshve = shvoveg * wallbol + wallshve = wallshve - wallsh + wallshve[wallshve < 0] = 0 + id = np.where(wallshve > walls) + wallshve[id] = walls[id] + wallsun = wallsun - wallshve # problem with wallshve only + id = np.where(wallsun < 0) + wallshve[id] = 0 + wallsun[id] = 0 + # if np.sum(wallshve <= 0) == wallshve.size: + # wallshve[:, :] = 0 return wallsh, wallsun, wallshve, facesh, facesun @@ -86,148 +103,148 @@ def shadowingfunction_wallheight_23( :return: """ - # automatically get the device to use from the input - device = a.device if isinstance(a, torch.Tensor) else torch.device("cpu") - degrees = torch.pi / 180.0 + # conversion + degrees = np.pi / 180.0 azimuth *= degrees altitude *= degrees - sizex, sizey = a.shape - bushplant = (bush > 1).float() - - # Tensor initialisation - sh = torch.zeros((sizex, sizey), device=device) - vbshvegsh = torch.zeros((sizex, sizey), device=device) - vegsh = torch.zeros((sizex, sizey), device=device) + bushplant - f = a.clone() - shvoveg = vegdem.clone() - - # ---coordinates drag the layer --- - y, x = torch.meshgrid( - torch.linspace(-1, 1, sizex, device=device), - torch.linspace(-1, 1, sizey, device=device), - indexing="ij", - ) - grille_base = torch.stack((x, y), dim=-1).unsqueeze(0) - - # Preparation of the layers packed for grid_sample - a_4d = a.unsqueeze(0).unsqueeze(0).float() - veg_4d = vegdem.unsqueeze(0).unsqueeze(0).float() - veg2_4d = vegdem2.unsqueeze(0).unsqueeze(0).float() - - # Trigonometric params for the sun's step - sinazimuth = torch.sin(azimuth) - cosazimuth = torch.cos(azimuth) - tanaltitudebyscale = torch.tan(altitude) / scale - - # Calculation of the maximum of steps - # We stop when the shadow exceed the max possible height - max_steps = int(amaxvalue / tanaltitudebyscale) + 2 - - # Facteur d'avancement du rayon (équivalent géométrique de ton ancien 'ds') - # Plus besoin de gros blocs If/Else selon les quadrants du soleil ! - delta_z_par_pas = tanaltitudebyscale - - for index in range(1, max_steps): - - # 1. Calculations of the slide - shift_x = index * sinazimuth / scale - shift_y = index * cosazimuth / scale - dz = index * delta_z_par_pas - - # Stop if the shadow is completely outside of the map - if abs(shift_x) >= sizey and abs(shift_y) >= sizex: - break - - # 2. Applying the offset to our normalized coordinate sheet (-1 to 1) - grille_deplacee = grille_base.clone() - grille_deplacee[..., 0] -= (2.0 * shift_x) / (sizey - 1) - grille_deplacee[..., 1] -= (2.0 * shift_y) / (sizex - 1) - - # 3. Global Material Sampling (Instant Layer Swipe) - temp = ( - F.grid_sample( - a_4d, grille_deplacee, mode="bilinear", padding_mode="border" - ).squeeze() - - dz - ) - tempvegdem = ( - F.grid_sample( - veg_4d, grille_deplacee, mode="bilinear", padding_mode="border" - ).squeeze() - - dz - ) - tempvegdem2 = ( - F.grid_sample( - veg2_4d, - grille_deplacee, - mode="bilinear", - padding_mode="border", - ).squeeze() - - dz - ) - - # 4. Update of cast shadow volumes - f = torch.fmax(f, temp) - shvoveg = torch.fmax(shvoveg, tempvegdem) - - sh = torch.where( - f > a, - torch.tensor(1.0, device=device), - torch.tensor(0.0, device=device), - ) - fabovea = tempvegdem > a - gabovea = tempvegdem2 > a - - # 5. Pergola logic (Overlaying layers with the & operator) - templastfabovea = tempvegdem + delta_z_par_pas - templastgabovea = tempvegdem2 + delta_z_par_pas - + # measure the size of the image + sizex = np.shape(a)[0] + sizey = np.shape(a)[1] + + # initialise parameters + dx = 0 + dy = 0 + dz = 0 + temp = np.zeros((sizex, sizey)) + tempvegdem = np.zeros((sizex, sizey)) + tempvegdem2 = np.zeros((sizex, sizey)) + templastfabovea = np.zeros((sizex, sizey)) + templastgabovea = np.zeros((sizex, sizey)) + bushplant = bush > 1 + sh = np.zeros((sizex, sizey)) # shadows from buildings + vbshvegsh = np.copy(sh) # vegetation blocking buildings + vegsh = np.add( + np.zeros((sizex, sizey)), bushplant, dtype=float + ) # vegetation shadow + f = np.copy(a) + shvoveg = np.copy(vegdem) # for vegetation shadowvolume + # g = np.copy(sh) + wallbol = (walls > 0).astype(float) + + # other loop parameters + pibyfour = np.pi / 4 + threetimespibyfour = 3 * pibyfour + fivetimespibyfour = 5 * pibyfour + seventimespibyfour = 7 * pibyfour + sinazimuth = np.sin(azimuth) + cosazimuth = np.cos(azimuth) + tanazimuth = np.tan(azimuth) + signsinazimuth = np.sign(sinazimuth) + signcosazimuth = np.sign(cosazimuth) + dssin = np.abs(1 / sinazimuth) + dscos = np.abs(1 / cosazimuth) + tanaltitudebyscale = np.tan(altitude) / scale + + index = 0 + + # new case with pergola (thin vertical layer of vegetation), August 2021 + dzprev = 0 + + # main loop + while (amaxvalue >= dz) and (np.abs(dx) < sizex) and (np.abs(dy) < sizey): + if ((pibyfour <= azimuth) and (azimuth < threetimespibyfour)) or ( + (fivetimespibyfour <= azimuth) and (azimuth < seventimespibyfour) + ): + dy = signsinazimuth * index + dx = -1 * signcosazimuth * np.abs(np.round(index / tanazimuth)) + ds = dssin + else: + dy = signsinazimuth * np.abs(np.round(index * tanazimuth)) + dx = -1 * signcosazimuth * index + ds = dscos + + # note: dx and dy represent absolute values while ds is an incremental + # value + dz = (ds * index) * tanaltitudebyscale + tempvegdem[0:sizex, 0:sizey] = 0 + tempvegdem2[0:sizex, 0:sizey] = 0 + temp[0:sizex, 0:sizey] = 0 + templastfabovea[0:sizex, 0:sizey] = 0.0 + templastgabovea[0:sizex, 0:sizey] = 0.0 + absdx = np.abs(dx) + absdy = np.abs(dy) + xc1 = int((dx + absdx) / 2) + xc2 = int(sizex + (dx - absdx) / 2) + yc1 = int((dy + absdy) / 2) + yc2 = int(sizey + (dy - absdy) / 2) + xp1 = -int((dx - absdx) / 2) + xp2 = int(sizex - (dx + absdx) / 2) + yp1 = -int((dy - absdy) / 2) + yp2 = int(sizey - (dy + absdy) / 2) + + tempvegdem[xp1:xp2, yp1:yp2] = vegdem[xc1:xc2, yc1:yc2] - dz + tempvegdem2[xp1:xp2, yp1:yp2] = vegdem2[xc1:xc2, yc1:yc2] - dz + temp[xp1:xp2, yp1:yp2] = a[xc1:xc2, yc1:yc2] - dz + + f = np.fmax(f, temp) # Moving building shadow + # moving vegetation shadow volume + shvoveg = np.fmax(shvoveg, tempvegdem) + + sh[f > a] = 1 + sh[f <= a] = 0 + fabovea = (tempvegdem > a).astype(int) # vegdem above DEM + gabovea = (tempvegdem2 > a).astype(int) # vegdem2 above DEM + + # new pergola condition + templastfabovea[xp1:xp2, yp1:yp2] = vegdem[xc1:xc2, yc1:yc2] - dzprev + templastgabovea[xp1:xp2, yp1:yp2] = vegdem2[xc1:xc2, yc1:yc2] - dzprev lastfabovea = templastfabovea > a lastgabovea = templastgabovea > a - - # If all 4 layers are True at the same time, it's a pergola (light passes through). - is_pergola = fabovea & gabovea & lastfabovea & lastgabovea - - # The shadow exists if one of the layers is True, UNLESS it's a pergola. - vegsh2 = (fabovea | gabovea | lastfabovea | lastgabovea) & ( - ~is_pergola + dzprev = dz + vegsh2 = np.add( + np.add( + np.add(fabovea, gabovea, dtype=float), lastfabovea, dtype=float + ), + lastgabovea, + dtype=float, ) - vegsh2 = vegsh2.float() + vegsh2[vegsh2 == 4] = 0.0 + # vegsh2[vegsh2 == 1] = 0. # This one is the ultimate question... + vegsh2[vegsh2 > 0] = 1.0 - # Accumulation of vegetation shadows - vegsh = torch.maximum(vegsh, vegsh2) - vegsh = torch.where( - vegsh * sh > 0, torch.tensor(0.0, device=device), vegsh - ) - vbshvegsh.add_(vegsh) + vegsh = np.fmax(vegsh, vegsh2) + vegsh[vegsh * sh > 0] = 0 + # removing shadows 'behind' buildings + vbshvegsh = np.copy(vegsh) + vbshvegsh + + index += 1 sh = 1 - sh - vbshvegsh = torch.where( - vbshvegsh > 0, torch.tensor(1.0, device=device), vbshvegsh - ) + vbshvegsh[vbshvegsh > 0] = 1 vbshvegsh = vbshvegsh - vegsh - vegsh = torch.where(vegsh > 0, torch.tensor(1.0, device=device), vegsh) + vegsh[vegsh > 0] = 1 shvoveg = (shvoveg - a) * vegsh # Vegetation shadow volume vegsh = 1 - vegsh vbshvegsh = 1 - vbshvegsh - - # print(torch.max(shvoveg)) + # print(np.max(shvoveg)) wallsh, wallsun, wallshve, facesh, facesun = shade_on_walls( - azimuth, aspect, walls, a, f, shvoveg, device + azimuth, aspect, walls, a, f, shvoveg ) - # print(torch.max(wallshve)) + # print(np.max(wallshve)) if walls_scheme is not False: wallsh_, wallsun_, wallshve_, facesh_, facesun_ = shade_on_walls( - azimuth, aspect_scheme, walls_scheme, a, f, shvoveg, device + azimuth, aspect_scheme, walls_scheme, a, f, shvoveg ) - # print(torch.max(wallshve_)) - shade_on_wall = wallsh_.clone() + # print(np.max(wallshve_)) + shade_on_wall = wallsh_.copy() shade_on_wall[shade_on_wall < wallshve_] = wallshve_[ shade_on_wall < wallshve_ ] + # return vegsh, sh, vbshvegsh, wallsh, wallsun, wallshve, facesh, facesun, + # shade_on_wall return ( ( vegsh, diff --git a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23_torch.py b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23_torch.py new file mode 100644 index 0000000..d7ff229 --- /dev/null +++ b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23_torch.py @@ -0,0 +1,249 @@ +from __future__ import division + +try: + import torch + import torch.nn.functional as F +except: + pass + +# import matplotlib.pylab as plt + + +def shade_on_walls(azimuth, aspect, walls, dsm, f, shvoveg, device): + cos_incidence = torch.cos(aspect - azimuth) + facesh = (cos_incidence <= 0).float() * (walls > 0).float() + + shvo = f - dsm + + facesun = ((facesh == 0) & (walls > 0)).float() + + wallsun = walls - shvo + wallsun = torch.clamp(wallsun, min=0.0) + wallsun = torch.where( + facesh == 1, torch.tensor(0.0, device=device), wallsun + ) + + wallsh = walls - wallsun + + wallshve = shvoveg * (walls > 0).float() + wallshve = torch.clamp(wallshve - wallsh, min=0.0) + + mask_clip = wallshve > walls + wallshve[mask_clip] = walls[mask_clip].to(dtype=wallshve.dtype) + + wallsun = wallsun - wallshve + mask_neg = wallsun < 0 + wallshve[mask_neg] = 0 + wallsun[mask_neg] = 0 + + return wallsh, wallsun, wallshve, facesh, facesun + + +def shadowingfunction_wallheight_23( + a, + vegdem, + vegdem2, + azimuth, + altitude, + scale, + amaxvalue, + bush, + walls, + aspect, + walls_scheme=False, + aspect_scheme=False, +): + """ + This function calculates shadows on a DSM and shadow height on building + walls including both buildings and vegetion units. + New functionallity to deal with pergolas, August 2021 + + INPUTS: + a = DSM + vegdem = Vegetation canopy DSM (magl) + vegdem2 = Trunkzone DSM (magl) + azimuth and altitude = sun position + scale= scale of DSM (1 meter pixels=1, 2 meter pixels=0.5) + walls= pixel row 'outside' buildings. will be calculated if empty + aspect=normal aspect of walls + + OUTPUT: + sh=ground and roof shadow + + wallsh = height of wall that is in shadow + wallsun = hieght of wall that is in sun + + original Matlab code: + Fredrik Lindberg 2013-08-14 + fredrikl@gvc.gu.se + + :param a: + :param vegdem: + :param vegdem2: + :param azimuth: + :param altitude: + :param scale: + :param amaxvalue: + :param bush: + :param walls: + :param aspect: + :return: + """ + + # automatically get the device to use from the input + device = a.device if isinstance(a, torch.Tensor) else torch.device("cpu") + degrees = torch.pi / 180.0 + azimuth *= degrees + altitude *= degrees + + sizex, sizey = a.shape + bushplant = (bush > 1).float() + + # Tensor initialisation + sh = torch.zeros((sizex, sizey), device=device) + vbshvegsh = torch.zeros((sizex, sizey), device=device) + vegsh = torch.zeros((sizex, sizey), device=device) + bushplant + f = a.clone() + shvoveg = vegdem.clone() + + # ---coordinates drag the layer --- + y, x = torch.meshgrid( + torch.linspace(-1, 1, sizex, device=device), + torch.linspace(-1, 1, sizey, device=device), + indexing="ij", + ) + grille_base = torch.stack((x, y), dim=-1).unsqueeze(0) + + # Preparation of the layers packed for grid_sample + a_4d = a.unsqueeze(0).unsqueeze(0).float() + veg_4d = vegdem.unsqueeze(0).unsqueeze(0).float() + veg2_4d = vegdem2.unsqueeze(0).unsqueeze(0).float() + + # Trigonometric params for the sun's step + sinazimuth = torch.sin(azimuth) + cosazimuth = torch.cos(azimuth) + tanaltitudebyscale = torch.tan(altitude) / scale + + # Calculation of the maximum of steps + # We stop when the shadow exceed the max possible height + max_steps = int(amaxvalue / tanaltitudebyscale) + 2 + + # Facteur d'avancement du rayon (équivalent géométrique de ton ancien 'ds') + # Plus besoin de gros blocs If/Else selon les quadrants du soleil ! + delta_z_par_pas = tanaltitudebyscale + + for index in range(1, max_steps): + + # 1. Calculations of the slide + shift_x = index * sinazimuth / scale + shift_y = index * cosazimuth / scale + dz = index * delta_z_par_pas + + # Stop if the shadow is completely outside of the map + if abs(shift_x) >= sizey and abs(shift_y) >= sizex: + break + + # 2. Applying the offset to our normalized coordinate sheet (-1 to 1) + grille_deplacee = grille_base.clone() + grille_deplacee[..., 0] -= (2.0 * shift_x) / (sizey - 1) + grille_deplacee[..., 1] -= (2.0 * shift_y) / (sizex - 1) + + # 3. Global Material Sampling (Instant Layer Swipe) + temp = ( + F.grid_sample( + a_4d, grille_deplacee, mode="bilinear", padding_mode="border" + ).squeeze() + - dz + ) + tempvegdem = ( + F.grid_sample( + veg_4d, grille_deplacee, mode="bilinear", padding_mode="border" + ).squeeze() + - dz + ) + tempvegdem2 = ( + F.grid_sample( + veg2_4d, + grille_deplacee, + mode="bilinear", + padding_mode="border", + ).squeeze() + - dz + ) + + # 4. Update of cast shadow volumes + f = torch.fmax(f, temp) + shvoveg = torch.fmax(shvoveg, tempvegdem) + + sh = torch.where( + f > a, + torch.tensor(1.0, device=device), + torch.tensor(0.0, device=device), + ) + fabovea = tempvegdem > a + gabovea = tempvegdem2 > a + + # 5. Pergola logic (Overlaying layers with the & operator) + templastfabovea = tempvegdem + delta_z_par_pas + templastgabovea = tempvegdem2 + delta_z_par_pas + + lastfabovea = templastfabovea > a + lastgabovea = templastgabovea > a + + # If all 4 layers are True at the same time, it's a pergola (light passes through). + is_pergola = fabovea & gabovea & lastfabovea & lastgabovea + + # The shadow exists if one of the layers is True, UNLESS it's a pergola. + vegsh2 = (fabovea | gabovea | lastfabovea | lastgabovea) & ( + ~is_pergola + ) + vegsh2 = vegsh2.float() + + # Accumulation of vegetation shadows + vegsh = torch.maximum(vegsh, vegsh2) + vegsh = torch.where( + vegsh * sh > 0, torch.tensor(0.0, device=device), vegsh + ) + vbshvegsh.add_(vegsh) + + sh = 1 - sh + vbshvegsh = torch.where( + vbshvegsh > 0, torch.tensor(1.0, device=device), vbshvegsh + ) + vbshvegsh = vbshvegsh - vegsh + + vegsh = torch.where(vegsh > 0, torch.tensor(1.0, device=device), vegsh) + shvoveg = (shvoveg - a) * vegsh # Vegetation shadow volume + vegsh = 1 - vegsh + vbshvegsh = 1 - vbshvegsh + + # print(torch.max(shvoveg)) + wallsh, wallsun, wallshve, facesh, facesun = shade_on_walls( + azimuth, aspect, walls, a, f, shvoveg, device + ) + # print(torch.max(wallshve)) + if walls_scheme is not False: + wallsh_, wallsun_, wallshve_, facesh_, facesun_ = shade_on_walls( + azimuth, aspect_scheme, walls_scheme, a, f, shvoveg, device + ) + # print(torch.max(wallshve_)) + shade_on_wall = wallsh_.clone() + shade_on_wall[shade_on_wall < wallshve_] = wallshve_[ + shade_on_wall < wallshve_ + ] + + return ( + ( + vegsh, + sh, + vbshvegsh, + wallsh, + wallsun, + wallshve, + facesh, + facesun, + shade_on_wall, + ) + if walls_scheme is not False + else (vegsh, sh, vbshvegsh, wallsh, wallsun, wallshve, facesh, facesun) + ) diff --git a/util/SEBESOLWEIGCommonFiles/sun_distance.py b/util/SEBESOLWEIGCommonFiles/sun_distance.py index 003e479..a46a91d 100644 --- a/util/SEBESOLWEIGCommonFiles/sun_distance.py +++ b/util/SEBESOLWEIGCommonFiles/sun_distance.py @@ -1,5 +1,5 @@ __author__ = "xlinfr" -import torch +import numpy as np def sun_distance(jday): @@ -9,14 +9,14 @@ def sun_distance(jday): #% with day of year as input. #% Partridge and Platt, 1975 """ - b = 2.0 * torch.pi * jday / 365.0 - D = torch.sqrt( + b = 2.0 * np.pi * jday / 365.0 + D = np.sqrt( ( 1.00011 - + 0.034221 * torch.cos(b) - + 0.001280 * torch.sin(b) - + 0.000719 * torch.cos(2.0 * b) - + 0.000077 * torch.sin(2.0 * b) + + np.dot(0.034221, np.cos(b)) + + np.dot(0.001280, np.sin(b)) + + np.dot(0.000719, np.cos((2.0 * b))) + + np.dot(0.000077, np.sin((2.0 * b))) ) ) return D diff --git a/util/SEBESOLWEIGCommonFiles/sun_distance_torch.py b/util/SEBESOLWEIGCommonFiles/sun_distance_torch.py new file mode 100644 index 0000000..58d8ffd --- /dev/null +++ b/util/SEBESOLWEIGCommonFiles/sun_distance_torch.py @@ -0,0 +1,24 @@ +__author__ = "xlinfr" +try: + import torch +except: + pass + +def sun_distance(jday): + """ + + #% Calculatesrelative earth sun distance + #% with day of year as input. + #% Partridge and Platt, 1975 + """ + b = 2.0 * torch.pi * jday / 365.0 + D = torch.sqrt( + ( + 1.00011 + + 0.034221 * torch.cos(b) + + 0.001280 * torch.sin(b) + + 0.000719 * torch.cos(2.0 * b) + + 0.000077 * torch.sin(2.0 * b) + ) + ) + return D diff --git a/util/SEBESOLWEIGCommonFiles/sun_position.py b/util/SEBESOLWEIGCommonFiles/sun_position.py index 1f309ff..f9664bc 100644 --- a/util/SEBESOLWEIGCommonFiles/sun_position.py +++ b/util/SEBESOLWEIGCommonFiles/sun_position.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import division from __future__ import print_function -import torch +import numpy as np def sun_position(time, location): @@ -150,7 +150,8 @@ def sun_position(time, location): apparent_sun_longitude, true_obliquity, sun_geocentric_position ) - # 11. Calculate the observer local hour angle (in degrees, westward from south). + # 11. Calculate the observer local hour angle (in degrees, westward from + # south). observer_local_hour = observer_local_hour_calculation( apparent_stime_at_greenwich, location, sun_rigth_ascension ) @@ -179,7 +180,6 @@ def sun_position(time, location): def julian_calculation(t_input): - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") """ % This function compute the julian day and julian century from the local % time and timezone information. Ephemeris are calculated with a delta_t=0 @@ -204,20 +204,21 @@ def julian_calculation(t_input): time = t_input if time["month"] == 1 or time["month"] == 2: - Y = torch.tensor(time["year"] - 1, device=device) - M = torch.tensor(time["month"] + 12, device=device) + Y = time["year"] - 1 + M = time["month"] + 12 else: - Y = torch.tensor(time["year"], device=device) - M = torch.tensor(time["month"], device=device) + Y = time["year"] + M = time["month"] - ut_time = torch.tensor( + # time of day in UT time. + ut_time = ( ((time["hour"] - time["UTC"]) / 24) + (time["min"] / (60 * 24)) - + (time["sec"] / (60 * 60 * 24)), - device=device, - ) # time of day in UT time. + + (time["sec"] / (60 * 60 * 24)) + ) + # Day of month in decimal time, ex. 2sd day of month at 12:30:30UT, + # D=2.521180556 D = time["day"] + ut_time - # Day of month in decimal time, ex. 2sd day of month at 12:30:30UT, D=2.521180556 # In 1582, the gregorian calendar was adopted if time["year"] == 1582: @@ -225,36 +226,36 @@ def julian_calculation(t_input): if ( time["day"] <= 4 ): # The Julian calendar ended on October 4, 1582 - B = torch.tensor(0, device=device) + B = 0 elif ( time["day"] >= 15 ): # The Gregorian calendar started on October 15, 1582 - A = torch.floor(Y / 100) - B = 2 - A + torch.floor(A / 4) + A = np.floor(Y / 100) + B = 2 - A + np.floor(A / 4) else: print( "This date never existed!. Date automatically set to October 4, 1582" ) time["month"] = 10 time["day"] = 4 - B = torch.tensor(0, device=device) + B = 0 elif time["month"] < 10: # Julian calendar - B = torch.tensor(0, device=device) + B = 0 else: # Gregorian calendar - A = torch.floor(Y / 100) - B = 2 - A + torch.floor(A / 4) + A = np.floor(Y / 100) + B = 2 - A + np.floor(A / 4) elif time["year"] < 1582: # Julian calendar - B = torch.tensor(0, device=device) + B = 0 else: - A = torch.floor(Y / 100) # Gregorian calendar - B = 2 - A + torch.floor(A / 4) + A = np.floor(Y / 100) # Gregorian calendar + B = 2 - A + np.floor(A / 4) julian = dict() julian["day"] = ( D + B - + torch.floor(365.25 * (Y + 4716)) - + torch.floor(30.6001 * (M + 1)) + + np.floor(365.25 * (Y + 4716)) + + np.floor(30.6001 * (M + 1)) - 1524.5 ) @@ -268,7 +269,6 @@ def julian_calculation(t_input): def earth_heliocentric_position_calculation(julian): - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") """ % This function compute the earth position relative to the sun, using % tabulated values. @@ -278,7 +278,7 @@ def earth_heliocentric_position_calculation(julian): """ # Tabulated values for the longitude calculation # L terms from the original code. - L0_terms = torch.tensor( + L0_terms = np.array( [ [175347046.0, 0, 0], [3341656.0, 4.6692568, 6283.07585], @@ -344,11 +344,10 @@ def earth_heliocentric_position_calculation(julian): [30, 0.44, 83996.85], [30, 2.74, 1349.87], [25, 3.16, 4690.48], - ], - device=device, + ] ) - L1_terms = torch.tensor( + L1_terms = np.array( [ [628331966747.0, 0, 0], [206059.0, 2.678235, 6283.07585], @@ -384,11 +383,10 @@ def earth_heliocentric_position_calculation(julian): [8, 5.3, 2352.87], [6, 2.65, 9437.76], [6, 4.67, 4690.48], - ], - device=device, + ] ) - L2_terms = torch.tensor( + L2_terms = np.array( [ [52919.0, 0, 0], [8720.0, 1.0721, 6283.0758], @@ -410,11 +408,10 @@ def earth_heliocentric_position_calculation(julian): [3, 2.28, 553.57], [2, 4.38, 5223.69], [2, 3.75, 0.98], - ], - device=device, + ] ) - L3_terms = torch.tensor( + L3_terms = np.array( [ [289.0, 5.844, 6283.076], [35, 0, 0], @@ -423,18 +420,15 @@ def earth_heliocentric_position_calculation(julian): [1, 4.72, 3.52], [1, 5.3, 18849.23], [1, 5.97, 242.73], - ], - device=device, + ] ) - L4_terms = torch.tensor( - [[114.0, 3.142, 0], [8, 4.13, 6283.08], [1, 3.84, 12566.15]], - device=device, + L4_terms = np.array( + [[114.0, 3.142, 0], [8, 4.13, 6283.08], [1, 3.84, 12566.15]] ) - L5_terms = torch.tensor([1, 3.14, 0], device=device) - L5_terms = torch.atleast_2d( - L5_terms - ) # since L5_terms is 1D, we have to convert it to 2D to avoid indexErrors + L5_terms = np.array([1, 3.14, 0]) + # since L5_terms is 1D, we have to convert it to 2D to avoid indexErrors + L5_terms = np.atleast_2d(L5_terms) A0 = L0_terms[:, 0] B0 = L0_terms[:, 1] @@ -463,25 +457,25 @@ def earth_heliocentric_position_calculation(julian): JME = julian["ephemeris_millenium"] # Compute the Earth Heliochentric longitude from the tabulated values. - L0 = torch.sum(A0 * torch.cos(B0 + (C0 * JME))) - L1 = torch.sum(A1 * torch.cos(B1 + (C1 * JME))) - L2 = torch.sum(A2 * torch.cos(B2 + (C2 * JME))) - L3 = torch.sum(A3 * torch.cos(B3 + (C3 * JME))) - L4 = torch.sum(A4 * torch.cos(B4 + (C4 * JME))) - L5 = A5 * torch.cos(B5 + (C5 * JME)) + L0 = np.sum(A0 * np.cos(B0 + (C0 * JME))) + L1 = np.sum(A1 * np.cos(B1 + (C1 * JME))) + L2 = np.sum(A2 * np.cos(B2 + (C2 * JME))) + L3 = np.sum(A3 * np.cos(B3 + (C3 * JME))) + L4 = np.sum(A4 * np.cos(B4 + (C4 * JME))) + L5 = A5 * np.cos(B5 + (C5 * JME)) earth_heliocentric_position = dict() earth_heliocentric_position["longitude"] = ( L0 + (L1 * JME) - + (L2 * torch.pow(JME, 2)) - + (L3 * torch.pow(JME, 3)) - + (L4 * torch.pow(JME, 4)) - + (L5 * torch.pow(JME, 5)) + + (L2 * np.power(JME, 2)) + + (L3 * np.power(JME, 3)) + + (L4 * np.power(JME, 4)) + + (L5 * np.power(JME, 5)) ) / 1e8 # Convert the longitude to degrees. earth_heliocentric_position["longitude"] = ( - earth_heliocentric_position["longitude"] * 180 / torch.pi + earth_heliocentric_position["longitude"] * 180 / np.pi ) # Limit the range to [0,360] @@ -491,20 +485,17 @@ def earth_heliocentric_position_calculation(julian): # Tabulated values for the earth heliocentric latitude. # B terms from the original code. - B0_terms = torch.tensor( + B0_terms = np.array( [ [280.0, 3.199, 84334.662], [102.0, 5.422, 5507.553], [80, 3.88, 5223.69], [44, 3.7, 2352.87], [32, 4, 1577.34], - ], - device=device, + ] ) - B1_terms = torch.tensor( - [[9, 3.9, 5507.55], [6, 1.73, 5223.69]], device=device - ) + B1_terms = np.array([[9, 3.9, 5507.55], [6, 1.73, 5223.69]]) A0 = B0_terms[:, 0] B0 = B0_terms[:, 1] @@ -514,14 +505,14 @@ def earth_heliocentric_position_calculation(julian): B1 = B1_terms[:, 1] C1 = B1_terms[:, 2] - L0 = torch.sum(A0 * torch.cos(B0 + (C0 * JME))) - L1 = torch.sum(A1 * torch.cos(B1 + (C1 * JME))) + L0 = np.sum(A0 * np.cos(B0 + (C0 * JME))) + L1 = np.sum(A1 * np.cos(B1 + (C1 * JME))) earth_heliocentric_position["latitude"] = (L0 + (L1 * JME)) / 1e8 # Convert the latitude to degrees. earth_heliocentric_position["latitude"] = ( - earth_heliocentric_position["latitude"] * 180 / torch.pi + earth_heliocentric_position["latitude"] * 180 / np.pi ) # Limit the range to [0,360]; @@ -531,7 +522,7 @@ def earth_heliocentric_position_calculation(julian): # Tabulated values for radius vector. # R terms from the original code - R0_terms = torch.tensor( + R0_terms = np.array( [ [100013989.0, 0, 0], [1670700.0, 3.0984635, 6283.07585], @@ -573,11 +564,10 @@ def earth_heliocentric_position_calculation(julian): [28, 1.21, 6286.6], [28, 1.9, 6279.55], [26, 4.59, 10447.39], - ], - device=device, + ] ) - R1_terms = torch.tensor( + R1_terms = np.array( [ [103019.0, 1.10749, 6283.07585], [1721.0, 1.0644, 12566.1517], @@ -589,11 +579,10 @@ def earth_heliocentric_position_calculation(julian): [10, 5.91, 10977.08], [9, 1.42, 6275.96], [9, 0.27, 5486.78], - ], - device=device, + ] ) - R2_terms = torch.tensor( + R2_terms = np.array( [ [4359.0, 5.7846, 6283.0758], [124.0, 5.579, 12566.152], @@ -601,19 +590,14 @@ def earth_heliocentric_position_calculation(julian): [9, 3.63, 77713.77], [6, 1.87, 5573.14], [3, 5.47, 18849], - ], - device=device, + ] ) - R3_terms = torch.tensor( - [[145.0, 4.273, 6283.076], [7, 3.92, 12566.15]], device=device - ) + R3_terms = np.array([[145.0, 4.273, 6283.076], [7, 3.92, 12566.15]]) R4_terms = [4, 2.56, 6283.08] - # Force it to be a tensor first, then ensure it's 2D - R4_terms = torch.atleast_2d( - torch.tensor(R4_terms, device=device) - ) # since L5_terms is 1D, we have to convert it to 2D to avoid indexErrors + # since L5_terms is 1D, we have to convert it to 2D to avoid indexErrors + R4_terms = np.atleast_2d(R4_terms) A0 = R0_terms[:, 0] B0 = R0_terms[:, 1] @@ -636,19 +620,19 @@ def earth_heliocentric_position_calculation(julian): C4 = R4_terms[:, 2] # Compute the Earth heliocentric radius vector - L0 = torch.sum(A0 * torch.cos(B0 + (C0 * JME))) - L1 = torch.sum(A1 * torch.cos(B1 + (C1 * JME))) - L2 = torch.sum(A2 * torch.cos(B2 + (C2 * JME))) - L3 = torch.sum(A3 * torch.cos(B3 + (C3 * JME))) - L4 = A4 * torch.cos(B4 + (C4 * JME)) + L0 = np.sum(A0 * np.cos(B0 + (C0 * JME))) + L1 = np.sum(A1 * np.cos(B1 + (C1 * JME))) + L2 = np.sum(A2 * np.cos(B2 + (C2 * JME))) + L3 = np.sum(A3 * np.cos(B3 + (C3 * JME))) + L4 = A4 * np.cos(B4 + (C4 * JME)) # Units are in AU earth_heliocentric_position["radius"] = ( L0 + (L1 * JME) - + (L2 * torch.pow(JME, 2)) - + (L3 * torch.pow(JME, 3)) - + (L4 * torch.pow(JME, 4)) + + (L2 * np.power(JME, 2)) + + (L3 * np.power(JME, 3)) + + (L4 * np.power(JME, 4)) ) / 1e8 return earth_heliocentric_position @@ -678,7 +662,6 @@ def sun_geocentric_position_calculation(earth_heliocentric_position): def nutation_calculation(julian): - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") """ % This function compute the nutation in longtitude and in obliquity, in % degrees. @@ -690,83 +673,64 @@ def nutation_calculation(julian): JCE = julian["ephemeris_century"] # 1. Mean elongation of the moon from the sun - p = torch.atleast_2d( - torch.tensor( - [(1 / 189474), -0.0019142, 445267.11148, 297.85036], device=device - ) - ) + p = np.atleast_2d([(1 / 189474), -0.0019142, 445267.11148, 297.85036]) # X0 = polyval(p, JCE); + # This is faster than polyval... X0 = ( - p[0, 0] * torch.pow(JCE, 3) - + p[0, 1] * torch.pow(JCE, 2) + p[0, 0] * np.power(JCE, 3) + + p[0, 1] * np.power(JCE, 2) + p[0, 2] * JCE + p[0, 3] - ) # This is faster than polyval... + ) # 2. Mean anomaly of the sun (earth) - p = torch.atleast_2d( - torch.tensor( - [-(1 / 300000), -0.0001603, 35999.05034, 357.52772], device=device - ) - ) + p = np.atleast_2d([-(1 / 300000), -0.0001603, 35999.05034, 357.52772]) # X1 = polyval(p, JCE) X1 = ( - p[0, 0] * torch.pow(JCE, 3) - + p[0, 1] * torch.pow(JCE, 2) + p[0, 0] * np.power(JCE, 3) + + p[0, 1] * np.power(JCE, 2) + p[0, 2] * JCE + p[0, 3] ) # 3. Mean anomaly of the moon - p = torch.atleast_2d( - torch.tensor( - [(1 / 56250), 0.0086972, 477198.867398, 134.96298], device=device - ) - ) + p = np.atleast_2d([(1 / 56250), 0.0086972, 477198.867398, 134.96298]) # X2 = polyval(p, JCE); X2 = ( - p[0, 0] * torch.pow(JCE, 3) - + p[0, 1] * torch.pow(JCE, 2) + p[0, 0] * np.power(JCE, 3) + + p[0, 1] * np.power(JCE, 2) + p[0, 2] * JCE + p[0, 3] ) # 4. Moon argument of latitude - p = torch.atleast_2d( - torch.tensor( - [(1 / 327270), -0.0036825, 483202.017538, 93.27191], device=device - ) - ) + p = np.atleast_2d([(1 / 327270), -0.0036825, 483202.017538, 93.27191]) # X3 = polyval(p, JCE) X3 = ( - p[0, 0] * torch.pow(JCE, 3) - + p[0, 1] * torch.pow(JCE, 2) + p[0, 0] * np.power(JCE, 3) + + p[0, 1] * np.power(JCE, 2) + p[0, 2] * JCE + p[0, 3] ) # 5. Longitude of the ascending node of the moon's mean orbit on the # ecliptic, measured from the mean equinox of the date - p = torch.atleast_2d( - torch.tensor( - [(1 / 450000), 0.0020708, -1934.136261, 125.04452], device=device - ) - ) + p = np.atleast_2d([(1 / 450000), 0.0020708, -1934.136261, 125.04452]) # X4 = polyval(p, JCE); X4 = ( - p[0, 0] * torch.pow(JCE, 3) - + p[0, 1] * torch.pow(JCE, 2) + p[0, 0] * np.power(JCE, 3) + + p[0, 1] * np.power(JCE, 2) + p[0, 2] * JCE + p[0, 3] ) # Y tabulated terms from the original code - Y_terms = torch.tensor( + Y_terms = np.array( [ [0, 0, 0, 0, 1], [-2, 0, 0, 2, 2], @@ -831,11 +795,10 @@ def nutation_calculation(julian): [2, -1, -1, 2, 2], [0, 0, 3, 2, 2], [2, -1, 0, 2, 2], - ], - device=device, + ] ) - nutation_terms = torch.tensor( + nutation_terms = np.array( [ [-171996, -174.2, 92025, 8.9], [-13187, -1.6, 5736, -3.1], @@ -900,37 +863,33 @@ def nutation_calculation(julian): [-3, 0, 0, 0], [-3, 0, 0, 0], [-3, 0, 0, 0], - ], - device=device, + ] ) # Using the tabulated values, compute the delta_longitude and # delta_obliquity. - Xi = torch.tensor( - [X0, X1, X2, X3, X4], device=device - ) # a col mat in octave + Xi = np.array([X0, X1, X2, X3, X4]) # a col mat in octave - tabulated_argument = torch.matmul(Y_terms.float(), Xi) * (torch.pi / 180) + tabulated_argument = Y_terms.dot(np.transpose(Xi)) * (np.pi / 180) delta_longitude = ( nutation_terms[:, 0] + (nutation_terms[:, 1] * JCE) - ) * torch.sin(tabulated_argument) + ) * np.sin(tabulated_argument) delta_obliquity = ( nutation_terms[:, 2] + (nutation_terms[:, 3] * JCE) - ) * torch.cos(tabulated_argument) + ) * np.cos(tabulated_argument) nutation = dict() # init nutation dictionary # Nutation in longitude - nutation["longitude"] = torch.sum(delta_longitude) / 36000000 + nutation["longitude"] = np.sum(delta_longitude) / 36000000 # Nutation in obliquity - nutation["obliquity"] = torch.sum(delta_obliquity) / 36000000 + nutation["obliquity"] = np.sum(delta_obliquity) / 36000000 return nutation def true_obliquity_calculation(julian, nutation): - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") """ This function compute the true obliquity of the ecliptic. @@ -939,37 +898,34 @@ def true_obliquity_calculation(julian, nutation): :return: """ - p = torch.atleast_2d( - torch.tensor( - [ - 2.45, - 5.79, - 27.87, - 7.12, - -39.05, - -249.67, - -51.38, - 1999.25, - -1.55, - -4680.93, - 84381.448, - ], - device=device, - ) + p = np.atleast_2d( + [ + 2.45, + 5.79, + 27.87, + 7.12, + -39.05, + -249.67, + -51.38, + 1999.25, + -1.55, + -4680.93, + 84381.448, + ] ) # mean_obliquity = polyval(p, julian.ephemeris_millenium/10); U = julian["ephemeris_millenium"] / 10 mean_obliquity = ( - p[0, 0] * torch.pow(U, 10) - + p[0, 1] * torch.pow(U, 9) - + p[0, 2] * torch.pow(U, 8) - + p[0, 3] * torch.pow(U, 7) - + p[0, 4] * torch.pow(U, 6) - + p[0, 5] * torch.pow(U, 5) - + p[0, 6] * torch.pow(U, 4) - + p[0, 7] * torch.pow(U, 3) - + p[0, 8] * torch.pow(U, 2) + p[0, 0] * np.power(U, 10) + + p[0, 1] * np.power(U, 9) + + p[0, 2] * np.power(U, 8) + + p[0, 3] * np.power(U, 7) + + p[0, 4] * np.power(U, 6) + + p[0, 5] * np.power(U, 5) + + p[0, 6] * np.power(U, 4) + + p[0, 7] * np.power(U, 3) + + p[0, 8] * np.power(U, 2) + p[0, 9] * U + p[0, 10] ) @@ -1029,15 +985,15 @@ def apparent_stime_at_greenwich_calculation(julian, nutation, true_obliquity): mean_stime = ( 280.46061837 + (360.98564736629 * (JD - 2451545)) - + (0.000387933 * torch.pow(JC, 2)) - - (torch.pow(JC, 3) / 38710000) + + (0.000387933 * np.power(JC, 2)) + - (np.power(JC, 3) / 38710000) ) # Limit the range to [0-360]; mean_stime = set_to_range(mean_stime, 0, 360) apparent_stime_at_greenwich = mean_stime + ( - nutation["longitude"] * torch.cos(true_obliquity * torch.pi / 180) + nutation["longitude"] * np.cos(true_obliquity * np.pi / 180) ) return apparent_stime_at_greenwich @@ -1054,18 +1010,16 @@ def sun_rigth_ascension_calculation( """ argument_numerator = ( - torch.sin(apparent_sun_longitude * torch.pi / 180) - * torch.cos(true_obliquity * torch.pi / 180) + np.sin(apparent_sun_longitude * np.pi / 180) + * np.cos(true_obliquity * np.pi / 180) ) - ( - torch.tan(sun_geocentric_position["latitude"] * torch.pi / 180) - * torch.sin(true_obliquity * torch.pi / 180) + np.tan(sun_geocentric_position["latitude"] * np.pi / 180) + * np.sin(true_obliquity * np.pi / 180) ) - argument_denominator = torch.cos(apparent_sun_longitude * torch.pi / 180) + argument_denominator = np.cos(apparent_sun_longitude * np.pi / 180) sun_rigth_ascension = ( - torch.arctan2(argument_numerator, argument_denominator) - * 180 - / torch.pi + np.arctan2(argument_numerator, argument_denominator) * 180 / np.pi ) # Limit the range to [0,360]; sun_rigth_ascension = set_to_range(sun_rigth_ascension, 0, 360) @@ -1084,15 +1038,15 @@ def sun_geocentric_declination_calculation( """ argument = ( - torch.sin(sun_geocentric_position["latitude"] * torch.pi / 180) - * torch.cos(true_obliquity * torch.pi / 180) + np.sin(sun_geocentric_position["latitude"] * np.pi / 180) + * np.cos(true_obliquity * np.pi / 180) ) + ( - torch.cos(sun_geocentric_position["latitude"] * torch.pi / 180) - * torch.sin(true_obliquity * torch.pi / 180) - * torch.sin(apparent_sun_longitude * torch.pi / 180) + np.cos(sun_geocentric_position["latitude"] * np.pi / 180) + * np.sin(true_obliquity * np.pi / 180) + * np.sin(apparent_sun_longitude * np.pi / 180) ) - sun_geocentric_declination = torch.arcsin(argument) * 180 / torch.pi + sun_geocentric_declination = np.arcsin(argument) * 180 / np.pi return sun_geocentric_declination @@ -1143,56 +1097,53 @@ def topocentric_sun_position_calculate( ) # Term u, used in the following calculations (in radians) - u = torch.arctan( - 0.99664719 - * torch.tan(torch.as_tensor(location["latitude"]) * torch.pi / 180) - ) + u = np.arctan(0.99664719 * np.tan(location["latitude"] * np.pi / 180)) # Term x, used in the following calculations - x = torch.cos(u) + ( - (torch.as_tensor(location["altitude"]) / 6378140) - * torch.cos(torch.as_tensor(location["latitude"]) * torch.pi / 180) + x = np.cos(u) + ( + (location["altitude"] / 6378140) + * np.cos(location["latitude"] * np.pi / 180) ) # Term y, used in the following calculations - y = (0.99664719 * torch.sin(u)) + ( - (torch.as_tensor(location["altitude"]) / 6378140) - * torch.sin(torch.as_tensor(location["latitude"]) * torch.pi / 180) + y = (0.99664719 * np.sin(u)) + ( + (location["altitude"] / 6378140) + * np.sin(location["latitude"] * np.pi / 180) ) # Parallax in the sun rigth ascension (in radians) nominator = ( -x - * torch.sin(eq_horizontal_parallax * torch.pi / 180) - * torch.sin(observer_local_hour * torch.pi / 180) + * np.sin(eq_horizontal_parallax * np.pi / 180) + * np.sin(observer_local_hour * np.pi / 180) ) - denominator = torch.cos(sun_geocentric_declination * torch.pi / 180) - ( + denominator = np.cos(sun_geocentric_declination * np.pi / 180) - ( x - * torch.sin(eq_horizontal_parallax * torch.pi / 180) - * torch.cos(observer_local_hour * torch.pi / 180) + * np.sin(eq_horizontal_parallax * np.pi / 180) + * np.cos(observer_local_hour * np.pi / 180) ) - sun_rigth_ascension_parallax = torch.arctan2(nominator, denominator) + sun_rigth_ascension_parallax = np.arctan2(nominator, denominator) # Conversion to degrees. topocentric_sun_position = dict() topocentric_sun_position["rigth_ascension_parallax"] = ( - sun_rigth_ascension_parallax * 180 / torch.pi + sun_rigth_ascension_parallax * 180 / np.pi ) # Topocentric sun rigth ascension (in degrees) topocentric_sun_position["rigth_ascension"] = sun_rigth_ascension + ( - sun_rigth_ascension_parallax * 180 / torch.pi + sun_rigth_ascension_parallax * 180 / np.pi ) # Topocentric sun declination (in degrees) nominator = ( - torch.sin(sun_geocentric_declination * torch.pi / 180) - - (y * torch.sin(eq_horizontal_parallax * torch.pi / 180)) - ) * torch.cos(sun_rigth_ascension_parallax) - denominator = torch.cos(sun_geocentric_declination * torch.pi / 180) - ( - y * torch.sin(eq_horizontal_parallax * torch.pi / 180) - ) * torch.cos(observer_local_hour * torch.pi / 180) + np.sin(sun_geocentric_declination * np.pi / 180) + - (y * np.sin(eq_horizontal_parallax * np.pi / 180)) + ) * np.cos(sun_rigth_ascension_parallax) + denominator = np.cos(sun_geocentric_declination * np.pi / 180) - ( + y * np.sin(eq_horizontal_parallax * np.pi / 180) + ) * np.cos(observer_local_hour * np.pi / 180) topocentric_sun_position["declination"] = ( - torch.arctan2(nominator, denominator) * 180 / torch.pi + np.arctan2(nominator, denominator) * 180 / np.pi ) return topocentric_sun_position @@ -1231,26 +1182,18 @@ def sun_topocentric_zenith_angle_calculate( # Topocentric elevation, without atmospheric refraction argument = ( - torch.sin(torch.as_tensor(location["latitude"]) * torch.pi / 180) - * torch.sin( - torch.as_tensor(topocentric_sun_position["declination"]) - * torch.pi - / 180 - ) + np.sin(location["latitude"] * np.pi / 180) + * np.sin(topocentric_sun_position["declination"] * np.pi / 180) ) + ( - torch.cos(torch.as_tensor(location["latitude"]) * torch.pi / 180) - * torch.cos( - torch.as_tensor(topocentric_sun_position["declination"]) - * torch.pi - / 180 - ) - * torch.cos(torch.as_tensor(topocentric_local_hour) * torch.pi / 180) + np.cos(location["latitude"] * np.pi / 180) + * np.cos(topocentric_sun_position["declination"] * np.pi / 180) + * np.cos(topocentric_local_hour * np.pi / 180) ) - true_elevation = torch.arcsin(argument) * 180 / torch.pi + true_elevation = np.arcsin(argument) * 180 / np.pi # Atmospheric refraction correction (in degrees) argument = true_elevation + (10.3 / (true_elevation + 5.11)) - refraction_corr = 1.02 / (60 * torch.tan(argument * torch.pi / 180)) + refraction_corr = 1.02 / (60 * np.tan(argument * np.pi / 180)) # For exact pressure and temperature correction, use this, # with P the pressure in mbar amd T the temperature in Kelvins: @@ -1265,23 +1208,15 @@ def sun_topocentric_zenith_angle_calculate( # Topocentric azimuth angle. The +180 conversion is to pass from astronomer # notation (westward from south) to navigation notation (eastward from # north); - nominator = torch.sin( - torch.as_tensor(topocentric_local_hour * torch.pi / 180) - ) + nominator = np.sin(topocentric_local_hour * np.pi / 180) denominator = ( - torch.cos(torch.as_tensor(topocentric_local_hour) * torch.pi / 180) - * torch.sin(torch.as_tensor(location["latitude"]) * torch.pi / 180) + np.cos(topocentric_local_hour * np.pi / 180) + * np.sin(location["latitude"] * np.pi / 180) ) - ( - torch.tan( - torch.as_tensor(topocentric_sun_position["declination"]) - * torch.pi - / 180 - ) - * torch.cos(torch.as_tensor(location["latitude"]) * torch.pi / 180) + np.tan(topocentric_sun_position["declination"] * np.pi / 180) + * np.cos(location["latitude"] * np.pi / 180) ) - sun["azimuth"] = ( - torch.arctan2(nominator, denominator) * 180 / torch.pi - ) + 180 + sun["azimuth"] = (np.arctan2(nominator, denominator) * 180 / np.pi) + 180 # Set the range to [0-360] sun["azimuth"] = set_to_range(sun["azimuth"], 0, 360) @@ -1297,7 +1232,7 @@ def set_to_range(var, min_interval, max_interval): :param max_interval: :return: """ - var = var - max_interval * torch.floor(var / max_interval) + var = var - max_interval * np.floor(var / max_interval) if var < min_interval: var = var + max_interval diff --git a/util/SEBESOLWEIGCommonFiles/sun_position_torch.py b/util/SEBESOLWEIGCommonFiles/sun_position_torch.py new file mode 100644 index 0000000..f61f4ef --- /dev/null +++ b/util/SEBESOLWEIGCommonFiles/sun_position_torch.py @@ -0,0 +1,1306 @@ +# -*- coding: utf-8 -*- +from __future__ import division +from __future__ import print_function +try: + import torch +except: + pass + +def sun_position(time, location): + """ + % sun = sun_position(time, location) + % + % This function compute the sun position (zenith and azimuth angle at the observer + % location) as a function of the observer local time and position. + % + % It is an implementation of the algorithm presented by Reda et Andreas in: + % Reda, I., Andreas, A. (2003) Solar position algorithm for solar + % radiation application. National Renewable Energy Laboratory (NREL) + % Technical report NREL/TP-560-34302. + % This document is avalaible at www.osti.gov/bridge + % + % This algorithm is based on numerical approximation of the exact equations. + % The authors of the original paper state that this algorithm should be + % precise at +/- 0.0003 degrees. I have compared it to NOAA solar table + % (http://www.srrb.noaa.gov/highlights/sunrise/azel.html) and to USNO solar + % table (http://aa.usno.navy.mil/data/docs/AltAz.html) and found very good + % correspondance (up to the precision of those tables), except for large + % zenith angle, where the refraction by the atmosphere is significant + % (difference of about 1 degree). Note that in this code the correction + % for refraction in the atmosphere as been implemented for a temperature + % of 10C (283 kelvins) and a pressure of 1010 mbar. See the subfunction + % �sun_topocentric_zenith_angle_calculation� for a possible modification + % to explicitely model the effect of temperature and pressure as describe + % in Reda & Andreas (2003). + % + % Input parameters: + % time: a structure that specify the time when the sun position is + % calculated. + % time.year: year. Valid for [-2000, 6000] + % time.month: month [1-12] + % time.day: calendar day [1-31] + % time.hour: local hour [0-23] + % time.min: minute [0-59] + % time.sec: second [0-59] + % time.UTC: offset hour from UTC. Local time = Greenwich time + time.UTC + % This input can also be passed using the Matlab time format ('dd-mmm-yyyy HH:MM:SS'). + % In that case, the time has to be specified as UTC time (time.UTC = 0) + % + % location: a structure that specify the location of the observer + % location.latitude: latitude (in degrees, north of equator is + % positive) + % location.longitude: longitude (in degrees, positive for east of + % Greenwich) + % location.altitude: altitude above mean sea level (in meters) + % + % Output parameters + % sun: a structure with the calculated sun position + % sun.zenith = zenith angle in degrees (angle from the vertical) + % sun.azimuth = azimuth angle in degrees, eastward from the north. + % Only the sun zenith and azimuth angles are returned as output, but a lot + % of other parameters are calculated that could also extracted as output of + % this function. + % + % Exemple of use + % + % location.longitude = -105.1786; + % location.latitude = 39.742476; + % location.altitude = 1830.14; + % time.year = 2005; + % time.month = 10; + % time.day = 17; + % time.hour = 6; + % time.min = 30; + % time.sec = 30; + % time.UTC = -7; + % % + % location.longitude = 11.94; + % location.latitude = 57.70; + % location.altitude = 3.0; + % time.UTC = 1; + % sun = sun_position(time, location); + % + % sun = + % + % zenith: 50.1080438859849 + % azimuth: 194.341174010338 + % + % History + % 09/03/2004 Original creation by Vincent Roy (vincent.roy@drdc-rddc.gc.ca) + % 10/03/2004 Fixed a bug in julian_calculation subfunction (was + % incorrect for year 1582 only), Vincent Roy + % 18/03/2004 Correction to the header (help display) only. No changes to + % the code. (changed the �elevation� field in �location� structure + % information to �altitude�), Vincent Roy + % 13/04/2004 Following a suggestion from Jody Klymak (jklymak@ucsd.edu), + % allowed the 'time' input to be passed as a Matlab time string. + % 22/08/2005 Following a bug report from Bruce Bowler + % (bbowler@bigelow.org), modified the julian_calculation function. Bug + % was 'MATLAB has allowed structure assignment to a non-empty non-structure + % to overwrite the previous value. This behavior will continue in this release, + % but will be an error in a future version of MATLAB. For advice on how to + % write code that will both avoid this warning and work in future versions of + % MATLAB, see R14SP2 Release Notes'. Script should now be + % compliant with futher release of Matlab... + """ + + # 1. Calculate the Julian Day, and Century. Julian Ephemeris day, century + # and millenium are calculated using a mean delta_t of 33.184 seconds. + julian = julian_calculation(time) + # print(julian) + + # 2. Calculate the Earth heliocentric longitude, latitude, and radius + # vector (L, B, and R) + earth_heliocentric_position = earth_heliocentric_position_calculation( + julian + ) + + # 3. Calculate the geocentric longitude and latitude + sun_geocentric_position = sun_geocentric_position_calculation( + earth_heliocentric_position + ) + + # 4. Calculate the nutation in longitude and obliquity (in degrees). + nutation = nutation_calculation(julian) + + # 5. Calculate the true obliquity of the ecliptic (in degrees). + true_obliquity = true_obliquity_calculation(julian, nutation) + + # 6. Calculate the aberration correction (in degrees) + aberration_correction = abberation_correction_calculation( + earth_heliocentric_position + ) + + # 7. Calculate the apparent sun longitude in degrees) + apparent_sun_longitude = apparent_sun_longitude_calculation( + sun_geocentric_position, nutation, aberration_correction + ) + + # 8. Calculate the apparent sideral time at Greenwich (in degrees) + apparent_stime_at_greenwich = apparent_stime_at_greenwich_calculation( + julian, nutation, true_obliquity + ) + + # 9. Calculate the sun rigth ascension (in degrees) + sun_rigth_ascension = sun_rigth_ascension_calculation( + apparent_sun_longitude, true_obliquity, sun_geocentric_position + ) + + # 10. Calculate the geocentric sun declination (in degrees). Positive or + # negative if the sun is north or south of the celestial equator. + sun_geocentric_declination = sun_geocentric_declination_calculation( + apparent_sun_longitude, true_obliquity, sun_geocentric_position + ) + + # 11. Calculate the observer local hour angle (in degrees, westward from south). + observer_local_hour = observer_local_hour_calculation( + apparent_stime_at_greenwich, location, sun_rigth_ascension + ) + + # 12. Calculate the topocentric sun position (rigth ascension, declination and + # rigth ascension parallax in degrees) + topocentric_sun_position = topocentric_sun_position_calculate( + earth_heliocentric_position, + location, + observer_local_hour, + sun_rigth_ascension, + sun_geocentric_declination, + ) + + # 13. Calculate the topocentric local hour angle (in degrees) + topocentric_local_hour = topocentric_local_hour_calculate( + observer_local_hour, topocentric_sun_position + ) + + # 14. Calculate the topocentric zenith and azimuth angle (in degrees) + sun = sun_topocentric_zenith_angle_calculate( + location, topocentric_sun_position, topocentric_local_hour + ) + + return sun + + +def julian_calculation(t_input): + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + """ + % This function compute the julian day and julian century from the local + % time and timezone information. Ephemeris are calculated with a delta_t=0 + % seconds. + + % If time input is a Matlab time string, extract the information from + % this string and create the structure as defined in the main header of + % this script. + """ + if not isinstance(t_input, dict): + # tt = datetime.datetime.strptime(t_input, "%Y-%m-%d %H:%M:%S.%f") # if t_input is a string of this format + # t_input should be a datetime object + time = dict() + time["UTC"] = 0 + time["year"] = t_input.year + time["month"] = t_input.month + time["day"] = t_input.day + time["hour"] = t_input.hour + time["min"] = t_input.minute + time["sec"] = t_input.second + else: + time = t_input + + if time["month"] == 1 or time["month"] == 2: + Y = torch.tensor(time["year"] - 1, device=device) + M = torch.tensor(time["month"] + 12, device=device) + else: + Y = torch.tensor(time["year"], device=device) + M = torch.tensor(time["month"], device=device) + + ut_time = torch.tensor( + ((time["hour"] - time["UTC"]) / 24) + + (time["min"] / (60 * 24)) + + (time["sec"] / (60 * 60 * 24)), + device=device, + ) # time of day in UT time. + D = time["day"] + ut_time + # Day of month in decimal time, ex. 2sd day of month at 12:30:30UT, D=2.521180556 + + # In 1582, the gregorian calendar was adopted + if time["year"] == 1582: + if time["month"] == 10: + if ( + time["day"] <= 4 + ): # The Julian calendar ended on October 4, 1582 + B = torch.tensor(0, device=device) + elif ( + time["day"] >= 15 + ): # The Gregorian calendar started on October 15, 1582 + A = torch.floor(Y / 100) + B = 2 - A + torch.floor(A / 4) + else: + print( + "This date never existed!. Date automatically set to October 4, 1582" + ) + time["month"] = 10 + time["day"] = 4 + B = torch.tensor(0, device=device) + elif time["month"] < 10: # Julian calendar + B = torch.tensor(0, device=device) + else: # Gregorian calendar + A = torch.floor(Y / 100) + B = 2 - A + torch.floor(A / 4) + elif time["year"] < 1582: # Julian calendar + B = torch.tensor(0, device=device) + else: + A = torch.floor(Y / 100) # Gregorian calendar + B = 2 - A + torch.floor(A / 4) + + julian = dict() + julian["day"] = ( + D + + B + + torch.floor(365.25 * (Y + 4716)) + + torch.floor(30.6001 * (M + 1)) + - 1524.5 + ) + + delta_t = 0 # 33.184; + julian["ephemeris_day"] = (julian["day"]) + (delta_t / 86400) + julian["century"] = (julian["day"] - 2451545) / 36525 + julian["ephemeris_century"] = (julian["ephemeris_day"] - 2451545) / 36525 + julian["ephemeris_millenium"] = (julian["ephemeris_century"]) / 10 + + return julian + + +def earth_heliocentric_position_calculation(julian): + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + """ + % This function compute the earth position relative to the sun, using + % tabulated values. + + % Tabulated values for the longitude calculation + % L terms from the original code. + """ + # Tabulated values for the longitude calculation + # L terms from the original code. + L0_terms = torch.tensor( + [ + [175347046.0, 0, 0], + [3341656.0, 4.6692568, 6283.07585], + [34894.0, 4.6261, 12566.1517], + [3497.0, 2.7441, 5753.3849], + [3418.0, 2.8289, 3.5231], + [3136.0, 3.6277, 77713.7715], + [2676.0, 4.4181, 7860.4194], + [2343.0, 6.1352, 3930.2097], + [1324.0, 0.7425, 11506.7698], + [1273.0, 2.0371, 529.691], + [1199.0, 1.1096, 1577.3435], + [990, 5.233, 5884.927], + [902, 2.045, 26.298], + [857, 3.508, 398.149], + [780, 1.179, 5223.694], + [753, 2.533, 5507.553], + [505, 4.583, 18849.228], + [492, 4.205, 775.523], + [357, 2.92, 0.067], + [317, 5.849, 11790.629], + [284, 1.899, 796.298], + [271, 0.315, 10977.079], + [243, 0.345, 5486.778], + [206, 4.806, 2544.314], + [205, 1.869, 5573.143], + [202, 2.4458, 6069.777], + [156, 0.833, 213.299], + [132, 3.411, 2942.463], + [126, 1.083, 20.775], + [115, 0.645, 0.98], + [103, 0.636, 4694.003], + [102, 0.976, 15720.839], + [102, 4.267, 7.114], + [99, 6.21, 2146.17], + [98, 0.68, 155.42], + [86, 5.98, 161000.69], + [85, 1.3, 6275.96], + [85, 3.67, 71430.7], + [80, 1.81, 17260.15], + [79, 3.04, 12036.46], + [71, 1.76, 5088.63], + [74, 3.5, 3154.69], + [74, 4.68, 801.82], + [70, 0.83, 9437.76], + [62, 3.98, 8827.39], + [61, 1.82, 7084.9], + [57, 2.78, 6286.6], + [56, 4.39, 14143.5], + [56, 3.47, 6279.55], + [52, 0.19, 12139.55], + [52, 1.33, 1748.02], + [51, 0.28, 5856.48], + [49, 0.49, 1194.45], + [41, 5.37, 8429.24], + [41, 2.4, 19651.05], + [39, 6.17, 10447.39], + [37, 6.04, 10213.29], + [37, 2.57, 1059.38], + [36, 1.71, 2352.87], + [36, 1.78, 6812.77], + [33, 0.59, 17789.85], + [30, 0.44, 83996.85], + [30, 2.74, 1349.87], + [25, 3.16, 4690.48], + ], + device=device, + ) + + L1_terms = torch.tensor( + [ + [628331966747.0, 0, 0], + [206059.0, 2.678235, 6283.07585], + [4303.0, 2.6351, 12566.1517], + [425.0, 1.59, 3.523], + [119.0, 5.796, 26.298], + [109.0, 2.966, 1577.344], + [93, 2.59, 18849.23], + [72, 1.14, 529.69], + [68, 1.87, 398.15], + [67, 4.41, 5507.55], + [59, 2.89, 5223.69], + [56, 2.17, 155.42], + [45, 0.4, 796.3], + [36, 0.47, 775.52], + [29, 2.65, 7.11], + [21, 5.34, 0.98], + [19, 1.85, 5486.78], + [19, 4.97, 213.3], + [17, 2.99, 6275.96], + [16, 0.03, 2544.31], + [16, 1.43, 2146.17], + [15, 1.21, 10977.08], + [12, 2.83, 1748.02], + [12, 3.26, 5088.63], + [12, 5.27, 1194.45], + [12, 2.08, 4694], + [11, 0.77, 553.57], + [10, 1.3, 3286.6], + [10, 4.24, 1349.87], + [9, 2.7, 242.73], + [9, 5.64, 951.72], + [8, 5.3, 2352.87], + [6, 2.65, 9437.76], + [6, 4.67, 4690.48], + ], + device=device, + ) + + L2_terms = torch.tensor( + [ + [52919.0, 0, 0], + [8720.0, 1.0721, 6283.0758], + [309.0, 0.867, 12566.152], + [27, 0.05, 3.52], + [16, 5.19, 26.3], + [16, 3.68, 155.42], + [10, 0.76, 18849.23], + [9, 2.06, 77713.77], + [7, 0.83, 775.52], + [5, 4.66, 1577.34], + [4, 1.03, 7.11], + [4, 3.44, 5573.14], + [3, 5.14, 796.3], + [3, 6.05, 5507.55], + [3, 1.19, 242.73], + [3, 6.12, 529.69], + [3, 0.31, 398.15], + [3, 2.28, 553.57], + [2, 4.38, 5223.69], + [2, 3.75, 0.98], + ], + device=device, + ) + + L3_terms = torch.tensor( + [ + [289.0, 5.844, 6283.076], + [35, 0, 0], + [17, 5.49, 12566.15], + [3, 5.2, 155.42], + [1, 4.72, 3.52], + [1, 5.3, 18849.23], + [1, 5.97, 242.73], + ], + device=device, + ) + L4_terms = torch.tensor( + [[114.0, 3.142, 0], [8, 4.13, 6283.08], [1, 3.84, 12566.15]], + device=device, + ) + + L5_terms = torch.tensor([1, 3.14, 0], device=device) + L5_terms = torch.atleast_2d( + L5_terms + ) # since L5_terms is 1D, we have to convert it to 2D to avoid indexErrors + + A0 = L0_terms[:, 0] + B0 = L0_terms[:, 1] + C0 = L0_terms[:, 2] + + A1 = L1_terms[:, 0] + B1 = L1_terms[:, 1] + C1 = L1_terms[:, 2] + + A2 = L2_terms[:, 0] + B2 = L2_terms[:, 1] + C2 = L2_terms[:, 2] + + A3 = L3_terms[:, 0] + B3 = L3_terms[:, 1] + C3 = L3_terms[:, 2] + + A4 = L4_terms[:, 0] + B4 = L4_terms[:, 1] + C4 = L4_terms[:, 2] + + A5 = L5_terms[:, 0] + B5 = L5_terms[:, 1] + C5 = L5_terms[:, 2] + + JME = julian["ephemeris_millenium"] + + # Compute the Earth Heliochentric longitude from the tabulated values. + L0 = torch.sum(A0 * torch.cos(B0 + (C0 * JME))) + L1 = torch.sum(A1 * torch.cos(B1 + (C1 * JME))) + L2 = torch.sum(A2 * torch.cos(B2 + (C2 * JME))) + L3 = torch.sum(A3 * torch.cos(B3 + (C3 * JME))) + L4 = torch.sum(A4 * torch.cos(B4 + (C4 * JME))) + L5 = A5 * torch.cos(B5 + (C5 * JME)) + + earth_heliocentric_position = dict() + earth_heliocentric_position["longitude"] = ( + L0 + + (L1 * JME) + + (L2 * torch.pow(JME, 2)) + + (L3 * torch.pow(JME, 3)) + + (L4 * torch.pow(JME, 4)) + + (L5 * torch.pow(JME, 5)) + ) / 1e8 + # Convert the longitude to degrees. + earth_heliocentric_position["longitude"] = ( + earth_heliocentric_position["longitude"] * 180 / torch.pi + ) + + # Limit the range to [0,360] + earth_heliocentric_position["longitude"] = set_to_range( + earth_heliocentric_position["longitude"], 0, 360 + ) + + # Tabulated values for the earth heliocentric latitude. + # B terms from the original code. + B0_terms = torch.tensor( + [ + [280.0, 3.199, 84334.662], + [102.0, 5.422, 5507.553], + [80, 3.88, 5223.69], + [44, 3.7, 2352.87], + [32, 4, 1577.34], + ], + device=device, + ) + + B1_terms = torch.tensor( + [[9, 3.9, 5507.55], [6, 1.73, 5223.69]], device=device + ) + + A0 = B0_terms[:, 0] + B0 = B0_terms[:, 1] + C0 = B0_terms[:, 2] + + A1 = B1_terms[:, 0] + B1 = B1_terms[:, 1] + C1 = B1_terms[:, 2] + + L0 = torch.sum(A0 * torch.cos(B0 + (C0 * JME))) + L1 = torch.sum(A1 * torch.cos(B1 + (C1 * JME))) + + earth_heliocentric_position["latitude"] = (L0 + (L1 * JME)) / 1e8 + + # Convert the latitude to degrees. + earth_heliocentric_position["latitude"] = ( + earth_heliocentric_position["latitude"] * 180 / torch.pi + ) + + # Limit the range to [0,360]; + earth_heliocentric_position["latitude"] = set_to_range( + earth_heliocentric_position["latitude"], 0, 360 + ) + + # Tabulated values for radius vector. + # R terms from the original code + R0_terms = torch.tensor( + [ + [100013989.0, 0, 0], + [1670700.0, 3.0984635, 6283.07585], + [13956.0, 3.05525, 12566.1517], + [3084.0, 5.1985, 77713.7715], + [1628.0, 1.1739, 5753.3849], + [1576.0, 2.8469, 7860.4194], + [925.0, 5.453, 11506.77], + [542.0, 4.564, 3930.21], + [472.0, 3.661, 5884.927], + [346.0, 0.964, 5507.553], + [329.0, 5.9, 5223.694], + [307.0, 0.299, 5573.143], + [243.0, 4.273, 11790.629], + [212.0, 5.847, 1577.344], + [186.0, 5.022, 10977.079], + [175.0, 3.012, 18849.228], + [110.0, 5.055, 5486.778], + [98, 0.89, 6069.78], + [86, 5.69, 15720.84], + [86, 1.27, 161000.69], + [85, 0.27, 17260.15], + [63, 0.92, 529.69], + [57, 2.01, 83996.85], + [56, 5.24, 71430.7], + [49, 3.25, 2544.31], + [47, 2.58, 775.52], + [45, 5.54, 9437.76], + [43, 6.01, 6275.96], + [39, 5.36, 4694], + [38, 2.39, 8827.39], + [37, 0.83, 19651.05], + [37, 4.9, 12139.55], + [36, 1.67, 12036.46], + [35, 1.84, 2942.46], + [33, 0.24, 7084.9], + [32, 0.18, 5088.63], + [32, 1.78, 398.15], + [28, 1.21, 6286.6], + [28, 1.9, 6279.55], + [26, 4.59, 10447.39], + ], + device=device, + ) + + R1_terms = torch.tensor( + [ + [103019.0, 1.10749, 6283.07585], + [1721.0, 1.0644, 12566.1517], + [702.0, 3.142, 0], + [32, 1.02, 18849.23], + [31, 2.84, 5507.55], + [25, 1.32, 5223.69], + [18, 1.42, 1577.34], + [10, 5.91, 10977.08], + [9, 1.42, 6275.96], + [9, 0.27, 5486.78], + ], + device=device, + ) + + R2_terms = torch.tensor( + [ + [4359.0, 5.7846, 6283.0758], + [124.0, 5.579, 12566.152], + [12, 3.14, 0], + [9, 3.63, 77713.77], + [6, 1.87, 5573.14], + [3, 5.47, 18849], + ], + device=device, + ) + + R3_terms = torch.tensor( + [[145.0, 4.273, 6283.076], [7, 3.92, 12566.15]], device=device + ) + + R4_terms = [4, 2.56, 6283.08] + # Force it to be a tensor first, then ensure it's 2D + R4_terms = torch.atleast_2d( + torch.tensor(R4_terms, device=device) + ) # since L5_terms is 1D, we have to convert it to 2D to avoid indexErrors + + A0 = R0_terms[:, 0] + B0 = R0_terms[:, 1] + C0 = R0_terms[:, 2] + + A1 = R1_terms[:, 0] + B1 = R1_terms[:, 1] + C1 = R1_terms[:, 2] + + A2 = R2_terms[:, 0] + B2 = R2_terms[:, 1] + C2 = R2_terms[:, 2] + + A3 = R3_terms[:, 0] + B3 = R3_terms[:, 1] + C3 = R3_terms[:, 2] + + A4 = R4_terms[:, 0] + B4 = R4_terms[:, 1] + C4 = R4_terms[:, 2] + + # Compute the Earth heliocentric radius vector + L0 = torch.sum(A0 * torch.cos(B0 + (C0 * JME))) + L1 = torch.sum(A1 * torch.cos(B1 + (C1 * JME))) + L2 = torch.sum(A2 * torch.cos(B2 + (C2 * JME))) + L3 = torch.sum(A3 * torch.cos(B3 + (C3 * JME))) + L4 = A4 * torch.cos(B4 + (C4 * JME)) + + # Units are in AU + earth_heliocentric_position["radius"] = ( + L0 + + (L1 * JME) + + (L2 * torch.pow(JME, 2)) + + (L3 * torch.pow(JME, 3)) + + (L4 * torch.pow(JME, 4)) + ) / 1e8 + + return earth_heliocentric_position + + +def sun_geocentric_position_calculation(earth_heliocentric_position): + """ + % This function compute the sun position relative to the earth. + """ + sun_geocentric_position = dict() + sun_geocentric_position["longitude"] = ( + earth_heliocentric_position["longitude"] + 180 + ) + # Limit the range to [0,360]; + sun_geocentric_position["longitude"] = set_to_range( + sun_geocentric_position["longitude"], 0, 360 + ) + + sun_geocentric_position["latitude"] = -earth_heliocentric_position[ + "latitude" + ] + # Limit the range to [0,360] + sun_geocentric_position["latitude"] = set_to_range( + sun_geocentric_position["latitude"], 0, 360 + ) + return sun_geocentric_position + + +def nutation_calculation(julian): + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + """ + % This function compute the nutation in longtitude and in obliquity, in + % degrees. + :param julian: + :return: nutation + """ + + # All Xi are in degrees. + JCE = julian["ephemeris_century"] + + # 1. Mean elongation of the moon from the sun + p = torch.atleast_2d( + torch.tensor( + [(1 / 189474), -0.0019142, 445267.11148, 297.85036], device=device + ) + ) + + # X0 = polyval(p, JCE); + X0 = ( + p[0, 0] * torch.pow(JCE, 3) + + p[0, 1] * torch.pow(JCE, 2) + + p[0, 2] * JCE + + p[0, 3] + ) # This is faster than polyval... + + # 2. Mean anomaly of the sun (earth) + p = torch.atleast_2d( + torch.tensor( + [-(1 / 300000), -0.0001603, 35999.05034, 357.52772], device=device + ) + ) + + # X1 = polyval(p, JCE) + X1 = ( + p[0, 0] * torch.pow(JCE, 3) + + p[0, 1] * torch.pow(JCE, 2) + + p[0, 2] * JCE + + p[0, 3] + ) + + # 3. Mean anomaly of the moon + p = torch.atleast_2d( + torch.tensor( + [(1 / 56250), 0.0086972, 477198.867398, 134.96298], device=device + ) + ) + + # X2 = polyval(p, JCE); + X2 = ( + p[0, 0] * torch.pow(JCE, 3) + + p[0, 1] * torch.pow(JCE, 2) + + p[0, 2] * JCE + + p[0, 3] + ) + + # 4. Moon argument of latitude + p = torch.atleast_2d( + torch.tensor( + [(1 / 327270), -0.0036825, 483202.017538, 93.27191], device=device + ) + ) + + # X3 = polyval(p, JCE) + X3 = ( + p[0, 0] * torch.pow(JCE, 3) + + p[0, 1] * torch.pow(JCE, 2) + + p[0, 2] * JCE + + p[0, 3] + ) + + # 5. Longitude of the ascending node of the moon's mean orbit on the + # ecliptic, measured from the mean equinox of the date + p = torch.atleast_2d( + torch.tensor( + [(1 / 450000), 0.0020708, -1934.136261, 125.04452], device=device + ) + ) + + # X4 = polyval(p, JCE); + X4 = ( + p[0, 0] * torch.pow(JCE, 3) + + p[0, 1] * torch.pow(JCE, 2) + + p[0, 2] * JCE + + p[0, 3] + ) + + # Y tabulated terms from the original code + Y_terms = torch.tensor( + [ + [0, 0, 0, 0, 1], + [-2, 0, 0, 2, 2], + [0, 0, 0, 2, 2], + [0, 0, 0, 0, 2], + [0, 1, 0, 0, 0], + [0, 0, 1, 0, 0], + [-2, 1, 0, 2, 2], + [0, 0, 0, 2, 1], + [0, 0, 1, 2, 2], + [-2, -1, 0, 2, 2], + [-2, 0, 1, 0, 0], + [-2, 0, 0, 2, 1], + [0, 0, -1, 2, 2], + [2, 0, 0, 0, 0], + [0, 0, 1, 0, 1], + [2, 0, -1, 2, 2], + [0, 0, -1, 0, 1], + [0, 0, 1, 2, 1], + [-2, 0, 2, 0, 0], + [0, 0, -2, 2, 1], + [2, 0, 0, 2, 2], + [0, 0, 2, 2, 2], + [0, 0, 2, 0, 0], + [-2, 0, 1, 2, 2], + [0, 0, 0, 2, 0], + [-2, 0, 0, 2, 0], + [0, 0, -1, 2, 1], + [0, 2, 0, 0, 0], + [2, 0, -1, 0, 1], + [-2, 2, 0, 2, 2], + [0, 1, 0, 0, 1], + [-2, 0, 1, 0, 1], + [0, -1, 0, 0, 1], + [0, 0, 2, -2, 0], + [2, 0, -1, 2, 1], + [2, 0, 1, 2, 2], + [0, 1, 0, 2, 2], + [-2, 1, 1, 0, 0], + [0, -1, 0, 2, 2], + [2, 0, 0, 2, 1], + [2, 0, 1, 0, 0], + [-2, 0, 2, 2, 2], + [-2, 0, 1, 2, 1], + [2, 0, -2, 0, 1], + [2, 0, 0, 0, 1], + [0, -1, 1, 0, 0], + [-2, -1, 0, 2, 1], + [-2, 0, 0, 0, 1], + [0, 0, 2, 2, 1], + [-2, 0, 2, 0, 1], + [-2, 1, 0, 2, 1], + [0, 0, 1, -2, 0], + [-1, 0, 1, 0, 0], + [-2, 1, 0, 0, 0], + [1, 0, 0, 0, 0], + [0, 0, 1, 2, 0], + [0, 0, -2, 2, 2], + [-1, -1, 1, 0, 0], + [0, 1, 1, 0, 0], + [0, -1, 1, 2, 2], + [2, -1, -1, 2, 2], + [0, 0, 3, 2, 2], + [2, -1, 0, 2, 2], + ], + device=device, + ) + + nutation_terms = torch.tensor( + [ + [-171996, -174.2, 92025, 8.9], + [-13187, -1.6, 5736, -3.1], + [-2274, -0.2, 977, -0.5], + [2062, 0.2, -895, 0.5], + [1426, -3.4, 54, -0.1], + [712, 0.1, -7, 0], + [-517, 1.2, 224, -0.6], + [-386, -0.4, 200, 0], + [-301, 0, 129, -0.1], + [217, -0.5, -95, 0.3], + [-158, 0, 0, 0], + [129, 0.1, -70, 0], + [123, 0, -53, 0], + [63, 0, 0, 0], + [63, 0.1, -33, 0], + [-59, 0, 26, 0], + [-58, -0.1, 32, 0], + [-51, 0, 27, 0], + [48, 0, 0, 0], + [46, 0, -24, 0], + [-38, 0, 16, 0], + [-31, 0, 13, 0], + [29, 0, 0, 0], + [29, 0, -12, 0], + [26, 0, 0, 0], + [-22, 0, 0, 0], + [21, 0, -10, 0], + [17, -0.1, 0, 0], + [16, 0, -8, 0], + [-16, 0.1, 7, 0], + [-15, 0, 9, 0], + [-13, 0, 7, 0], + [-12, 0, 6, 0], + [11, 0, 0, 0], + [-10, 0, 5, 0], + [-8, 0, 3, 0], + [7, 0, -3, 0], + [-7, 0, 0, 0], + [-7, 0, 3, 0], + [-7, 0, 3, 0], + [6, 0, 0, 0], + [6, 0, -3, 0], + [6, 0, -3, 0], + [-6, 0, 3, 0], + [-6, 0, 3, 0], + [5, 0, 0, 0], + [-5, 0, 3, 0], + [-5, 0, 3, 0], + [-5, 0, 3, 0], + [4, 0, 0, 0], + [4, 0, 0, 0], + [4, 0, 0, 0], + [-4, 0, 0, 0], + [-4, 0, 0, 0], + [-4, 0, 0, 0], + [3, 0, 0, 0], + [-3, 0, 0, 0], + [-3, 0, 0, 0], + [-3, 0, 0, 0], + [-3, 0, 0, 0], + [-3, 0, 0, 0], + [-3, 0, 0, 0], + [-3, 0, 0, 0], + ], + device=device, + ) + + # Using the tabulated values, compute the delta_longitude and + # delta_obliquity. + Xi = torch.tensor( + [X0, X1, X2, X3, X4], device=device + ) # a col mat in octave + + tabulated_argument = torch.matmul(Y_terms.float(), Xi) * (torch.pi / 180) + + delta_longitude = ( + nutation_terms[:, 0] + (nutation_terms[:, 1] * JCE) + ) * torch.sin(tabulated_argument) + delta_obliquity = ( + nutation_terms[:, 2] + (nutation_terms[:, 3] * JCE) + ) * torch.cos(tabulated_argument) + + nutation = dict() # init nutation dictionary + # Nutation in longitude + nutation["longitude"] = torch.sum(delta_longitude) / 36000000 + + # Nutation in obliquity + nutation["obliquity"] = torch.sum(delta_obliquity) / 36000000 + + return nutation + + +def true_obliquity_calculation(julian, nutation): + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + """ + This function compute the true obliquity of the ecliptic. + + :param julian: + :param nutation: + :return: + """ + + p = torch.atleast_2d( + torch.tensor( + [ + 2.45, + 5.79, + 27.87, + 7.12, + -39.05, + -249.67, + -51.38, + 1999.25, + -1.55, + -4680.93, + 84381.448, + ], + device=device, + ) + ) + + # mean_obliquity = polyval(p, julian.ephemeris_millenium/10); + U = julian["ephemeris_millenium"] / 10 + mean_obliquity = ( + p[0, 0] * torch.pow(U, 10) + + p[0, 1] * torch.pow(U, 9) + + p[0, 2] * torch.pow(U, 8) + + p[0, 3] * torch.pow(U, 7) + + p[0, 4] * torch.pow(U, 6) + + p[0, 5] * torch.pow(U, 5) + + p[0, 6] * torch.pow(U, 4) + + p[0, 7] * torch.pow(U, 3) + + p[0, 8] * torch.pow(U, 2) + + p[0, 9] * U + + p[0, 10] + ) + + true_obliquity = (mean_obliquity / 3600) + nutation["obliquity"] + + return true_obliquity + + +def abberation_correction_calculation(earth_heliocentric_position): + """ + This function compute the aberration_correction, as a function of the + earth-sun distance. + + :param earth_heliocentric_position: + :return: + """ + aberration_correction = -20.4898 / ( + 3600 * earth_heliocentric_position["radius"] + ) + return aberration_correction + + +def apparent_sun_longitude_calculation( + sun_geocentric_position, nutation, aberration_correction +): + """ + This function compute the sun apparent longitude + + :param sun_geocentric_position: + :param nutation: + :param aberration_correction: + :return: + """ + apparent_sun_longitude = ( + sun_geocentric_position["longitude"] + + nutation["longitude"] + + aberration_correction + ) + return apparent_sun_longitude + + +def apparent_stime_at_greenwich_calculation(julian, nutation, true_obliquity): + """ + This function compute the apparent sideral time at Greenwich. + + :param julian: + :param nutation: + :param true_obliquity: + :return: + """ + + JD = julian["day"] + JC = julian["century"] + + # Mean sideral time, in degrees + mean_stime = ( + 280.46061837 + + (360.98564736629 * (JD - 2451545)) + + (0.000387933 * torch.pow(JC, 2)) + - (torch.pow(JC, 3) / 38710000) + ) + + # Limit the range to [0-360]; + mean_stime = set_to_range(mean_stime, 0, 360) + + apparent_stime_at_greenwich = mean_stime + ( + nutation["longitude"] * torch.cos(true_obliquity * torch.pi / 180) + ) + return apparent_stime_at_greenwich + + +def sun_rigth_ascension_calculation( + apparent_sun_longitude, true_obliquity, sun_geocentric_position +): + """ + This function compute the sun rigth ascension. + :param apparent_sun_longitude: + :param true_obliquity: + :param sun_geocentric_position: + :return: + """ + + argument_numerator = ( + torch.sin(apparent_sun_longitude * torch.pi / 180) + * torch.cos(true_obliquity * torch.pi / 180) + ) - ( + torch.tan(sun_geocentric_position["latitude"] * torch.pi / 180) + * torch.sin(true_obliquity * torch.pi / 180) + ) + argument_denominator = torch.cos(apparent_sun_longitude * torch.pi / 180) + + sun_rigth_ascension = ( + torch.arctan2(argument_numerator, argument_denominator) + * 180 + / torch.pi + ) + # Limit the range to [0,360]; + sun_rigth_ascension = set_to_range(sun_rigth_ascension, 0, 360) + return sun_rigth_ascension + + +def sun_geocentric_declination_calculation( + apparent_sun_longitude, true_obliquity, sun_geocentric_position +): + """ + + :param apparent_sun_longitude: + :param true_obliquity: + :param sun_geocentric_position: + :return: + """ + + argument = ( + torch.sin(sun_geocentric_position["latitude"] * torch.pi / 180) + * torch.cos(true_obliquity * torch.pi / 180) + ) + ( + torch.cos(sun_geocentric_position["latitude"] * torch.pi / 180) + * torch.sin(true_obliquity * torch.pi / 180) + * torch.sin(apparent_sun_longitude * torch.pi / 180) + ) + + sun_geocentric_declination = torch.arcsin(argument) * 180 / torch.pi + return sun_geocentric_declination + + +def observer_local_hour_calculation( + apparent_stime_at_greenwich, location, sun_rigth_ascension +): + """ + This function computes observer local hour. + + :param apparent_stime_at_greenwich: + :param location: + :param sun_rigth_ascension: + :return: + """ + + observer_local_hour = ( + apparent_stime_at_greenwich + + location["longitude"] + - sun_rigth_ascension + ) + # Set the range to [0-360] + observer_local_hour = set_to_range(observer_local_hour, 0, 360) + return observer_local_hour + + +def topocentric_sun_position_calculate( + earth_heliocentric_position, + location, + observer_local_hour, + sun_rigth_ascension, + sun_geocentric_declination, +): + """ + This function compute the sun position (rigth ascension and declination) + with respect to the observer local position at the Earth surface. + + :param earth_heliocentric_position: + :param location: + :param observer_local_hour: + :param sun_rigth_ascension: + :param sun_geocentric_declination: + :return: + """ + + # Equatorial horizontal parallax of the sun in degrees + eq_horizontal_parallax = 8.794 / ( + 3600 * earth_heliocentric_position["radius"] + ) + + # Term u, used in the following calculations (in radians) + u = torch.arctan( + 0.99664719 + * torch.tan(torch.as_tensor(location["latitude"]) * torch.pi / 180) + ) + + # Term x, used in the following calculations + x = torch.cos(u) + ( + (torch.as_tensor(location["altitude"]) / 6378140) + * torch.cos(torch.as_tensor(location["latitude"]) * torch.pi / 180) + ) + + # Term y, used in the following calculations + y = (0.99664719 * torch.sin(u)) + ( + (torch.as_tensor(location["altitude"]) / 6378140) + * torch.sin(torch.as_tensor(location["latitude"]) * torch.pi / 180) + ) + + # Parallax in the sun rigth ascension (in radians) + nominator = ( + -x + * torch.sin(eq_horizontal_parallax * torch.pi / 180) + * torch.sin(observer_local_hour * torch.pi / 180) + ) + denominator = torch.cos(sun_geocentric_declination * torch.pi / 180) - ( + x + * torch.sin(eq_horizontal_parallax * torch.pi / 180) + * torch.cos(observer_local_hour * torch.pi / 180) + ) + sun_rigth_ascension_parallax = torch.arctan2(nominator, denominator) + # Conversion to degrees. + topocentric_sun_position = dict() + topocentric_sun_position["rigth_ascension_parallax"] = ( + sun_rigth_ascension_parallax * 180 / torch.pi + ) + + # Topocentric sun rigth ascension (in degrees) + topocentric_sun_position["rigth_ascension"] = sun_rigth_ascension + ( + sun_rigth_ascension_parallax * 180 / torch.pi + ) + + # Topocentric sun declination (in degrees) + nominator = ( + torch.sin(sun_geocentric_declination * torch.pi / 180) + - (y * torch.sin(eq_horizontal_parallax * torch.pi / 180)) + ) * torch.cos(sun_rigth_ascension_parallax) + denominator = torch.cos(sun_geocentric_declination * torch.pi / 180) - ( + y * torch.sin(eq_horizontal_parallax * torch.pi / 180) + ) * torch.cos(observer_local_hour * torch.pi / 180) + topocentric_sun_position["declination"] = ( + torch.arctan2(nominator, denominator) * 180 / torch.pi + ) + return topocentric_sun_position + + +def topocentric_local_hour_calculate( + observer_local_hour, topocentric_sun_position +): + """ + This function compute the topocentric local jour angle in degrees + + :param observer_local_hour: + :param topocentric_sun_position: + :return: + """ + + topocentric_local_hour = ( + observer_local_hour + - topocentric_sun_position["rigth_ascension_parallax"] + ) + return topocentric_local_hour + + +def sun_topocentric_zenith_angle_calculate( + location, topocentric_sun_position, topocentric_local_hour +): + """ + This function compute the sun zenith angle, taking into account the + atmospheric refraction. A default temperature of 283K and a + default pressure of 1010 mbar are used. + + :param location: + :param topocentric_sun_position: + :param topocentric_local_hour: + :return: + """ + + # Topocentric elevation, without atmospheric refraction + argument = ( + torch.sin(torch.as_tensor(location["latitude"]) * torch.pi / 180) + * torch.sin( + torch.as_tensor(topocentric_sun_position["declination"]) + * torch.pi + / 180 + ) + ) + ( + torch.cos(torch.as_tensor(location["latitude"]) * torch.pi / 180) + * torch.cos( + torch.as_tensor(topocentric_sun_position["declination"]) + * torch.pi + / 180 + ) + * torch.cos(torch.as_tensor(topocentric_local_hour) * torch.pi / 180) + ) + true_elevation = torch.arcsin(argument) * 180 / torch.pi + + # Atmospheric refraction correction (in degrees) + argument = true_elevation + (10.3 / (true_elevation + 5.11)) + refraction_corr = 1.02 / (60 * torch.tan(argument * torch.pi / 180)) + + # For exact pressure and temperature correction, use this, + # with P the pressure in mbar amd T the temperature in Kelvins: + # refraction_corr = (P/1010) * (283/T) * 1.02 / (60 * tan(argument * pi/180)); + + # Apparent elevation + apparent_elevation = true_elevation + refraction_corr + + sun = dict() + sun["zenith"] = 90 - apparent_elevation + + # Topocentric azimuth angle. The +180 conversion is to pass from astronomer + # notation (westward from south) to navigation notation (eastward from + # north); + nominator = torch.sin( + torch.as_tensor(topocentric_local_hour * torch.pi / 180) + ) + denominator = ( + torch.cos(torch.as_tensor(topocentric_local_hour) * torch.pi / 180) + * torch.sin(torch.as_tensor(location["latitude"]) * torch.pi / 180) + ) - ( + torch.tan( + torch.as_tensor(topocentric_sun_position["declination"]) + * torch.pi + / 180 + ) + * torch.cos(torch.as_tensor(location["latitude"]) * torch.pi / 180) + ) + sun["azimuth"] = ( + torch.arctan2(nominator, denominator) * 180 / torch.pi + ) + 180 + + # Set the range to [0-360] + sun["azimuth"] = set_to_range(sun["azimuth"], 0, 360) + return sun + + +def set_to_range(var, min_interval, max_interval): + """ + Sets a variable in range min_interval and max_interval + + :param var: + :param min_interval: + :param max_interval: + :return: + """ + var = var - max_interval * torch.floor(var / max_interval) + + if var < min_interval: + var = var + max_interval + return var diff --git a/util/imageMorphometricParms_v2.py b/util/imageMorphometricParms_v2.py index 8bb0380..883853d 100644 --- a/util/imageMorphometricParms_v2.py +++ b/util/imageMorphometricParms_v2.py @@ -26,8 +26,6 @@ import numpy as np import scipy.ndimage.interpolation as sc -# import matplotlib as plt - def imagemorphparam_v2(dsm, dem, scale, mid, dtheta, feedback, imp_point): diff --git a/util/landCoverFractions_v2.py b/util/landCoverFractions_v2.py index 9c9c85f..6f6da28 100644 --- a/util/landCoverFractions_v2.py +++ b/util/landCoverFractions_v2.py @@ -35,16 +35,6 @@ def landcover_v2(lc_grid, mid, dtheta, feedback, imp_point, iter): lc_frac = np.zeros((int(360.0 / dtheta), iter)) deg = np.zeros((int(360.0 / dtheta), 1)) - # n = lc_grid.shape[0] - # imid = np.floor((n/2.)) - # if mid == 1: - # dY = np.int16(np.arange(np.dot(1, imid))) # the half length of the grid (y) - # else: #moved inside loop as it varies on an irregular grid - # dY = np.int16(np.arange(np.dot(1, n))) # the whole length of the grid (y) - - # dX = np.int16(np.arange(imid, imid+1)) - # lx = dX.shape[0] - # ly = dY.shape[0] j = int(0) for angle in np.arange(0, 360, dtheta): @@ -66,8 +56,6 @@ def landcover_v2(lc_grid, mid, dtheta, feedback, imp_point, iter): lineMid = d[:, int(imid)] # whole center line bld = lineMid[np.where(lineMid > 0)] # line within grid only - # b = np.round(((lc_grid.max()-lc_grid.min())/d.max())*d+lc_grid.min(), 0) #not needed anymore - # bld = b[dY, dX] # lc array ly = bld.shape[0] # number of pixels to consider in NtoS lx = 1 #!TODO should this consider full length (EtoW) of grid and if so, how? diff --git a/util/ncWMSConnector.py b/util/ncWMSConnector.py index 3ebfffd..469b9cb 100644 --- a/util/ncWMSConnector.py +++ b/util/ncWMSConnector.py @@ -10,7 +10,7 @@ import pandas as pd import numpy as np import netCDF4 as nc4 - from requests.auth import HTTPDigestAuth, HTTPBasicAuth + from requests.auth import HTTPDigestAuth import requests except: pass diff --git a/util/shadowingfunctions.py b/util/shadowingfunctions.py index 552e81f..6b8ce49 100644 --- a/util/shadowingfunctions.py +++ b/util/shadowingfunctions.py @@ -1,126 +1,94 @@ # -*- coding: utf-8 -*- # Ready for python action! -import torch -from math import radians -import matplotlib.pylab as plt import numpy as np -import torch.nn.functional as F - - -def _to_tensor(x, device, dtype=torch.float32): - if isinstance(x, torch.Tensor): - return x.to(device) - if x is None: - return None - return torch.tensor(x, dtype=dtype, device=device) +from math import radians def shadowingfunctionglobalradiation( - a, azimuth, altitude, scale, feedback, forsvf, device=torch.device("cpu") + a, azimuth, altitude, scale, feedback, forsvf ): - """ - Computes global radiation shadows on a DSM using PyTorch hardware acceleration. - - This function leverages `F.grid_sample` to perform global map translations, - completely removing multi-index array slicing and trigonometric quadrant switches. - It is heavily optimized for GPU matrix operations. - - Args: - a (torch.Tensor or numpy.ndarray): Digital Surface Model (DSM) matrix. - azimuth (float): Sun azimuth angle in degrees. - altitude (float): Sun altitude angle in degrees. - scale (float): Spatial resolution modifier (1 pixel = 1 meter -> 1.0). - feedback (QgsProcessingFeedback or similar): Object used to report algorithm progress. - forsvf (int): Flag to toggle progress reporting (0 = report progress, 1 = skip). - device (torch.device, optional): Device context for execution. Defaults to CPU. - - Returns: - torch.Tensor: Binary shadow mask (1.0 = in sun, 0.0 = in shadow) matching `a.dtype`. - """ - # Ensure inputs are correctly formatted PyTorch tensors on the target device - if not isinstance(a, torch.Tensor): - a = torch.tensor(a, device=device) - else: - a = a.to(device=device) - - # Convert angular sun positions to radians - degrees = torch.pi / 180.0 - azimuth = azimuth * degrees - altitude = altitude * degrees - sizex, sizey = a.shape - - # Track progress bounds if requested + # %This m.file calculates shadows on a DEM + # % conversion + degrees = np.pi / 180.0 + # if azimuth == 0.0: + # azimuth = 0.000000000001 + azimuth = np.dot(azimuth, degrees) + altitude = np.dot(altitude, degrees) + # % measure the size of the image + sizex = a.shape[0] + sizey = a.shape[1] if forsvf == 0: - barstep = max(sizex, sizey) - total = 100.0 / barstep - - # Clone initial DSM layout to accumulate the upper shadow envelope - f = a.clone() - - # --- COORDINATE GRID GENERATION FOR GLOBAL TRANSFORMATION --- - y, x = torch.meshgrid( - torch.linspace(-1, 1, sizex, device=device), - torch.linspace(-1, 1, sizey, device=device), - indexing="ij", - ) - # Target PyTorch shape: (1, H, W, 2) with (X, Y) layout at dim=-1 - grille_base = torch.stack((x, y), dim=-1).unsqueeze(0) - - # Convert the 2D DSM to a 4D tensor chunk (1, 1, H, W) for grid sampling - a_4d = a.unsqueeze(0).unsqueeze(0) - - # Pre-calculate sun path factors - amaxvalue = torch.max(a).item() - sinazimuth = torch.sin(azimuth) - cosazimuth = torch.cos(azimuth) - tanaltitudebyscale = torch.tan(altitude) / scale - - # Dynamically estimate the safety limit for our vector operations - max_steps = int(amaxvalue / tanaltitudebyscale.item()) + 2 - - # Vectorized loop structure (Eliminates slice variables and block switches) - for step_idx in range(1, max_steps): - index = float(step_idx) - + barstep = np.max([sizex, sizey]) + total = 100.0 / barstep # dlg.progressBar.setRange(0, barstep) + # % initialise parameters + f = a + dx = 0.0 + dy = 0.0 + dz = 0.0 + temp = np.zeros((sizex, sizey)) + index = 1.0 + # % other loop parameters + amaxvalue = a.max() + pibyfour = np.pi / 4.0 + threetimespibyfour = 3.0 * pibyfour + fivetimespibyfour = 5.0 * pibyfour + seventimespibyfour = 7.0 * pibyfour + sinazimuth = np.sin(azimuth) + cosazimuth = np.cos(azimuth) + tanazimuth = np.tan(azimuth) + signsinazimuth = np.sign(sinazimuth) + signcosazimuth = np.sign(cosazimuth) + dssin = np.abs((1.0 / sinazimuth)) + dscos = np.abs((1.0 / cosazimuth)) + tanaltitudebyscale = np.tan(altitude) / scale + # % main loop + while amaxvalue >= dz and np.abs(dx) < sizex and np.abs(dy) < sizey: if forsvf == 0: feedback.setProgress(int(index * total)) - # 1. Project ground physical shadow offset (in pixels) - shift_x = index * sinazimuth / scale - shift_y = index * cosazimuth / scale - dz = index * tanaltitudebyscale - - # Safe breakout loop check if rays completely clear the canvas boundaries - if abs(shift_x) >= sizey and abs(shift_y) >= sizex: - break - - # 2. Shift the underlying layout grid space (-1 to 1 space boundaries) - grille_deplacee = grille_base.clone() - grille_deplacee[..., 0] -= (2.0 * shift_x) / (sizey - 1) - grille_deplacee[..., 1] -= (2.0 * shift_y) / (sizex - 1) - - # 3. Retrieve translated elevation layout using bilinear interpolation - temp = ( - F.grid_sample( - a_4d, grille_deplacee, mode="bilinear", padding_mode="border" - ).squeeze() - - dz + if ( + pibyfour <= azimuth + and azimuth < threetimespibyfour + or fivetimespibyfour <= azimuth + and azimuth < seventimespibyfour + ): + dy = signsinazimuth * index + dx = -1.0 * signcosazimuth * np.abs(np.round(index / tanazimuth)) + ds = dssin + else: + dy = signsinazimuth * np.abs(np.round(index * tanazimuth)) + dx = -1.0 * signcosazimuth * index + ds = dscos + + # % note: dx and dy represent absolute values while ds is an incremental value + dz = ds * index * tanaltitudebyscale + temp[0:sizex, 0:sizey] = 0.0 + absdx = np.abs(dx) + absdy = np.abs(dy) + xc1 = (dx + absdx) / 2.0 + 1.0 + xc2 = sizex + (dx - absdx) / 2.0 + yc1 = (dy + absdy) / 2.0 + 1.0 + yc2 = sizey + (dy - absdy) / 2.0 + xp1 = -((dx - absdx) / 2.0) + 1.0 + xp2 = sizex - (dx + absdx) / 2.0 + yp1 = -((dy - absdy) / 2.0) + 1.0 + yp2 = sizey - (dy + absdy) / 2.0 + temp[int(xp1) - 1 : int(xp2), int(yp1) - 1 : int(yp2)] = ( + a[int(xc1) - 1 : int(xc2), int(yc1) - 1 : int(yc2)] - dz ) + f = np.fmax(f, temp) + index += 1.0 - # 4. Amalgamate upper terrain shadow bounds - f = torch.maximum(f, temp) - - # Calculate local shadow gaps f = f - a - - # Binary conversion: Where f == 0, the ground matches the shadow envelope (it is in sun) - sh = (f == 0).to(dtype=a.dtype) + f = np.logical_not(f) + sh = np.double(f) return sh -# @jit(nopython=True) + + def shadowingfunction_20( a, vegdem, @@ -132,148 +100,285 @@ def shadowingfunction_20( bush, feedback, forsvf, - device=torch.device("cpu"), ): - # automatically get the device to use from the input - device = a.device if isinstance(a, torch.Tensor) else torch.device("cpu") - a = _to_tensor(a, device) - vegdem = _to_tensor(vegdem, device) - vegdem2 = _to_tensor(vegdem2, device) - bush = _to_tensor(bush, device) + # This function casts shadows on buildings and vegetation units. + # New capability to deal with pergolas 20210827 - degrees = torch.pi / 180.0 + # conversion + degrees = np.pi / 180.0 azimuth = azimuth * degrees altitude = altitude * degrees + # measure the size of grid sizex = a.shape[0] sizey = a.shape[1] + # progressbar for svf plugin if forsvf == 0: - barstep = max(sizex, sizey) + barstep = np.max([sizex, sizey]) total = 100.0 / barstep feedback.setProgress(0) + # initialise parameters + dx = 0.0 + dy = 0.0 dz = 0.0 - temp = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) - tempvegdem = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) - tempvegdem2 = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) - templastfabovea = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) - templastgabovea = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) + temp = np.zeros((sizex, sizey)) + tempvegdem = np.zeros((sizex, sizey)) + tempvegdem2 = np.zeros((sizex, sizey)) + templastfabovea = np.zeros((sizex, sizey)) + templastgabovea = np.zeros((sizex, sizey)) bushplant = bush > 1.0 - sh = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) - vbshvegsh = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) - vegsh = bushplant.to(dtype=a.dtype) + sh = np.zeros((sizex, sizey)) # shadows from buildings + vbshvegsh = np.zeros((sizex, sizey)) # vegetation blocking buildings + vegsh = np.add( + np.zeros((sizex, sizey)), bushplant, dtype=float + ) # vegetation shadow f = a - shvoveg = vegdem.clone() - y, x = torch.meshgrid( - torch.linspace(-1, 1, sizex, device=device), - torch.linspace(-1, 1, sizey, device=device), - indexing="ij", - ) - grille_base = torch.stack((x, y), dim=-1).unsqueeze(0) - - # Preparation of the layers packed for grid_sample - a_4d = a.unsqueeze(0).unsqueeze(0).float() - veg_4d = vegdem.unsqueeze(0).unsqueeze(0).float() - veg2_4d = vegdem2.unsqueeze(0).unsqueeze(0).float() - - # Trigonometric params for the sun's step - sinazimuth = torch.sin(azimuth) - cosazimuth = torch.cos(azimuth) - tanaltitudebyscale = torch.tan(altitude) / scale - - # Calculation of the maximum of steps - # We stop when the shadow exceed the max possible height - max_steps = int(amaxvalue / tanaltitudebyscale) + 2 - - # Facteur d'avancement du rayon (équivalent géométrique de ton ancien 'ds') - # Plus besoin de gros blocs If/Else selon les quadrants du soleil ! - delta_z_par_pas = tanaltitudebyscale - - for index in range(1, max_steps): - # 1. Calculations of the slide - shift_x = index * sinazimuth / scale - shift_y = index * cosazimuth / scale - dz = index * delta_z_par_pas - - # Stop if the shadow is completely outside of the map - if abs(shift_x) >= sizey and abs(shift_y) >= sizex: - break - - # 2. Applying the offset to our normalized coordinate sheet (-1 to 1) - grille_deplacee = grille_base.clone() - grille_deplacee[..., 0] -= (2.0 * shift_x) / (sizey - 1) - grille_deplacee[..., 1] -= (2.0 * shift_y) / (sizex - 1) - - # 3. Global Material Sampling (Instant Layer Swipe) - temp = ( - F.grid_sample( - a_4d, grille_deplacee, mode="bilinear", padding_mode="border" - ).squeeze() - - dz - ) - tempvegdem = ( - F.grid_sample( - veg_4d, grille_deplacee, mode="bilinear", padding_mode="border" - ).squeeze() - - dz - ) - tempvegdem2 = ( - F.grid_sample( - veg2_4d, - grille_deplacee, - mode="bilinear", - padding_mode="border", - ).squeeze() - - dz + pibyfour = np.pi / 4.0 + threetimespibyfour = 3.0 * pibyfour + fivetimespibyfour = 5.0 * pibyfour + seventimespibyfour = 7.0 * pibyfour + sinazimuth = np.sin(azimuth) + cosazimuth = np.cos(azimuth) + tanazimuth = np.tan(azimuth) + signsinazimuth = np.sign(sinazimuth) + signcosazimuth = np.sign(cosazimuth) + dssin = np.abs((1.0 / sinazimuth)) + dscos = np.abs((1.0 / cosazimuth)) + tanaltitudebyscale = np.tan(altitude) / scale + # index = 1 + index = 0 + + # new case with pergola (thin vertical layer of vegetation), August 2021 + dzprev = 0 + + # main loop + while (amaxvalue >= dz) and (np.abs(dx) < sizex) and (np.abs(dy) < sizey): + if forsvf == 0: + # dlg.progressBar.setValue(index) + feedback.setProgress(int(index * total)) + if ( + (pibyfour <= azimuth) + and (azimuth < threetimespibyfour) + or (fivetimespibyfour <= azimuth) + and (azimuth < seventimespibyfour) + ): + dy = signsinazimuth * index + dx = -1.0 * signcosazimuth * np.abs(np.round(index / tanazimuth)) + ds = dssin + else: + dy = signsinazimuth * np.abs(np.round(index * tanazimuth)) + dx = -1.0 * signcosazimuth * index + ds = dscos + # note: dx and dy represent absolute values while ds is an incremental + # value + dz = (ds * index) * tanaltitudebyscale + tempvegdem[0:sizex, 0:sizey] = 0.0 + tempvegdem2[0:sizex, 0:sizey] = 0.0 + temp[0:sizex, 0:sizey] = 0.0 + templastfabovea[0:sizex, 0:sizey] = 0.0 + templastgabovea[0:sizex, 0:sizey] = 0.0 + absdx = np.abs(dx) + absdy = np.abs(dy) + xc1 = int((dx + absdx) / 2.0) + xc2 = int(sizex + (dx - absdx) / 2.0) + yc1 = int((dy + absdy) / 2.0) + yc2 = int(sizey + (dy - absdy) / 2.0) + xp1 = int(-((dx - absdx) / 2.0)) + xp2 = int(sizex - (dx + absdx) / 2.0) + yp1 = int(-((dy - absdy) / 2.0)) + yp2 = int(sizey - (dy + absdy) / 2.0) + + tempvegdem[xp1:xp2, yp1:yp2] = vegdem[xc1:xc2, yc1:yc2] - dz + tempvegdem2[xp1:xp2, yp1:yp2] = vegdem2[xc1:xc2, yc1:yc2] - dz + temp[xp1:xp2, yp1:yp2] = a[xc1:xc2, yc1:yc2] - dz + + f = np.fmax(f, temp) # Moving building shadow + sh[(f > a)] = 1.0 + sh[(f <= a)] = 0.0 + fabovea = tempvegdem > a # vegdem above DEM + gabovea = tempvegdem2 > a # vegdem2 above DEM + + # new pergola condition + templastfabovea[xp1:xp2, yp1:yp2] = vegdem[xc1:xc2, yc1:yc2] - dzprev + templastgabovea[xp1:xp2, yp1:yp2] = vegdem2[xc1:xc2, yc1:yc2] - dzprev + lastfabovea = templastfabovea > a + lastgabovea = templastgabovea > a + dzprev = dz + vegsh2 = np.add( + np.add( + np.add(fabovea, gabovea, dtype=float), lastfabovea, dtype=float + ), + lastgabovea, + dtype=float, ) + vegsh2[vegsh2 == 4] = 0.0 + # vegsh2[vegsh2 == 1] = 0. # This one is the ultimate question... + vegsh2[vegsh2 > 0] = 1.0 - # 4. Update of cast shadow volumes - f = torch.fmax(f, temp) - shvoveg = torch.fmax(shvoveg, tempvegdem) + vegsh = np.fmax(vegsh, vegsh2) + vegsh[(vegsh * sh > 0.0)] = 0.0 + vbshvegsh = vegsh + vbshvegsh # removing shadows 'behind' buildings - sh = torch.where( - f > a, - torch.tensor(1.0, device=device), - torch.tensor(0.0, device=device), - ) - fabovea = tempvegdem > a - gabovea = tempvegdem2 > a + index += 1.0 - # 5. Pergola logic (Overlaying layers with the & operator) - templastfabovea = tempvegdem + delta_z_par_pas - templastgabovea = tempvegdem2 + delta_z_par_pas + sh = 1.0 - sh + vbshvegsh[(vbshvegsh > 0.0)] = 1.0 + vbshvegsh = vbshvegsh - vegsh + vegsh = 1.0 - vegsh + vbshvegsh = 1.0 - vbshvegsh - lastfabovea = templastfabovea > a - lastgabovea = templastgabovea > a + shadowresult = {"sh": sh, "vegsh": vegsh, "vbshvegsh": vbshvegsh} + + return shadowresult - # If all 4 layers are True at the same time, it's a pergola (light passes through). - is_pergola = fabovea & gabovea & lastfabovea & lastgabovea - # The shadow exists if one of the layers is True, UNLESS it's a pergola. - vegsh2 = (fabovea | gabovea | lastfabovea | lastgabovea) & ( - ~is_pergola - ) - vegsh2 = vegsh2.float() +def shadowingfunction_20_old( + a, vegdem, vegdem2, azimuth, altitude, scale, amaxvalue, bush, dlg, forsvf +): - # Accumulation of vegetation shadows - vegsh = torch.maximum(vegsh, vegsh2) - vegsh = torch.where( - vegsh * sh > 0, torch.tensor(0.0, device=device), vegsh + # % This function casts shadows on buildings and vegetation units + # % conversion + degrees = np.pi / 180.0 + if azimuth == 0.0: + azimuth = 0.000000000001 + azimuth = np.dot(azimuth, degrees) + altitude = np.dot(altitude, degrees) + # % measure the size of the image + sizex = a.shape[0] + sizey = a.shape[1] + # % initialise parameters + if forsvf == 0: + barstep = np.max([sizex, sizey]) + dlg.progressBar.setRange(0, barstep) + dlg.progressBar.setValue(0) + + dx = 0.0 + dy = 0.0 + dz = 0.0 + temp = np.zeros((sizex, sizey)) + tempvegdem = np.zeros((sizex, sizey)) + tempvegdem2 = np.zeros((sizex, sizey)) + sh = np.zeros((sizex, sizey)) + vbshvegsh = np.zeros((sizex, sizey)) + vegsh = np.zeros((sizex, sizey)) + tempbush = np.zeros((sizex, sizey)) + f = a + g = np.zeros((sizex, sizey)) + bushplant = bush > 1.0 + pibyfour = np.pi / 4.0 + threetimespibyfour = 3.0 * pibyfour + fivetimespibyfour = 5.0 * pibyfour + seventimespibyfour = 7.0 * pibyfour + sinazimuth = np.sin(azimuth) + cosazimuth = np.cos(azimuth) + tanazimuth = np.tan(azimuth) + signsinazimuth = np.sign(sinazimuth) + signcosazimuth = np.sign(cosazimuth) + dssin = np.abs((1.0 / sinazimuth)) + dscos = np.abs((1.0 / cosazimuth)) + tanaltitudebyscale = np.tan(altitude) / scale + index = 1 + + # % main loop + while amaxvalue >= dz and np.abs(dx) < sizex and np.abs(dy) < sizey: + if forsvf == 0: + dlg.progressBar.setValue(index) + if ( + pibyfour <= azimuth + and azimuth < threetimespibyfour + or fivetimespibyfour <= azimuth + and azimuth < seventimespibyfour + ): + dy = signsinazimuth * index + dx = -1.0 * signcosazimuth * np.abs(np.round(index / tanazimuth)) + ds = dssin + else: + dy = signsinazimuth * np.abs(np.round(index * tanazimuth)) + dx = -1.0 * signcosazimuth * index + ds = dscos + # % note: dx and dy represent absolute values while ds is an incremental value + dz = np.dot(np.dot(ds, index), tanaltitudebyscale) + tempvegdem[0:sizex, 0:sizey] = 0.0 + tempvegdem2[0:sizex, 0:sizey] = 0.0 + temp[0:sizex, 0:sizey] = 0.0 + absdx = np.abs(dx) + absdy = np.abs(dy) + xc1 = (dx + absdx) / 2.0 + 1.0 + xc2 = sizex + (dx - absdx) / 2.0 + yc1 = (dy + absdy) / 2.0 + 1.0 + yc2 = sizey + (dy - absdy) / 2.0 + xp1 = -((dx - absdx) / 2.0) + 1.0 + xp2 = sizex - (dx + absdx) / 2.0 + yp1 = -((dy - absdy) / 2.0) + 1.0 + yp2 = sizey - (dy + absdy) / 2.0 + tempvegdem[int(xp1) - 1 : int(xp2), int(yp1) - 1 : int(yp2)] = ( + vegdem[int(xc1) - 1 : int(xc2), int(yc1) - 1 : int(yc2)] - dz + ) + tempvegdem2[int(xp1) - 1 : int(xp2), int(yp1) - 1 : int(yp2)] = ( + vegdem2[int(xc1) - 1 : int(xc2), int(yc1) - 1 : int(yc2)] - dz + ) + temp[int(xp1) - 1 : int(xp2), int(yp1) - 1 : int(yp2)] = ( + a[int(xc1) - 1 : int(xc2), int(yc1) - 1 : int(yc2)] - dz ) - vbshvegsh.add_(vegsh) + # f = np.maximum(f, temp) # bad performance in python3. Replaced with + # fmax + f = np.fmax(f, temp) + sh[(f > a)] = 1.0 + sh[(f <= a)] = 0.0 + # %Moving building shadow + fabovea = tempvegdem > a + # %vegdem above DEM + gabovea = tempvegdem2 > a + # %vegdem2 above DEM + # vegsh2 = np.float(fabovea)-np.float(gabovea) + vegsh2 = np.subtract(fabovea, gabovea, dtype=float) + # vegsh = np.maximum(vegsh, vegsh2) # bad performance in python3. + # Replaced with fmax + vegsh = np.fmax(vegsh, vegsh2) + vegsh[(vegsh * sh > 0.0)] = 0.0 + # % removing shadows 'behind' buildings + vbshvegsh = vegsh + vbshvegsh + # % vegsh at high sun altitudes + if index == 1.0: + firstvegdem = tempvegdem - temp + firstvegdem[(firstvegdem <= 0.0)] = 1000.0 + vegsh[(firstvegdem < dz)] = 1.0 + vegsh = vegsh * (vegdem2 > a) + vbshvegsh = np.zeros((sizex, sizey)) + + # % Bush shadow on bush plant + if np.logical_and(bush.max() > 0.0, np.max((fabovea * bush)) > 0.0): + tempbush[0:sizex, 0:sizey] = 0.0 + tempbush[int(xp1) - 1 : int(xp2), int(yp1) - 1 : int(yp2)] = ( + bush[int(xc1) - 1 : int(xc2), int(yc1) - 1 : int(yc2)] - dz + ) + # g = np.maximum(g, tempbush) # bad performance in python3. + # Replaced with fmax + g = np.fmax(g, tempbush) + g *= bushplant + index += 1.0 sh = 1.0 - sh - vbshvegsh = torch.where( - vbshvegsh > 0.0, torch.tensor(1.0, device=device), vbshvegsh - ) + vbshvegsh[(vbshvegsh > 0.0)] = 1.0 vbshvegsh = vbshvegsh - vegsh + + if bush.max() > 0.0: + g = g - bush + g[(g > 0.0)] = 1.0 + g[(g < 0.0)] = 0.0 + vegsh = vegsh - bushplant + g + vegsh[(vegsh < 0.0)] = 0.0 + + vegsh[(vegsh > 0.0)] = 1.0 vegsh = 1.0 - vegsh vbshvegsh = 1.0 - vbshvegsh shadowresult = {"sh": sh, "vegsh": vegsh, "vbshvegsh": vbshvegsh} + return shadowresult @@ -291,174 +396,194 @@ def shadowingfunction_findwallID( facesh, wall_dict, sh, - device, ): """ - Identifies which wall ID and voxel height are visible along solar ray vectors from ground pixels. - - This version removes explicit pixel-slice shifting loops by utilizing PyTorch's hardware-accelerated - `F.grid_sample` engine. It implements nearest-neighbor interpolation to prevent spatial distortion - of discrete categorical Wall IDs during matrix transformation layers. - - Args: - dsm (torch.Tensor): Digital Surface Model tensor. - azimuth (float): Sun azimuth angle in degrees. - altitude (float): Sun altitude angle in degrees. - scale (float): Spatial resolution modifier (1 pixel = 1 meter -> 1.0). - walls (torch.Tensor): Height profile of the pixels representing building walls. - uniqueWallIDs (torch.Tensor): Matrix containing distinct structural identifiers for each wall. - dem (torch.Tensor): Digital Elevation Model representing bare earth topography. - wall2d_id (torch.Tensor or list): Reference map containing baseline wall components. - voxel_height (torch.Tensor or list): Reference metric indicating structural slice heights. - voxelId_list (torch.Tensor or list): Linear index tracker for specific 3D voxel spaces. - facesh (torch.Tensor): Binary mask identifying building walls shaded by their own geometry. - wall_dict (dict): Dictionary mapping categorical string/int Wall IDs to their true heights. - sh (torch.Tensor): Baseline ground shadow mask layer (1 = sun, 0 = shadow). - device (torch.device): Execution hardware targeting context (CPU/GPU). - - Returns: - tuple: A tuple containing: - - buildIDSeen (torch.Tensor): Categorical ID of the wall casting a shadow onto the pixel. - - voxelHeight (torch.Tensor): Accumulated vertical shadow volume height profile. - - voxelId (torch.Tensor): Specific structural voxel block identifier seen by the layout. - """ - # 1. PRE-PROCESSING & FORMATTING - dsm = dsm - dem - dsm = torch.where(dsm < 0.5, torch.tensor(0.0, device=device), dsm) - - # Conversion degrees to radians - azimuth_rad = radians(azimuth) - altitude_rad = radians(altitude) - - rows, cols = dsm.shape - - # Initialise tracked arrays - buildIDSeen = torch.zeros((rows, cols), device=device) - voxelHeight = torch.zeros((rows, cols), device=device) - temp3 = torch.ones((rows, cols), device=device) - - # Mask wall tracking based on facing attributes - uniqueWallIDs_masked = uniqueWallIDs * facesh - - # Build a high-performance native tensor lookup table for wall heights - max_wall_id = int(max(wall_dict.keys())) if wall_dict else 0 - wall_height_lookup = torch.zeros(max_wall_id + 1, device=device) - for k, v in wall_dict.items(): - wall_height_lookup[int(k)] = v - - # --- COORDINATE GRID GENERATION FOR THE SAMPLING TRANSFORMS --- - y, x = torch.meshgrid( - torch.linspace(-1, 1, rows, device=device), - torch.linspace(-1, 1, cols, device=device), - indexing="ij", - ) - grille_base = torch.stack((x, y), dim=-1).unsqueeze(0) - - # Emballer uniqueWallIDs in 4D (1, 1, H, W) for grid_sample - # We keep it as float for grid_sample, and convert back to long for indexing - wall_id_4d = uniqueWallIDs_masked.unsqueeze(0).unsqueeze(0).float() - - # Trig variables - sinazimuth = torch.sin(torch.tensor(azimuth_rad, device=device)) - cosazimuth = torch.cos(torch.tensor(azimuth_rad, device=device)) - tanaltitudebyscale = ( - torch.tan(torch.tensor(altitude_rad, device=device)) / scale - ) - - amaxvalue = torch.max(dsm) - max_steps = int(amaxvalue / tanaltitudebyscale) + 2 - - # 2. CORE VECTORIZED GRID-SHIFTING LOOP - for index in range(1, max_steps): - # Project spatial offsets on pixel dimensions - shift_x = index * sinazimuth / scale - shift_y = index * cosazimuth / scale - dz = index * tanaltitudebyscale - - # Break early if ray projections completely escape spatial boundaries - if abs(shift_x) >= cols and abs(shift_y) >= rows: - break - - # Displace the structural tracking sheet - grille_deplacee = grille_base.clone() - grille_deplacee[..., 0] -= (2.0 * shift_x) / (cols - 1) - grille_deplacee[..., 1] -= (2.0 * shift_y) / (rows - 1) - - # CRITICAL OPTIMIZATION: mode="nearest" keeps Wall IDs integer-pure (no interpolation fuzziness) - tempwallID = F.grid_sample( - wall_id_4d, grille_deplacee, mode="nearest", padding_mode="zeros" - ).squeeze() - - # Batch-map wall ID categories directly to structural heights via tensor indexing - temp_wallHeight = wall_height_lookup[tempwallID.long()] - - # Track wall degradation down ray segments - temp2 = temp_wallHeight - dz + This function identifies what wall id and voxel height that is seen from a ground pixel - # Process logical flags globally on the GPU cores - valid_mask = temp2 > 0 - active_pixels_mask = valid_mask & temp3 - - # Amalgamate ray intersection metrics - buildIDSeen = torch.where(active_pixels_mask, tempwallID, buildIDSeen) - voxelHeight = torch.where( - active_pixels_mask, temp_wallHeight - temp2, voxelHeight - ) + INPUTS: + dsm = Digital surface model + azimuth and altitude = sun position in degrees + scale= scale of DSM (1 meter pixels=1, 2 meter pixels=0.5) + uniqueWallIDs = pixel row 'outside' buildings. will be calculated if empty + walls = height of walls + dem = Digital elevation model. (Should be excluded in future to incorporate ground elevation) - # Update remaining target pixel profiles using fast tensor boolean operations - temp3 = (temp2 <= 0) & (buildIDSeen == 0) + OUTPUT: + buildIDSeen = ID seen from ground pixel + voxelHeight = Wall height shadow volume - # 3. POST-PROCESSING & VOXEL IDENTIFICATION - voxelHeight_ceil = torch.ceil(voxelHeight) - voxelId = torch.zeros((rows, cols), device=device) + Fredrik Lindberg 2023-02-16 + fredrikl@gvc.gu.se - # Secure mapping arrays are local tensors - if not isinstance(wall2d_id, torch.Tensor): - wall2d_id = torch.tensor(wall2d_id, device=device) - else: - wall2d_id = wall2d_id.to(device) - - if not isinstance(voxel_height, torch.Tensor): - voxel_height = torch.tensor(voxel_height, device=device) - else: - voxel_height = voxel_height.to(device) - - if not isinstance(voxelId_list, torch.Tensor): - voxelId_list = torch.tensor( - voxelId_list, dtype=torch.long, device=device - ) - else: - voxelId_list = voxelId_list.to(device=device, dtype=torch.long) - - # Find unique Wall ID & Voxel Height pairings found across the canvas - stacked_profiles = torch.column_stack( - [buildIDSeen.flatten(), voxelHeight_ceil.flatten()] - ) - unique_combinations = torch.unique(stacked_profiles, dim=0) - unique_combinations = unique_combinations[ - ~torch.all(unique_combinations == 0, dim=1) - ] - - # Map the linear 3D voxel index references - for temp_id, temp_height in unique_combinations: - mask = (wall2d_id == temp_id) & (voxel_height == temp_height) - temp_fill_id = voxelId_list[mask] - - pixel_mask = (buildIDSeen == temp_id) & ( - voxelHeight_ceil == temp_height - ) + """ - if temp_fill_id.numel() > 0: - voxelId[pixel_mask] = temp_fill_id[0].float() + # Remove ground heights + dsm = dsm - dem + # buildings = 1 - ((dsm) > 0) + dsm[dsm < 0.5] = 0 + + # conversion, degrees to radians + azimuth = radians(azimuth) + altitude = radians(altitude) + + # measure the size of the image + rows = np.shape(dsm)[0] + cols = np.shape(dsm)[1] + + # initialise parameters + f = np.copy(dsm) + buildIDSeen = np.zeros((rows, cols)) + # h = np.zeros((rows, cols)) + + dx = 0 + dy = 0 + dz = 0 + temp = np.zeros((rows, cols)) + temp2 = np.zeros((rows, cols)) # walls + tempwallID = np.zeros((rows, cols)) + uniqueWallIDsOrig = np.copy(uniqueWallIDs) + + voxelHeight = np.zeros((rows, cols)) + temp3 = np.ones((rows, cols)) + + # other loop parameters + amaxvalue = np.max(dsm) + pibyfour = np.pi / 4 + threetimespibyfour = 3 * pibyfour + fivetimespibyfour = 5 * pibyfour + seventimespibyfour = 7 * pibyfour + sinazimuth = np.sin(azimuth) + cosazimuth = np.cos(azimuth) + tanazimuth = np.tan(azimuth) + signsinazimuth = np.sign(sinazimuth) + signcosazimuth = np.sign(cosazimuth) + dssin = np.abs(1 / sinazimuth) + dscos = np.abs(1 / cosazimuth) + tanaltitudebyscale = np.tan(altitude) / scale + + index = 1 + + # main loop + while (amaxvalue >= dz) and (np.abs(dx) < rows) and (np.abs(dy) < cols): + + if (pibyfour <= azimuth and azimuth < threetimespibyfour) or ( + fivetimespibyfour <= azimuth and azimuth < seventimespibyfour + ): + dy = signsinazimuth * index + dx = -1 * signcosazimuth * np.abs(np.round(index / tanazimuth)) + ds = dssin else: - buildIDSeen[pixel_mask] = 0.0 - voxelHeight_ceil[pixel_mask] = 0.0 - - # Invert original shadow notation layout logic (1 - sh) - # This aligns the mapping accurately to raw cast shadows - shadow_correction = 1.0 - sh - buildIDSeen *= shadow_correction - voxelHeight *= shadow_correction - voxelId *= shadow_correction + dy = signsinazimuth * np.abs(np.round(index * tanazimuth)) + dx = -1 * signcosazimuth * index + ds = dscos + + # note: dx and dy represent absolute values while ds is an incremental + # value + dz = ds * index * tanaltitudebyscale + temp[0:rows, 0:cols] = 0 + temp2[0:rows, 0:cols] = 0 + + absdx = np.abs(dx) + absdy = np.abs(dy) + + xc1 = int((dx + absdx) / 2) + xc2 = int(rows + (dx - absdx) / 2) + yc1 = int((dy + absdy) / 2) + yc2 = int(cols + (dy - absdy) / 2) + + xp1 = int(-((dx - absdx) / 2)) + xp2 = int(rows - (dx + absdx) / 2) + yp1 = int(-((dy - absdy) / 2)) + yp2 = int(cols - (dy + absdy) / 2) + + wallSeen = facesh + uniqueWallIDs = uniqueWallIDs * wallSeen + # uniqueWallIDs = ((uniqueWallIDs - firstMove) < 0) * uniqueWallIDsOrig + uniqueWallIDs # adding missing corner + # wallSeenHeight = walls * wallSeen + + # temp2[xp1:xp2, yp1:yp2] = wallSeenHeight[xc1:xc2, yc1:yc2] - dz # Moving wall shadow + # Moving wall id + tempwallID[xp1:xp2, yp1:yp2] = uniqueWallIDs[xc1:xc2, yc1:yc2] + + # Get wall height from wall id + temp_wallHeight = np.vectorize(wall_dict.__getitem__)(tempwallID) + + # Descending wall, how much of the wall that is still above ground + # level + temp2 = temp_wallHeight - dz - return buildIDSeen, voxelHeight, voxelId + # buildIDSeen = Wall pixels/voxels seen, i.e. only voxels that are positive (above ground level) (temp2 > 0). + # temp3 indicates those pixels that the walls have not progressed into + # yet (saved in previous iteration). + buildIDSeen = (temp2 > 0) * temp3 * tempwallID + buildIDSeen + + + + # voxelHeight = the elevation on a wall that is seen from a pixel with the given altitude and azimuth (only above ground leve, i.e. (temp2 > 0)). + # voxelHeight = wall height - descending wall, i.e. temp_wallHeight - + # temp2. Only applicable to pixels where there is no value from + # previous iterations (temp3). + voxelHeight = (temp2 > 0) * temp3 * ( + temp_wallHeight - temp2 + ) + voxelHeight + + # Remember pixels previous iteration that walls have not progressed + # into yet. + temp3 = np.copy(temp2 <= 0) * (buildIDSeen == 0) + + index += 1 + + # Ceil voxel height values to integers + voxelHeight_ceil = np.ceil(voxelHeight) + # voxelHeight_ceil = np.round(voxelHeight) + + # Empty raster to fill with voxel IDs + voxelId = np.zeros((rows, cols)) + # Convert wall2d_id from list to numpy array + wall2d_id = np.array(wall2d_id) + # Convert voxel_height from list to numpy array + voxel_height = np.array(voxel_height) + # Convert voxelId_list from list to numpy array + voxelId_list = np.array(voxelId_list, dtype=int) + + # Flatten buildIDseen from matrix to array + a = buildIDSeen.flatten() + # Flatten voxelHeight_ceil from matrix to array + b = voxelHeight_ceil.flatten() + # Combine the two above arrays into an n by 2 array + c = np.column_stack([a, b]) + # Find unique values in c + d = np.unique(c, axis=0) + # Remove rows where both columns are zero + d = d[~np.all(d == 0, axis=1)] + + + not_in_list = 0 + in_list = 0 + # Fill voxelId matrix with unique voxel IDs + for temp_id, temp_height in d: + # print(str(temp_id) + ' ' + str(temp_height)) + temp_fill_id = voxelId_list[ + ((wall2d_id == temp_id) & (voxel_height == temp_height)) + ] + if temp_fill_id.__len__() > 0: + # print('temp_fill_id = ' + str(temp_fill_id)) + voxelId[ + (buildIDSeen == temp_id) & (voxelHeight_ceil == temp_height) + ] = temp_fill_id + in_list += 1 + else: + not_in_list += 1 + buildIDSeen[ + (buildIDSeen == temp_id) & (voxelHeight_ceil == temp_height) + ] = 0 + voxelHeight_ceil[ + (buildIDSeen == temp_id) & (voxelHeight_ceil == temp_height) + ] = 0 + + # Correct for shadows, i.e. remove weird pixels on top of buildings etc + buildIDSeen = buildIDSeen * (1 - sh) + voxelHeight = voxelHeight * (1 - sh) + voxelId = voxelId * (1 - sh) + + return buildIDSeen, voxelHeight, voxelId \ No newline at end of file diff --git a/util/shadowingfunctions_torch.py b/util/shadowingfunctions_torch.py new file mode 100644 index 0000000..a3e2c3c --- /dev/null +++ b/util/shadowingfunctions_torch.py @@ -0,0 +1,463 @@ +# -*- coding: utf-8 -*- +# Ready for python action! +from math import radians +try: + import torch + import torch.nn.functional as F +except: + pass + + +def _to_tensor(x, device, dtype=torch.float32): + if isinstance(x, torch.Tensor): + return x.to(device) + if x is None: + return None + return torch.tensor(x, dtype=dtype, device=device) + + +def shadowingfunctionglobalradiation( + a, azimuth, altitude, scale, feedback, forsvf, device=torch.device("cpu") +): + """ + Computes global radiation shadows on a DSM using PyTorch hardware acceleration. + + This function leverages `F.grid_sample` to perform global map translations, + completely removing multi-index array slicing and trigonometric quadrant switches. + It is heavily optimized for GPU matrix operations. + + Args: + a (torch.Tensor or numpy.ndarray): Digital Surface Model (DSM) matrix. + azimuth (float): Sun azimuth angle in degrees. + altitude (float): Sun altitude angle in degrees. + scale (float): Spatial resolution modifier (1 pixel = 1 meter -> 1.0). + feedback (QgsProcessingFeedback or similar): Object used to report algorithm progress. + forsvf (int): Flag to toggle progress reporting (0 = report progress, 1 = skip). + device (torch.device, optional): Device context for execution. Defaults to CPU. + + Returns: + torch.Tensor: Binary shadow mask (1.0 = in sun, 0.0 = in shadow) matching `a.dtype`. + """ + # Ensure inputs are correctly formatted PyTorch tensors on the target device + if not isinstance(a, torch.Tensor): + a = torch.tensor(a, device=device) + else: + a = a.to(device=device) + + # Convert angular sun positions to radians + degrees = torch.pi / 180.0 + azimuth = azimuth * degrees + altitude = altitude * degrees + + sizex, sizey = a.shape + + # Track progress bounds if requested + if forsvf == 0: + barstep = max(sizex, sizey) + total = 100.0 / barstep + + # Clone initial DSM layout to accumulate the upper shadow envelope + f = a.clone() + + # --- COORDINATE GRID GENERATION FOR GLOBAL TRANSFORMATION --- + y, x = torch.meshgrid( + torch.linspace(-1, 1, sizex, device=device), + torch.linspace(-1, 1, sizey, device=device), + indexing="ij", + ) + # Target PyTorch shape: (1, H, W, 2) with (X, Y) layout at dim=-1 + grille_base = torch.stack((x, y), dim=-1).unsqueeze(0) + + # Convert the 2D DSM to a 4D tensor chunk (1, 1, H, W) for grid sampling + a_4d = a.unsqueeze(0).unsqueeze(0) + + # Pre-calculate sun path factors + amaxvalue = torch.max(a).item() + sinazimuth = torch.sin(azimuth) + cosazimuth = torch.cos(azimuth) + tanaltitudebyscale = torch.tan(altitude) / scale + + # Dynamically estimate the safety limit for our vector operations + max_steps = int(amaxvalue / tanaltitudebyscale.item()) + 2 + + # Vectorized loop structure (Eliminates slice variables and block switches) + for step_idx in range(1, max_steps): + index = float(step_idx) + + if forsvf == 0: + feedback.setProgress(int(index * total)) + + # 1. Project ground physical shadow offset (in pixels) + shift_x = index * sinazimuth / scale + shift_y = index * cosazimuth / scale + dz = index * tanaltitudebyscale + + # Safe breakout loop check if rays completely clear the canvas boundaries + if abs(shift_x) >= sizey and abs(shift_y) >= sizex: + break + + # 2. Shift the underlying layout grid space (-1 to 1 space boundaries) + grille_deplacee = grille_base.clone() + grille_deplacee[..., 0] -= (2.0 * shift_x) / (sizey - 1) + grille_deplacee[..., 1] -= (2.0 * shift_y) / (sizex - 1) + + # 3. Retrieve translated elevation layout using bilinear interpolation + temp = ( + F.grid_sample( + a_4d, grille_deplacee, mode="bilinear", padding_mode="border" + ).squeeze() + - dz + ) + + # 4. Amalgamate upper terrain shadow bounds + f = torch.maximum(f, temp) + + # Calculate local shadow gaps + f = f - a + + # Binary conversion: Where f == 0, the ground matches the shadow envelope (it is in sun) + sh = (f == 0).to(dtype=a.dtype) + + return sh + + +# @jit(nopython=True) +def shadowingfunction_20( + a, + vegdem, + vegdem2, + azimuth, + altitude, + scale, + amaxvalue, + bush, + feedback, + forsvf, + device=torch.device("cpu"), +): + + a = _to_tensor(a, device) + vegdem = _to_tensor(vegdem, device) + vegdem2 = _to_tensor(vegdem2, device) + bush = _to_tensor(bush, device) + + degrees = torch.pi / 180.0 + azimuth = azimuth * degrees + altitude = altitude * degrees + + sizex = a.shape[0] + sizey = a.shape[1] + + if forsvf == 0: + barstep = max(sizex, sizey) + total = 100.0 / barstep + feedback.setProgress(0) + + dz = 0.0 + temp = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) + tempvegdem = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) + tempvegdem2 = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) + templastfabovea = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) + templastgabovea = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) + bushplant = bush > 1.0 + sh = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) + vbshvegsh = torch.zeros((sizex, sizey), device=device, dtype=a.dtype) + vegsh = bushplant.to(dtype=a.dtype) + f = a + + shvoveg = vegdem.clone() + y, x = torch.meshgrid( + torch.linspace(-1, 1, sizex, device=device), + torch.linspace(-1, 1, sizey, device=device), + indexing="ij", + ) + grille_base = torch.stack((x, y), dim=-1).unsqueeze(0) + + # Preparation of the layers packed for grid_sample + a_4d = a.unsqueeze(0).unsqueeze(0).float() + veg_4d = vegdem.unsqueeze(0).unsqueeze(0).float() + veg2_4d = vegdem2.unsqueeze(0).unsqueeze(0).float() + + # Trigonometric params for the sun's step + sinazimuth = torch.sin(azimuth) + cosazimuth = torch.cos(azimuth) + tanaltitudebyscale = torch.tan(altitude) / scale + + # Calculation of the maximum of steps + # We stop when the shadow exceed the max possible height + max_steps = int(amaxvalue / tanaltitudebyscale) + 2 + + # Facteur d'avancement du rayon (équivalent géométrique de ton ancien 'ds') + # Plus besoin de gros blocs If/Else selon les quadrants du soleil ! + delta_z_par_pas = tanaltitudebyscale + + for index in range(1, max_steps): + # 1. Calculations of the slide + shift_x = index * sinazimuth / scale + shift_y = index * cosazimuth / scale + dz = index * delta_z_par_pas + + # Stop if the shadow is completely outside of the map + if abs(shift_x) >= sizey and abs(shift_y) >= sizex: + break + + # 2. Applying the offset to our normalized coordinate sheet (-1 to 1) + grille_deplacee = grille_base.clone() + grille_deplacee[..., 0] -= (2.0 * shift_x) / (sizey - 1) + grille_deplacee[..., 1] -= (2.0 * shift_y) / (sizex - 1) + + # 3. Global Material Sampling (Instant Layer Swipe) + temp = ( + F.grid_sample( + a_4d, grille_deplacee, mode="bilinear", padding_mode="border" + ).squeeze() + - dz + ) + tempvegdem = ( + F.grid_sample( + veg_4d, grille_deplacee, mode="bilinear", padding_mode="border" + ).squeeze() + - dz + ) + tempvegdem2 = ( + F.grid_sample( + veg2_4d, + grille_deplacee, + mode="bilinear", + padding_mode="border", + ).squeeze() + - dz + ) + + # 4. Update of cast shadow volumes + f = torch.fmax(f, temp) + shvoveg = torch.fmax(shvoveg, tempvegdem) + + sh = torch.where( + f > a, + torch.tensor(1.0, device=device), + torch.tensor(0.0, device=device), + ) + fabovea = tempvegdem > a + gabovea = tempvegdem2 > a + + # 5. Pergola logic (Overlaying layers with the & operator) + templastfabovea = tempvegdem + delta_z_par_pas + templastgabovea = tempvegdem2 + delta_z_par_pas + + lastfabovea = templastfabovea > a + lastgabovea = templastgabovea > a + + # If all 4 layers are True at the same time, it's a pergola (light passes through). + is_pergola = fabovea & gabovea & lastfabovea & lastgabovea + + # The shadow exists if one of the layers is True, UNLESS it's a pergola. + vegsh2 = (fabovea | gabovea | lastfabovea | lastgabovea) & ( + ~is_pergola + ) + vegsh2 = vegsh2.float() + + # Accumulation of vegetation shadows + vegsh = torch.maximum(vegsh, vegsh2) + vegsh = torch.where( + vegsh * sh > 0, torch.tensor(0.0, device=device), vegsh + ) + vbshvegsh.add_(vegsh) + + sh = 1.0 - sh + vbshvegsh = torch.where( + vbshvegsh > 0.0, torch.tensor(1.0, device=device), vbshvegsh + ) + vbshvegsh = vbshvegsh - vegsh + vegsh = 1.0 - vegsh + vbshvegsh = 1.0 - vbshvegsh + + shadowresult = {"sh": sh, "vegsh": vegsh, "vbshvegsh": vbshvegsh} + return shadowresult + + +def shadowingfunction_findwallID( + dsm, + azimuth, + altitude, + scale, + walls, + uniqueWallIDs, + dem, + wall2d_id, + voxel_height, + voxelId_list, + facesh, + wall_dict, + sh, + device, +): + """ + Identifies which wall ID and voxel height are visible along solar ray vectors from ground pixels. + + This version removes explicit pixel-slice shifting loops by utilizing PyTorch's hardware-accelerated + `F.grid_sample` engine. It implements nearest-neighbor interpolation to prevent spatial distortion + of discrete categorical Wall IDs during matrix transformation layers. + + Args: + dsm (torch.Tensor): Digital Surface Model tensor. + azimuth (float): Sun azimuth angle in degrees. + altitude (float): Sun altitude angle in degrees. + scale (float): Spatial resolution modifier (1 pixel = 1 meter -> 1.0). + walls (torch.Tensor): Height profile of the pixels representing building walls. + uniqueWallIDs (torch.Tensor): Matrix containing distinct structural identifiers for each wall. + dem (torch.Tensor): Digital Elevation Model representing bare earth topography. + wall2d_id (torch.Tensor or list): Reference map containing baseline wall components. + voxel_height (torch.Tensor or list): Reference metric indicating structural slice heights. + voxelId_list (torch.Tensor or list): Linear index tracker for specific 3D voxel spaces. + facesh (torch.Tensor): Binary mask identifying building walls shaded by their own geometry. + wall_dict (dict): Dictionary mapping categorical string/int Wall IDs to their true heights. + sh (torch.Tensor): Baseline ground shadow mask layer (1 = sun, 0 = shadow). + device (torch.device): Execution hardware targeting context (CPU/GPU). + + Returns: + tuple: A tuple containing: + - buildIDSeen (torch.Tensor): Categorical ID of the wall casting a shadow onto the pixel. + - voxelHeight (torch.Tensor): Accumulated vertical shadow volume height profile. + - voxelId (torch.Tensor): Specific structural voxel block identifier seen by the layout. + """ + # 1. PRE-PROCESSING & FORMATTING + dsm = dsm - dem + dsm = torch.where(dsm < 0.5, torch.tensor(0.0, device=device), dsm) + + # Conversion degrees to radians + azimuth_rad = radians(azimuth) + altitude_rad = radians(altitude) + + rows, cols = dsm.shape + + # Initialise tracked arrays + buildIDSeen = torch.zeros((rows, cols), device=device) + voxelHeight = torch.zeros((rows, cols), device=device) + temp3 = torch.ones((rows, cols), device=device, dtype=torch.bool) + + # Mask wall tracking based on facing attributes + uniqueWallIDs_masked = uniqueWallIDs * facesh + + # Build a high-performance native tensor lookup table for wall heights + max_wall_id = int(max(wall_dict.keys())) if wall_dict else 0 + wall_height_lookup = torch.zeros(max_wall_id + 1, device=device) + for k, v in wall_dict.items(): + wall_height_lookup[int(k)] = v + + # --- COORDINATE GRID GENERATION FOR THE SAMPLING TRANSFORMS --- + y, x = torch.meshgrid( + torch.linspace(-1, 1, rows, device=device), + torch.linspace(-1, 1, cols, device=device), + indexing="ij", + ) + grille_base = torch.stack((x, y), dim=-1).unsqueeze(0) + + # Emballer uniqueWallIDs in 4D (1, 1, H, W) for grid_sample + # We keep it as float for grid_sample, and convert back to long for indexing + wall_id_4d = uniqueWallIDs_masked.unsqueeze(0).unsqueeze(0).float() + + # Trig variables + sinazimuth = torch.sin(torch.tensor(azimuth_rad, device=device)) + cosazimuth = torch.cos(torch.tensor(azimuth_rad, device=device)) + tanaltitudebyscale = ( + torch.tan(torch.tensor(altitude_rad, device=device)) / scale + ) + + amaxvalue = torch.max(dsm) + max_steps = int(amaxvalue / tanaltitudebyscale) + 2 + + # 2. CORE VECTORIZED GRID-SHIFTING LOOP + for index in range(1, max_steps): + # Project spatial offsets on pixel dimensions + shift_x = index * sinazimuth / scale + shift_y = index * cosazimuth / scale + dz = index * tanaltitudebyscale + + # Break early if ray projections completely escape spatial boundaries + if abs(shift_x) >= cols and abs(shift_y) >= rows: + break + + # Displace the structural tracking sheet + grille_deplacee = grille_base.clone() + grille_deplacee[..., 0] -= (2.0 * shift_x) / (cols - 1) + grille_deplacee[..., 1] -= (2.0 * shift_y) / (rows - 1) + + # CRITICAL OPTIMIZATION: mode="nearest" keeps Wall IDs integer-pure (no interpolation fuzziness) + tempwallID = F.grid_sample( + wall_id_4d, grille_deplacee, mode="nearest", padding_mode="zeros" + ).squeeze() + + # Batch-map wall ID categories directly to structural heights via tensor indexing + temp_wallHeight = wall_height_lookup[tempwallID.long()] + + # Track wall degradation down ray segments + temp2 = temp_wallHeight - dz + + # Process logical flags globally on the GPU cores + valid_mask = temp2 > 0 + active_pixels_mask = valid_mask & temp3 + + # Amalgamate ray intersection metrics + buildIDSeen = torch.where(active_pixels_mask, tempwallID, buildIDSeen) + voxelHeight = torch.where( + active_pixels_mask, temp_wallHeight - temp2, voxelHeight + ) + + # Update remaining target pixel profiles using fast tensor boolean operations + temp3 = (temp2 <= 0) & (buildIDSeen == 0.0) + + # 3. POST-PROCESSING & VOXEL IDENTIFICATION + voxelHeight_ceil = torch.ceil(voxelHeight) + voxelId = torch.zeros((rows, cols), device=device) + + # Secure mapping arrays are local tensors + if not isinstance(wall2d_id, torch.Tensor): + wall2d_id = torch.tensor(wall2d_id, device=device) + else: + wall2d_id = wall2d_id.to(device) + + if not isinstance(voxel_height, torch.Tensor): + voxel_height = torch.tensor(voxel_height, device=device) + else: + voxel_height = voxel_height.to(device) + + if not isinstance(voxelId_list, torch.Tensor): + voxelId_list = torch.tensor( + voxelId_list, dtype=torch.long, device=device + ) + else: + voxelId_list = voxelId_list.to(device=device, dtype=torch.long) + + # Find unique Wall ID & Voxel Height pairings found across the canvas + stacked_profiles = torch.column_stack( + [buildIDSeen.flatten(), voxelHeight_ceil.flatten()] + ) + unique_combinations = torch.unique(stacked_profiles, dim=0) + unique_combinations = unique_combinations[ + ~torch.all(unique_combinations == 0, dim=1) + ] + + # Map the linear 3D voxel index references + for temp_id, temp_height in unique_combinations: + mask = (wall2d_id == temp_id) & (voxel_height == temp_height) + temp_fill_id = voxelId_list[mask] + + pixel_mask = (buildIDSeen == temp_id) & ( + voxelHeight_ceil == temp_height + ) + + if temp_fill_id.numel() > 0: + voxelId[pixel_mask] = temp_fill_id[0].float() + else: + buildIDSeen[pixel_mask] = 0.0 + voxelHeight_ceil[pixel_mask] = 0.0 + + # Invert original shadow notation layout logic (1 - sh) + # This aligns the mapping accurately to raw cast shadows + shadow_correction = 1.0 - sh + buildIDSeen *= shadow_correction + voxelHeight *= shadow_correction + voxelId *= shadow_correction + + return buildIDSeen, voxelHeight, voxelId diff --git a/util/ssParms.py b/util/ssParms.py index 4de863c..2a589ac 100644 --- a/util/ssParms.py +++ b/util/ssParms.py @@ -9,12 +9,11 @@ from ..functions import wallalgorithms as wa # from umep_suewsss_export_component import write_GridLayout_file, create_GridLayout_dict -from ..util.umep_suewsss_export_component import ( +from .umep_suewsss_export_component import ( write_GridLayout_file, create_GridLayout_dict, ) -# import matplotlib as plt def ss_calc(build, cdsm, walls, numPixels, feedback): diff --git a/util/torch_fallback.py b/util/torch_fallback.py new file mode 100644 index 0000000..edc7562 --- /dev/null +++ b/util/torch_fallback.py @@ -0,0 +1,25 @@ +import sys +import builtins # <-- We use this to make torch completely global + +try: + import torch + import torch.nn.functional as F +except ImportError: + class MetaMock(type): + def __getattr__(cls, name): + return cls + def __call__(cls, *args, **kwargs): + return cls + + class MockTorch(metaclass=MetaMock): + pass + + mock_instance = MockTorch + + # 1. Fix for files that DO execute "import torch" + sys.modules['torch'] = mock_instance + sys.modules['torch.nn.functional'] = mock_instance + + # 2. Fix for files that DO NOT execute "import torch" but use the word anyway + setattr(builtins, 'torch', mock_instance) + setattr(builtins, 'F', mock_instance) \ No newline at end of file diff --git a/util/umep_solweig_export_component.py b/util/umep_solweig_export_component.py index 0c8d34e..0e12793 100644 --- a/util/umep_solweig_export_component.py +++ b/util/umep_solweig_export_component.py @@ -55,12 +55,14 @@ def write_solweig_config(configDict, refdir): f.write("# Point of Interest file for ground\n") f.write("poi_file={}\n".format(configDict["poi_file"])) f.write("poi_field={}\n".format(configDict["poi_field"])) + f.write( + "# Input file for surface temperature data in v2026a (.txt file)\n" + ) + f.write("input_surf={}\n".format(configDict["input_surf"])) f.write( "# Input file for wall temperture scheme (Wallenberg et al. 2025)\n" ) f.write("input_wall={}\n".format(configDict["input_wall"])) - f.write("# Input file for surface temperature data\n") - f.write("input_surf={}\n".format(configDict.get("input_surf", ""))) f.write("# Point of Interest file for walls\n") f.write("woi_file={}\n".format(configDict["woi_file"])) f.write("woi_field={}\n".format(configDict["woi_field"])) @@ -104,6 +106,14 @@ def write_solweig_config(configDict, refdir): "# use anisotrphic sky (Wallenberg et al. XXXX and Wallenberg et al. XXXX)\n" ) f.write("aniso={}\n".format(configDict["aniso"])) + f.write( + "# use the surface temperature parameterization v2026a\n" + ) + f.write("groundmodel={}\n".format(configDict["groundmodel"])) + f.write( + "# use the outgoing longwave radiation computation scheme v2026a\n" + ) + f.write("outgoinglongwave={}\n".format(configDict["outgoingLW"])) f.write( "# use wall surface temperature scheme (Wallenberg et al. 2025, GMD)\n" ) @@ -112,8 +122,6 @@ def write_solweig_config(configDict, refdir): "# If building materials is not included in lc, then this is used for all buildings (Wood, Brick or Concrete)\n" ) f.write("walltype={}\n".format(configDict["walltype"])) - f.write("# use OHM for ground surface temperature modeling\n") - f.write("groundmodel={}\n".format(configDict["groundmodel"])) f.write("# output settings\n") f.write("outputtmrt={}\n".format(configDict["outputtmrt"])) f.write("outputkup={}\n".format(configDict["outputkup"])) From ff2bba196cbb663531fd4ef704cfba15b2a83711 Mon Sep 17 00:00:00 2001 From: "Mael.PHILIPPE" Date: Fri, 5 Jun 2026 14:34:14 +0200 Subject: [PATCH 13/20] fix/sklearn error --- functions/svf_for_voxels.py | 5 +++++ functions/svf_for_voxels_torch.py | 10 +++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/functions/svf_for_voxels.py b/functions/svf_for_voxels.py index b945aaa..48e5de5 100644 --- a/functions/svf_for_voxels.py +++ b/functions/svf_for_voxels.py @@ -250,6 +250,11 @@ def svf_kmeans( svf_height_array, feedback, ): + + try: + from sklearn.cluster import KMeans + except: + raise ImportError("[UMEP-processing Error] pleas install sklearn via pip install scikit-learn or via osgeo4w") # Calculate where there are buildings and not. Used to elevate dem. ground = dsm - dem diff --git a/functions/svf_for_voxels_torch.py b/functions/svf_for_voxels_torch.py index cc7267b..6122367 100644 --- a/functions/svf_for_voxels_torch.py +++ b/functions/svf_for_voxels_torch.py @@ -1,5 +1,8 @@ -from sklearn.cluster import KMeans +try: + from sklearn.cluster import KMeans +except: + pass import numpy as np @@ -295,6 +298,11 @@ def svf_kmeans( feedback, device=torch.device("cpu"), ): + try: + from sklearn.cluster import KMeans + except: + raise ImportError("[UMEP-processing Error] pleas install sklearn via pip install scikit-learn or via osgeo4w") + with torch.no_grad(): dsm = _to_tensor(dsm, device) From 9cd6a67959f91e21e355b1e38e821bbff4dbf99d Mon Sep 17 00:00:00 2001 From: "Mael.PHILIPPE" Date: Mon, 8 Jun 2026 10:26:11 +0200 Subject: [PATCH 14/20] code formating and issues fixing --- __init__.py | 3 +- .../SEBE_2015a_calc_forprocessing.py | 2 +- functions/SEBEfiles/importdata.py | 2 +- functions/SEBEfiles/sunmapcreator_2015a.py | 2 +- functions/SOLWEIGpython/CirclePlotBar.py | 7 +- .../SOLWEIGpython/Kside_veg_v2022a_torch.py | 2 + .../SOLWEIGpython/Kup_veg_2015a_torch.py | 1 + functions/SOLWEIGpython/Lcyl_v2022a.py | 6 - functions/SOLWEIGpython/Lside_veg_torch.py | 2 + functions/SOLWEIGpython/Lside_veg_v2015a.py | 295 +++++++++++------- functions/SOLWEIGpython/Lvikt_veg_torch.py | 1 + functions/SOLWEIGpython/PET_calculations.py | 3 +- .../Solweig_2025a_calc_forprocessing.py | 2 +- .../Solweig_2026a_calc_forprocessing_torch.py | 10 +- functions/SOLWEIGpython/Solweig_run.py | 11 +- functions/SOLWEIGpython/Solweig_run_torch.py | 15 +- functions/SOLWEIGpython/Tgmaps_v1_torch.py | 1 + functions/SOLWEIGpython/anisotropic_sky.py | 2 - functions/SOLWEIGpython/daylen_torch.py | 1 + functions/SOLWEIGpython/ground_surface.py | 20 +- .../SOLWEIGpython/ground_surface_torch.py | 31 +- functions/SOLWEIGpython/patch_radiation.py | 5 - functions/svf_for_voxels.py | 11 +- functions/svf_for_voxels_torch.py | 31 +- functions/svf_functions.py | 2 +- functions/svf_functions_torch.py | 26 +- functions/wallalgorithms.py | 2 +- functions/wallalgorithms_torch.py | 10 +- lc_update.py | 10 +- preprocessor/skyviewfactor_algorithm.py | 64 ++-- preprocessor/wall_heightaspect_algorithm.py | 32 +- processor/sebe_algorithm.py | 2 +- processor/solweig_algorithm.py | 12 +- util/SEBESOLWEIGCommonFiles/Perez_v3.py | 5 +- util/SEBESOLWEIGCommonFiles/Perez_v3_torch.py | 2 + .../Solweig_v2015_metdata_noload_torch.py | 2 + .../clearnessindex_2013b_torch.py | 1 + .../create_patches_torch.py | 1 + .../diffusefraction_torch.py | 2 + .../shadowingfunction_wallheight_13_torch.py | 1 + .../shadowingfunction_wallheight_23.py | 7 +- .../shadowingfunction_wallheight_23_torch.py | 5 +- .../sun_distance_torch.py | 1 + util/SEBESOLWEIGCommonFiles/sun_position.py | 5 +- .../sun_position_torch.py | 2 + util/landCoverFractions_v2.py | 1 - util/shadowingfunctions.py | 7 +- util/shadowingfunctions_torch.py | 1 + util/ssParms.py | 1 - util/torch_fallback.py | 12 +- util/umep_solweig_export_component.py | 6 +- 51 files changed, 382 insertions(+), 306 deletions(-) diff --git a/__init__.py b/__init__.py index 91562c8..7a34a1f 100644 --- a/__init__.py +++ b/__init__.py @@ -21,6 +21,7 @@ ***************************************************************************/ This script initializes the plugin, making it known to QGIS. """ + # 1. Initialize fallback immediately to shield all downstream imports from .util import torch_fallback @@ -46,4 +47,4 @@ def classFactory(iface): # pylint: disable=invalid-name from .processing_umep import ProcessingUMEPPlugin # Crucial: pass the iface variable QGIS gives you right into the plugin - return ProcessingUMEPPlugin() \ No newline at end of file + return ProcessingUMEPPlugin() diff --git a/functions/SEBEfiles/SEBE_2015a_calc_forprocessing.py b/functions/SEBEfiles/SEBE_2015a_calc_forprocessing.py index c20f468..d8ce856 100644 --- a/functions/SEBEfiles/SEBE_2015a_calc_forprocessing.py +++ b/functions/SEBEfiles/SEBE_2015a_calc_forprocessing.py @@ -259,4 +259,4 @@ def SEBE_2015a_calc( "vegdata": vegdata, } - return seberesult \ No newline at end of file + return seberesult diff --git a/functions/SEBEfiles/importdata.py b/functions/SEBEfiles/importdata.py index 9fa3bc0..9a2b110 100644 --- a/functions/SEBEfiles/importdata.py +++ b/functions/SEBEfiles/importdata.py @@ -283,4 +283,4 @@ def importdata_ascii(fileName, delimiter, headerRows): # making changes to data to fit the Matlab function headerRows = float(headerRows) - return output, delimiter, headerRows \ No newline at end of file + return output, delimiter, headerRows diff --git a/functions/SEBEfiles/sunmapcreator_2015a.py b/functions/SEBEfiles/sunmapcreator_2015a.py index 610b577..5b21cd2 100644 --- a/functions/SEBEfiles/sunmapcreator_2015a.py +++ b/functions/SEBEfiles/sunmapcreator_2015a.py @@ -147,4 +147,4 @@ def sunmapcreator_2015a( radmatD[:, 2:15] = radmatD[:, 2:15] / multiyear radmatR[:, 2:15] = radmatR[:, 2:15] / multiyear - return radmatI, radmatD, radmatR \ No newline at end of file + return radmatI, radmatD, radmatR diff --git a/functions/SOLWEIGpython/CirclePlotBar.py b/functions/SOLWEIGpython/CirclePlotBar.py index b23695d..ec07925 100644 --- a/functions/SOLWEIGpython/CirclePlotBar.py +++ b/functions/SOLWEIGpython/CirclePlotBar.py @@ -68,7 +68,7 @@ def PolarBarPlot( radii_sub = 0 for i in range(skyalt_c.__len__()): clrs = lv_norm[lv_alt == skyalt[i]] - # print(clrs) + if skyalt_c[i] > 1: theta_patch = ( np.arange(0, skyalt_c[skyalt == skyalt[i]][0], 1) @@ -80,11 +80,6 @@ def PolarBarPlot( / skyalt_c[skyalt == skyalt[i]][0] ) - # if plot_type: - # patch_order_range = range(skyalt_c[skyalt == skyalt[i]][0]) - # else: - # patch_order_range = reversed(range(skyalt_c[skyalt == skyalt[i]][0])) - patch_order_range = range(skyalt_c[skyalt == skyalt[i]][0]) for j in patch_order_range: diff --git a/functions/SOLWEIGpython/Kside_veg_v2022a_torch.py b/functions/SOLWEIGpython/Kside_veg_v2022a_torch.py index 9d37c4d..a774d81 100644 --- a/functions/SOLWEIGpython/Kside_veg_v2022a_torch.py +++ b/functions/SOLWEIGpython/Kside_veg_v2022a_torch.py @@ -1,10 +1,12 @@ from __future__ import absolute_import from .Kvikt_veg import Kvikt_veg + try: import torch except: pass + def Kside_veg_v2022a( radI, radD, diff --git a/functions/SOLWEIGpython/Kup_veg_2015a_torch.py b/functions/SOLWEIGpython/Kup_veg_2015a_torch.py index dff01c6..9bd2af4 100644 --- a/functions/SOLWEIGpython/Kup_veg_2015a_torch.py +++ b/functions/SOLWEIGpython/Kup_veg_2015a_torch.py @@ -3,6 +3,7 @@ except: pass + def Kup_veg_2015a( radI, radD, diff --git a/functions/SOLWEIGpython/Lcyl_v2022a.py b/functions/SOLWEIGpython/Lcyl_v2022a.py index a7fc5da..7a8e9eb 100644 --- a/functions/SOLWEIGpython/Lcyl_v2022a.py +++ b/functions/SOLWEIGpython/Lcyl_v2022a.py @@ -155,11 +155,5 @@ def Lcyl_v2022a( current_step, ) - # print('Lside_sky old = ' + str(Lside_sky.max())) - # print('Lside_veg old = ' + str(Lside_veg.max())) - # print('Lside_sh old = ' + str(Lside_sh.max())) - # print('Lside_sun old = ' + str(Lside_sun.max())) - # print('Lside_ref old = ' + str(Lside_ref.max())) - return Ldown, Lside, Least_, Lwest_, Lnorth_, Lsouth_ # return Ldown, Lside, Lside_sky, Lside_veg, Lside_sh, Lside_sun, Lside_ref, Lsky_normal, Lsky_down, Lsky_side, Least_, Lwest_, Lnorth_, Lsouth_ diff --git a/functions/SOLWEIGpython/Lside_veg_torch.py b/functions/SOLWEIGpython/Lside_veg_torch.py index 1aa3540..91d5347 100644 --- a/functions/SOLWEIGpython/Lside_veg_torch.py +++ b/functions/SOLWEIGpython/Lside_veg_torch.py @@ -1,10 +1,12 @@ from __future__ import absolute_import from .Lvikt_veg_torch import Lvikt_veg + try: import torch except: pass + def Lside_veg_v2022a( svfS, svfW, diff --git a/functions/SOLWEIGpython/Lside_veg_v2015a.py b/functions/SOLWEIGpython/Lside_veg_v2015a.py index 3aecfb3..a9604b1 100644 --- a/functions/SOLWEIGpython/Lside_veg_v2015a.py +++ b/functions/SOLWEIGpython/Lside_veg_v2015a.py @@ -2,133 +2,208 @@ import numpy as np from .Lvikt_veg import Lvikt_veg -def Lside_veg_v2015a(svfS,svfW,svfN,svfE,svfEveg,svfSveg,svfWveg,svfNveg,svfEaveg,svfSaveg,svfWaveg,svfNaveg,azimuth,altitude,Ta,Tw,SBC,ewall,Ldown,esky,t,F_sh,CI,LupE,LupS,LupW,LupN): + +def Lside_veg_v2015a( + svfS, + svfW, + svfN, + svfE, + svfEveg, + svfSveg, + svfWveg, + svfNveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + azimuth, + altitude, + Ta, + Tw, + SBC, + ewall, + Ldown, + esky, + t, + F_sh, + CI, + LupE, + LupS, + LupW, + LupN, +): # This m-file is the current one that estimates L from the four cardinal points 20100414 - - #Building height angle from svf - svfalfaE=np.arcsin(np.exp((np.log(1-svfE))/2)) - svfalfaS=np.arcsin(np.exp((np.log(1-svfS))/2)) - svfalfaW=np.arcsin(np.exp((np.log(1-svfW))/2)) - svfalfaN=np.arcsin(np.exp((np.log(1-svfN))/2)) - - vikttot=4.4897 - aziW=azimuth+t - aziN=azimuth-90+t - aziE=azimuth-180+t - aziS=azimuth-270+t - - F_sh = 2*F_sh-1 #(cylindric_wedge scaled 0-1) - - c=1-CI - Lsky_allsky = esky*SBC*((Ta+273.15)**4)*(1-c)+c*SBC*((Ta+273.15)**4) - + + # Building height angle from svf + svfalfaE = np.arcsin(np.exp((np.log(1 - svfE)) / 2)) + svfalfaS = np.arcsin(np.exp((np.log(1 - svfS)) / 2)) + svfalfaW = np.arcsin(np.exp((np.log(1 - svfW)) / 2)) + svfalfaN = np.arcsin(np.exp((np.log(1 - svfN)) / 2)) + + vikttot = 4.4897 + aziW = azimuth + t + aziN = azimuth - 90 + t + aziE = azimuth - 180 + t + aziS = azimuth - 270 + t + + F_sh = 2 * F_sh - 1 # (cylindric_wedge scaled 0-1) + + c = 1 - CI + Lsky_allsky = esky * SBC * ((Ta + 273.15) ** 4) * (1 - c) + c * SBC * ( + (Ta + 273.15) ** 4 + ) + ## Least - [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg(svfE, svfEveg, svfEaveg, vikttot) - + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfE, svfEveg, svfEaveg, vikttot + ) + if altitude > 0: # daytime - alfaB=np.arctan(svfalfaE) - betaB=np.arctan(np.tan((svfalfaE)*F_sh)) - betasun=((alfaB-betaB)/2)+betaB + alfaB = np.arctan(svfalfaE) + betaB = np.arctan(np.tan((svfalfaE) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB # betasun = np.arctan(0.5*np.tan(svfalfaE)*(1+F_sh)) #TODO This should be considered in future versions - if (azimuth > (180-t)) and (azimuth <= (360-t)): - Lwallsun=SBC*ewall*((Ta+273.15+Tw*np.sin(aziE*(np.pi/180)))**4)*\ - viktwall*(1-F_sh)*np.cos(betasun)*0.5 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*F_sh*0.5 + if (azimuth > (180 - t)) and (azimuth <= (360 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziE * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) else: - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 - else: #nighttime - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 - - Lsky=((svfE+svfEveg-1)*Lsky_allsky)*viktsky*0.5 - Lveg=SBC*ewall*((Ta+273.15)**4)*viktveg*0.5 - Lground=LupE*0.5 - Lrefl=(Ldown+LupE)*(viktrefl)*(1-ewall)*0.5 - Least=Lsky+Lwallsun+Lwallsh+Lveg+Lground+Lrefl - + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + Lsky = ((svfE + svfEveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupE * 0.5 + Lrefl = (Ldown + LupE) * (viktrefl) * (1 - ewall) * 0.5 + Least = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky - + ## Lsouth - [viktveg,viktwall,viktsky,viktrefl]=Lvikt_veg(svfS,svfSveg,svfSaveg,vikttot) - - if altitude>0: # daytime - alfaB=np.arctan(svfalfaS) - betaB=np.arctan(np.tan((svfalfaS)*F_sh)) - betasun=((alfaB-betaB)/2)+betaB + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfS, svfSveg, svfSaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = np.arctan(svfalfaS) + betaB = np.arctan(np.tan((svfalfaS) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB # betasun = np.arctan(0.5*np.tan(svfalfaS)*(1+F_sh)) - if (azimuth <= (90-t)) or (azimuth > (270-t)): - Lwallsun=SBC*ewall*((Ta+273.15+Tw*np.sin(aziS*(np.pi/180)))**4)*\ - viktwall*(1-F_sh)*np.cos(betasun)*0.5 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*F_sh*0.5 + if (azimuth <= (90 - t)) or (azimuth > (270 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziS * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) else: - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 - else: #nighttime - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 - - Lsky=((svfS+svfSveg-1)*Lsky_allsky)*viktsky*0.5 - Lveg=SBC*ewall*((Ta+273.15)**4)*viktveg*0.5 - Lground=LupS*0.5 - Lrefl=(Ldown+LupS)*(viktrefl)*(1-ewall)*0.5 - Lsouth=Lsky+Lwallsun+Lwallsh+Lveg+Lground+Lrefl - + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + Lsky = ((svfS + svfSveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupS * 0.5 + Lrefl = (Ldown + LupS) * (viktrefl) * (1 - ewall) * 0.5 + Lsouth = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky - + ## Lwest - [viktveg,viktwall,viktsky,viktrefl]=Lvikt_veg(svfW,svfWveg,svfWaveg,vikttot) - - if altitude>0: # daytime - alfaB=np.arctan(svfalfaW) - betaB=np.arctan(np.tan((svfalfaW)*F_sh)) - betasun=((alfaB-betaB)/2)+betaB + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfW, svfWveg, svfWaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = np.arctan(svfalfaW) + betaB = np.arctan(np.tan((svfalfaW) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB # betasun = np.arctan(0.5*np.tan(svfalfaW)*(1+F_sh)) - if (azimuth > (360-t)) or (azimuth <= (180-t)): - Lwallsun=SBC*ewall*((Ta+273.15+Tw*np.sin(aziW*(np.pi/180)))**4)*\ - viktwall*(1-F_sh)*np.cos(betasun)*0.5 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*F_sh*0.5 + if (azimuth > (360 - t)) or (azimuth <= (180 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziW * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) else: - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 - else: #nighttime - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 - - Lsky=((svfW+svfWveg-1)*Lsky_allsky)*viktsky*0.5 - Lveg=SBC*ewall*((Ta+273.15)**4)*viktveg*0.5 - Lground=LupW*0.5 - Lrefl=(Ldown+LupW)*(viktrefl)*(1-ewall)*0.5 - Lwest=Lsky+Lwallsun+Lwallsh+Lveg+Lground+Lrefl - + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + Lsky = ((svfW + svfWveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupW * 0.5 + Lrefl = (Ldown + LupW) * (viktrefl) * (1 - ewall) * 0.5 + Lwest = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl + # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky - + ## Lnorth - [viktveg,viktwall,viktsky,viktrefl]=Lvikt_veg(svfN,svfNveg,svfNaveg,vikttot) - - if altitude>0: # daytime - alfaB=np.arctan(svfalfaN) - betaB=np.arctan(np.tan((svfalfaN)*F_sh)) - betasun=((alfaB-betaB)/2)+betaB + [viktveg, viktwall, viktsky, viktrefl] = Lvikt_veg( + svfN, svfNveg, svfNaveg, vikttot + ) + + if altitude > 0: # daytime + alfaB = np.arctan(svfalfaN) + betaB = np.arctan(np.tan((svfalfaN) * F_sh)) + betasun = ((alfaB - betaB) / 2) + betaB # betasun = np.arctan(0.5*np.tan(svfalfaN)*(1+F_sh)) - if (azimuth > (90-t)) and (azimuth <= (270-t)): - Lwallsun=SBC*ewall*((Ta+273.15+Tw*np.sin(aziN*(np.pi/180)))**4)*\ - viktwall*(1-F_sh)*np.cos(betasun)*0.5 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*F_sh*0.5 + if (azimuth > (90 - t)) and (azimuth <= (270 - t)): + Lwallsun = ( + SBC + * ewall + * ((Ta + 273.15 + Tw * np.sin(aziN * (np.pi / 180))) ** 4) + * viktwall + * (1 - F_sh) + * np.cos(betasun) + * 0.5 + ) + Lwallsh = ( + SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * F_sh * 0.5 + ) else: - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 - else: #nighttime - Lwallsun=0 - Lwallsh=SBC*ewall*((Ta+273.15)**4)*viktwall*0.5 - - Lsky=((svfN+svfNveg-1)*Lsky_allsky)*viktsky*0.5 - Lveg=SBC*ewall*((Ta+273.15)**4)*viktveg*0.5 - Lground=LupN*0.5 - Lrefl=(Ldown+LupN)*(viktrefl)*(1-ewall)*0.5 - Lnorth=Lsky+Lwallsun+Lwallsh+Lveg+Lground+Lrefl + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + else: # nighttime + Lwallsun = 0 + Lwallsh = SBC * ewall * ((Ta + 273.15) ** 4) * viktwall * 0.5 + + Lsky = ((svfN + svfNveg - 1) * Lsky_allsky) * viktsky * 0.5 + Lveg = SBC * ewall * ((Ta + 273.15) ** 4) * viktveg * 0.5 + Lground = LupN * 0.5 + Lrefl = (Ldown + LupN) * (viktrefl) * (1 - ewall) * 0.5 + Lnorth = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky - - return Least,Lsouth,Lwest,Lnorth \ No newline at end of file + + return Least, Lsouth, Lwest, Lnorth diff --git a/functions/SOLWEIGpython/Lvikt_veg_torch.py b/functions/SOLWEIGpython/Lvikt_veg_torch.py index 8ad4e02..0e42c06 100644 --- a/functions/SOLWEIGpython/Lvikt_veg_torch.py +++ b/functions/SOLWEIGpython/Lvikt_veg_torch.py @@ -3,6 +3,7 @@ except: pass + def Lvikt_veg(svf, svfveg, svfaveg, vikttot): device = None if isinstance(svf, torch.Tensor): diff --git a/functions/SOLWEIGpython/PET_calculations.py b/functions/SOLWEIGpython/PET_calculations.py index 73dd3c1..773b2b5 100644 --- a/functions/SOLWEIGpython/PET_calculations.py +++ b/functions/SOLWEIGpython/PET_calculations.py @@ -40,8 +40,7 @@ def calculate_PET_grid(Ta, RH, Tmrt, va, pet, feedback): pet_index = np.zeros_like(Tmrt) total = 100.0 / (int(pet_index.shape[0] * pet_index.shape[1])) index = 0 - # print(Tmrt.shape) - # print(va.shape) + for y in range(pet_index.shape[0]): if feedback.isCanceled(): feedback.setProgressText("Calculation cancelled") diff --git a/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py b/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py index c89f014..306c2d8 100644 --- a/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py +++ b/functions/SOLWEIGpython/Solweig_2025a_calc_forprocessing.py @@ -777,4 +777,4 @@ def Solweig_2025a_calc( Kside, steradians, voxelTable, - ) \ No newline at end of file + ) diff --git a/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing_torch.py b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing_torch.py index 48af240..4109a7c 100644 --- a/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing_torch.py +++ b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing_torch.py @@ -7,7 +7,9 @@ from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b_torch import ( clearnessindex_2013b, ) -from ...util.SEBESOLWEIGCommonFiles.diffusefraction_torch import diffusefraction +from ...util.SEBESOLWEIGCommonFiles.diffusefraction_torch import ( + diffusefraction, +) from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_13_torch import ( shadowingfunction_wallheight_13, ) @@ -29,13 +31,17 @@ from .anisotropic_sky import anisotropic_sky as ani_sky from .patch_radiation import patch_steradians from copy import deepcopy + try: import torch except: pass # Ground surface temperature -from .ground_surface_torch import surfaceTemperature_calc, outgoingLongwave_calc +from .ground_surface_torch import ( + surfaceTemperature_calc, + outgoingLongwave_calc, +) def Solweig_2026a_calc( diff --git a/functions/SOLWEIGpython/Solweig_run.py b/functions/SOLWEIGpython/Solweig_run.py index 1d899cc..e764ce8 100644 --- a/functions/SOLWEIGpython/Solweig_run.py +++ b/functions/SOLWEIGpython/Solweig_run.py @@ -58,14 +58,13 @@ pass - def solweig_run(configPath, feedback): """ Input: configPath : config file including geodata paths and settings. feedback : To communicate with qgis gui. Set to None if standalone """ - + print("Running cpu") # Load config file @@ -616,7 +615,7 @@ def solweig_run(configPath, feedback): TgK_wall = param["Ts_deg"]["Value"]["Walls"] Tstart_wall = param["Tstart"]["Value"]["Walls"] TmaxLST_wall = param["TmaxLST"]["Value"]["Walls"] - + # Parameterization for the 2026 ground scheme groundSurface = int(configDict["groundmodel"]) if groundSurface == 1: @@ -656,10 +655,10 @@ def solweig_run(configPath, feedback): ) else: pass - + # Replace the ground view factors with integration of solid angles outgoingLW = int(configDict["outgoinglongwave"]) - + # Import data for wall temperature parameterization TODO: fix for standalone wallScheme = int(configDict["wallscheme"]) if wallScheme == 1: @@ -1432,4 +1431,4 @@ def solweig_run(configPath, feedback): tmrtplot, dsm_transf, dsm_crs, - ) \ No newline at end of file + ) diff --git a/functions/SOLWEIGpython/Solweig_run_torch.py b/functions/SOLWEIGpython/Solweig_run_torch.py index 07a6775..1f5c5fe 100644 --- a/functions/SOLWEIGpython/Solweig_run_torch.py +++ b/functions/SOLWEIGpython/Solweig_run_torch.py @@ -34,6 +34,7 @@ import pandas as pd import matplotlib.pylab as plt from shutil import copyfile + try: import torch except: @@ -72,7 +73,6 @@ def solweig_run(configPath, feedback): "Please install it using: pip install torch in your venv or using osgeo4w" ) - # Load config file configDict = read_solweig_config(configPath) @@ -420,7 +420,6 @@ def solweig_run(configPath, feedback): ) wallaspect = torch.tensor(wallaspect, device=device) - # Metdata headernum = 1 delim = " " @@ -515,7 +514,7 @@ def solweig_run(configPath, feedback): header=header, comments="", ) - # print(poisxy) + # Num format for POI output numformat = "%d %d %d %d %.5f " + "%.2f " * 35 @@ -773,7 +772,10 @@ def solweig_run(configPath, feedback): # Calculate wall height for wall scheme, i.e. include corners (thicker walls) walls_scheme = wa.findwalls_sp( - dsm, 2, device, torch.tensor([[1, 1, 1], [1, 0, 1], [1, 1, 1]]).to(device) + dsm, + 2, + device, + torch.tensor([[1, 1, 1], [1, 0, 1], [1, 1, 1]]).to(device), ) # Calculate wall aspect for wall scheme, i.e. include corners (thicker walls) dirwalls_scheme = wa.filter1Goodwin_as_aspect_v3( @@ -930,8 +932,6 @@ def solweig_run(configPath, feedback): else: CI = 1.0 - - # Timestep of the simulation used in the ground scheme calculation first_timestep = ( pd.to_datetime(int(YYYY[0][0].item()), format="%Y") @@ -947,7 +947,6 @@ def solweig_run(configPath, feedback): ) timeStep = (second_timestep - first_timestep).seconds - ( Tmrt, @@ -1520,7 +1519,7 @@ def solweig_run(configPath, feedback): ] ] ).to(device) - # print(settingsData) + np.savetxt( configDict["output_dir"] + "/treeplantersettings.txt", ( diff --git a/functions/SOLWEIGpython/Tgmaps_v1_torch.py b/functions/SOLWEIGpython/Tgmaps_v1_torch.py index ad15225..e75070d 100644 --- a/functions/SOLWEIGpython/Tgmaps_v1_torch.py +++ b/functions/SOLWEIGpython/Tgmaps_v1_torch.py @@ -3,6 +3,7 @@ except: pass + def Tgmaps_v1(lc_grid, solweig_parameters): # Tgmaps_v1 Populates grids with cooeficients for Tg wave diff --git a/functions/SOLWEIGpython/anisotropic_sky.py b/functions/SOLWEIGpython/anisotropic_sky.py index efc5a39..93c08c0 100644 --- a/functions/SOLWEIGpython/anisotropic_sky.py +++ b/functions/SOLWEIGpython/anisotropic_sky.py @@ -272,8 +272,6 @@ def anisotropic_sky( else: azimuth_difference = np.abs(solar_azimuth - patch_azimuth[i]) - # print('Building pixels = ' + str(temp_sh.sum())) - # print('Building pixels wall scheme = ' + str(np.sum(temp_sh_w > 0))) ( Lside_sun_temp, Lside_sh_temp, diff --git a/functions/SOLWEIGpython/daylen_torch.py b/functions/SOLWEIGpython/daylen_torch.py index 5c746d4..bcd9f5b 100644 --- a/functions/SOLWEIGpython/daylen_torch.py +++ b/functions/SOLWEIGpython/daylen_torch.py @@ -3,6 +3,7 @@ except: pass + def daylen(DOY, XLAT): # Calculation of declination of sun (Eqn. 16). Amplitude= +/-23.45 # deg. Minimum = DOY 355 (DEC 21), maximum = DOY 172.5 (JUN 21/22). diff --git a/functions/SOLWEIGpython/ground_surface.py b/functions/SOLWEIGpython/ground_surface.py index b8225e7..9d9f0af 100644 --- a/functions/SOLWEIGpython/ground_surface.py +++ b/functions/SOLWEIGpython/ground_surface.py @@ -399,7 +399,7 @@ def outgoingLongwave_calc( # Boolean array 1 if the pixel is a wall, 0 if not wallbol = walls > 0 - + # The alb grids only take into account the sunlit surfaces in the alb calculation albnosh calculate it for all the surfaces albsunlit = alb * shadow @@ -411,7 +411,7 @@ def outgoingLongwave_calc( # Grid of the outgoing longwave radiation coming from the ground Lup = (SBC * emis * (Tg + 273.15) ** 4 + Ldown * (1 - emis)) * buildings - + ### Initialize the ground view factor grids as np.zeros() # Upwelling longwave radiation gvfLup = np.zeros((rows, cols)) @@ -458,10 +458,10 @@ def outgoingLongwave_calc( # Boolean array 1 if the pixel is (or was) a wall, 0 if not pastwalls = np.copy(wallbol) - + # Grid of the longwave radiation emitted by the walls Lwall = SBC * emis_wall * (Tgwall + Ta + 273.15) ** 4 * wallbol - + # Initialisation of the tables # First the ones containing the translated rasters (temporary) building_temp = np.copy(buildings) @@ -585,7 +585,7 @@ def outgoingLongwave_calc( int(x_select_start) : math.ceil(x_select_end), int(y_select_start) : math.ceil(y_select_end), ] - + # Change the boolean building grid, if the px was already a building it remains one (px value = 0) building_copy = np.min([building_copy, building_temp], axis=0) @@ -596,7 +596,7 @@ def outgoingLongwave_calc( Lup_sum += Lup_temp * view_factor * building_copy / 20 albsun_sum += albsun_temp * view_factor * building_copy / 20 albtot_sum += albtot_temp * view_factor * building_copy / 20 - + # Create a boolean grid to assert that the sunlit walls are not inside a building onlywall_temp = np.logical_and( walls_temp, np.logical_not(pastwalls) @@ -622,7 +622,7 @@ def outgoingLongwave_calc( / zs * np.sqrt((r + sizepx / 2) ** 2 + zs**2) ) - + # Then add the radiation incoming from those walls Lup_sum += ( onlywall_temp @@ -641,7 +641,7 @@ def outgoingLongwave_calc( albtot_sum += ( onlywall_temp * alb_wall * viewfactor_wall * building_copy / 20 ) - + # Finally add the radiation in Lside dphi = np.arctan((r + step) / zs) - np.arctan(r / zs) dtrigo = zs / np.sqrt(r**2 + zs**2) * r / np.sqrt( @@ -649,7 +649,7 @@ def outgoingLongwave_calc( ) - zs / np.sqrt((r + step) ** 2 + zs**2) * (r + step) / np.sqrt( (r + step) ** 2 + zs**2 ) - + # Calculation of the solid angle for each of the cardinal points # plus add the radiation from a potential wall steradiansW, steradiansS, steradiansE, steradiansN = 0, 0, 0, 0 @@ -746,4 +746,4 @@ def outgoingLongwave_calc( gvfLsideS, gvfLsideE, gvfLsideN, - ) \ No newline at end of file + ) diff --git a/functions/SOLWEIGpython/ground_surface_torch.py b/functions/SOLWEIGpython/ground_surface_torch.py index 1d3b8cd..54cfc5b 100644 --- a/functions/SOLWEIGpython/ground_surface_torch.py +++ b/functions/SOLWEIGpython/ground_surface_torch.py @@ -3,6 +3,7 @@ """ import math + try: import torch except: @@ -418,7 +419,7 @@ def outgoingLongwave_calc( # Copy of the sunlit wall grid and replacement of the wall height with 1 if sunlit sunlitwall = sunwall sunlitwall[sunlitwall > 0] = 1 - + # Boolean array 1 if the pixel is a wall, 0 if not wallbol = walls > 0 @@ -430,7 +431,7 @@ def outgoingLongwave_calc( # step in meters between every iteration step = 1 - + # Grid of the outgoing longwave radiation coming from the ground Lup = (SBC * emis * (Tg + 273.15) ** 4 + Ldown * (1 - emis)) * buildings @@ -480,7 +481,7 @@ def outgoingLongwave_calc( # Boolean array 1 if the pixel is (or was) a wall, 0 if not pastwalls = wallbol.clone() - + # Grid of the longwave radiation emitted by the walls Lwall = SBC * emis_wall * (Tgwall + Ta + 273.15) ** 4 * wallbol @@ -607,7 +608,7 @@ def outgoingLongwave_calc( int(x_select_start) : math.ceil(x_select_end), int(y_select_start) : math.ceil(y_select_end), ] - + # All walls grid walls_temp[ int(x_transl_start) : math.ceil(x_transl_end), @@ -628,42 +629,44 @@ def outgoingLongwave_calc( albsun_sum += albsun_temp * view_factor * building_copy / 20 albtot_sum += albtot_temp * view_factor * building_copy / 20 - # Create a boolean grid to assert that the sunlit walls are not inside a building onlywall_temp = torch.logical_and( walls_temp, torch.logical_not(pastwalls) ) - onlywall_temp = onlywall_temp * building_copy + onlywall_temp = onlywall_temp * building_copy onlysunwall_temp = sunlitwall_temp * building_copy pastwalls = torch.logical_or(pastwalls, walls_temp) - - + # Compute the view factor of the wall surface for a given translatio distance viewfactor_wall = ( 1 / 2 ** (1 / 2) / 3 - * torch.sqrt(1 + (r + step) / torch.sqrt((r + step) ** 2 + zs**2)) + * torch.sqrt( + 1 + (r + step) / torch.sqrt((r + step) ** 2 + zs**2) + ) * ( 2 - + (r + sizepx / 2) / torch.sqrt((r + sizepx / 2) ** 2 + zs**2) + + (r + sizepx / 2) + / torch.sqrt((r + sizepx / 2) ** 2 + zs**2) ) * ( 1 - - (r + sizepx / 2) / torch.sqrt((r + sizepx / 2) ** 2 + zs**2) + - (r + sizepx / 2) + / torch.sqrt((r + sizepx / 2) ** 2 + zs**2) ) / zs * torch.sqrt((r + sizepx / 2) ** 2 + zs**2) ) - + # Then add the radiation incoming from those walls Lup_sum += ( onlywall_temp * Lwall_temp * viewfactor_wall * building_copy - / 20 + / 20 ) albsun_sum += ( onlysunwall_temp @@ -673,7 +676,7 @@ def outgoingLongwave_calc( / 20 ) albtot_sum += ( - onlywall_temp * alb_wall * viewfactor_wall * building_copy / 20 + onlywall_temp * alb_wall * viewfactor_wall * building_copy / 20 ) # Finally add the radiation in Lside diff --git a/functions/SOLWEIGpython/patch_radiation.py b/functions/SOLWEIGpython/patch_radiation.py index e46ac50..651e014 100644 --- a/functions/SOLWEIGpython/patch_radiation.py +++ b/functions/SOLWEIGpython/patch_radiation.py @@ -160,7 +160,6 @@ def longwave_from_buildings( and (solar_altitude > 0) ): - # Calculate longwave radiation from sunlit walls to vertical surface Lside_sun = ( sunlit_surface @@ -342,12 +341,8 @@ def longwave_from_buildings_wallScheme( Lwest = np.zeros((voxelMaps.shape[0], voxelMaps.shape[1])) Lnorth = np.zeros((voxelMaps.shape[0], voxelMaps.shape[1])) - # print(voxelMaps) - # print(voxelTable.head()) l = list(np.unique(voxelMaps)[1:]) - # print(l) a = dict(voxelTable.loc[l, "LongwaveRadiation"]) - # print(a) patch_radiation = np.vectorize(a.get)(voxelMaps).astype(float) patch_radiation[np.isnan(patch_radiation)] = 0 Lside += patch_radiation * steradian * angle_of_incidence diff --git a/functions/svf_for_voxels.py b/functions/svf_for_voxels.py index 48e5de5..e0fca1d 100644 --- a/functions/svf_for_voxels.py +++ b/functions/svf_for_voxels.py @@ -250,11 +250,13 @@ def svf_kmeans( svf_height_array, feedback, ): - + try: from sklearn.cluster import KMeans except: - raise ImportError("[UMEP-processing Error] pleas install sklearn via pip install scikit-learn or via osgeo4w") + raise ImportError( + "[UMEP-processing Error] pleas install sklearn via pip install scikit-learn or via osgeo4w" + ) # Calculate where there are buildings and not. Used to elevate dem. ground = dsm - dem @@ -456,7 +458,8 @@ def svf_kmeans( # get the calculated svf for the wall pixel to check if it is # higher or lower than 0.5 temp_svf = svftotal[ - int(voxelTable[temp_y, 5][0]), int(voxelTable[temp_y, 6][0]) + int(voxelTable[temp_y, 5][0]), + int(voxelTable[temp_y, 6][0]), ] if ( temp_svf < 0.5 @@ -536,4 +539,4 @@ def interpolate_svf(voxelTable): new_svf # Add the new SVFs to table ) - return voxelTable \ No newline at end of file + return voxelTable diff --git a/functions/svf_for_voxels_torch.py b/functions/svf_for_voxels_torch.py index 6122367..591944f 100644 --- a/functions/svf_for_voxels_torch.py +++ b/functions/svf_for_voxels_torch.py @@ -1,4 +1,3 @@ - try: from sklearn.cluster import KMeans except: @@ -31,7 +30,6 @@ def wallscheme_prepare( torch.tensor([[1, 1, 1], [1, 0, 1], [1, 1, 1]], device=dsm.device), ) walls_copy = torch.clone(walls) - print("-1 device :" + str(device)) aspect = wa.filter1Goodwin_as_aspect_v3( walls_copy, scale, dsm, feedback, 100, device ) @@ -301,8 +299,10 @@ def svf_kmeans( try: from sklearn.cluster import KMeans except: - raise ImportError("[UMEP-processing Error] pleas install sklearn via pip install scikit-learn or via osgeo4w") - + raise ImportError( + "[UMEP-processing Error] pleas install sklearn via pip install scikit-learn or via osgeo4w" + ) + with torch.no_grad(): dsm = _to_tensor(dsm, device) @@ -350,7 +350,8 @@ def svf_kmeans( for i in cluster_range: # cluster_heights[counter] = torch.round(building_heights[kmeans_clusters == i].mean()) cluster_heights[counter] = ( - torch.round(wallHeights[kmeans_clusters == i].mean()) - svf_height + torch.round(wallHeights[kmeans_clusters == i].mean()) + - svf_height ) # Remove svf_height which is the voxel size to be below the top of the wall counter += 1 @@ -559,25 +560,25 @@ def interpolate_svf(voxelTable): unique_wall_pixels = np.unique(voxelTable_np[:, 4]) unique_wall_pixels = unique_wall_pixels[unique_wall_pixels != 0] - + for unique_wall in unique_wall_pixels: # Mask for the current wall pixel wall_mask = voxelTable_np[:, 4] == unique_wall - temp_data = voxelTable_np[wall_mask, :] # All data for current wall pixel - + temp_data = voxelTable_np[ + wall_mask, : + ] # All data for current wall pixel + # Mask for valid SVF calculation heights valid_svf_mask = temp_data[:, -1] != 0 - temp_heights = temp_data[valid_svf_mask, 1] # Voxel heights - temp_svf = temp_data[valid_svf_mask, -4] # SVF values - + temp_heights = temp_data[valid_svf_mask, 1] # Voxel heights + temp_svf = temp_data[valid_svf_mask, -4] # SVF values + if len(temp_heights) == 1: calculated_svf = temp_svf[0] if len(temp_svf) > 0 else 0.0 new_svf = np.full(temp_data.shape[0], calculated_svf) - + elif len(temp_heights) > 1: # Interpolate - new_svf = np.interp( - temp_data[:, 1], temp_heights, temp_svf - ) + new_svf = np.interp(temp_data[:, 1], temp_heights, temp_svf) else: continue diff --git a/functions/svf_functions.py b/functions/svf_functions.py index b947cdf..bc48285 100644 --- a/functions/svf_functions.py +++ b/functions/svf_functions.py @@ -572,4 +572,4 @@ def svfForProcessing655(dsm, vegdem, vegdem2, scale, usevegdem, feedback): "svfNaveg": svfNaveg, } - return svfresult \ No newline at end of file + return svfresult diff --git a/functions/svf_functions_torch.py b/functions/svf_functions_torch.py index 1141a47..f1c7857 100644 --- a/functions/svf_functions_torch.py +++ b/functions/svf_functions_torch.py @@ -142,9 +142,8 @@ def svfForProcessing153( feedback, device=torch.device("cpu"), ): - - with torch.no_grad(): + with torch.no_grad(): dsm = _to_tensor(dsm, device) vegdem = _to_tensor(vegdem, device) @@ -211,7 +210,9 @@ def svfForProcessing153( skyvaultaziint = torch.tensor( [360 / patches for patches in aziinterval], device=device ) - iazimuth = torch.zeros(int(torch.sum(aziinterval).item()), device=device) + iazimuth = torch.zeros( + int(torch.sum(aziinterval).item()), device=device + ) shmat = torch.zeros( (rows, cols, int(torch.sum(aziinterval).item())), device=device @@ -238,7 +239,6 @@ def svfForProcessing153( ) = svfv.wallscheme_prepare( dsm, scale, pixel_resolution, feedback, device=device ) - print("-2 device :" + str(device)) # Rasters to fill with values in loop all_buildIDSeen = torch.zeros( @@ -343,7 +343,9 @@ def svfForProcessing153( device, ) - vegsh = torch.tensor(shadowresult["vegsh"], device=device) + vegsh = torch.tensor( + shadowresult["vegsh"], device=device + ) vbshvegsh = torch.tensor( shadowresult["vbshvegsh"], device=device ) @@ -388,7 +390,9 @@ def svfForProcessing153( weight = annulus_weight(k, aziinterval[i], device) * sh svf = svf + weight - weight = annulus_weight(k, aziintervalaniso[i], device) * sh + weight = ( + annulus_weight(k, aziintervalaniso[i], device) * sh + ) if (azimuth >= 0) and (azimuth < 180): svfE = svfE + weight if (azimuth >= 90) and (azimuth < 270): @@ -421,7 +425,9 @@ def svfForProcessing153( svfNaveg = svfNaveg + weight * vbshvegsh index += 1 - feedback.setProgress(int(index * (100.0 / torch.sum(aziinterval)))) + feedback.setProgress( + int(index * (100.0 / torch.sum(aziinterval))) + ) svfS = svfS + 3.0459e-004 svfW = svfW + 3.0459e-004 @@ -491,7 +497,7 @@ def svfForProcessing655( feedback, device=torch.device("cpu"), ): - + with torch.no_grad(): dsm = _to_tensor(dsm, device) @@ -587,7 +593,9 @@ def svfForProcessing655( ): weight = annulus_weight(k, aziinterval[i], device) * sh svf = svf + weight - weight = annulus_weight(k, aziintervalaniso[i], device) * sh + weight = ( + annulus_weight(k, aziintervalaniso[i], device) * sh + ) if (azimuth >= 0) and (azimuth < 180): svfE = svfE + weight if (azimuth >= 90) and (azimuth < 270): diff --git a/functions/wallalgorithms.py b/functions/wallalgorithms.py index 2db9812..bbb4cdc 100644 --- a/functions/wallalgorithms.py +++ b/functions/wallalgorithms.py @@ -221,4 +221,4 @@ def get_ders(dsm, scale): grad = np.arctan(grad) asp = asp * -1 asp = asp + (asp < 0) * (np.pi * 2) - return grad, asp \ No newline at end of file + return grad, asp diff --git a/functions/wallalgorithms_torch.py b/functions/wallalgorithms_torch.py index 5669a36..18082ec 100644 --- a/functions/wallalgorithms_torch.py +++ b/functions/wallalgorithms_torch.py @@ -4,6 +4,7 @@ __author__ = "xlinfr" import math + try: import torch import torch.nn.functional as F @@ -114,7 +115,10 @@ def filter1Goodwin_as_aspect_v3( row, col = a.shape # 1. Compute kernel footprint based on scale factor - filtersize = torch.floor((scale + torch.tensor(0.0000000001, device=device)) * torch.tensor(9, device=device)) + filtersize = torch.floor( + (scale + torch.tensor(0.0000000001, device=device)) + * torch.tensor(9, device=device) + ) if filtersize <= 2: filtersize = 3 elif filtersize != 9 and filtersize % 2 == 0: @@ -136,8 +140,6 @@ def filter1Goodwin_as_aspect_v3( filtmatrix_list = [] buildfilt1_list = [] buildfilt2_list = [] - print("-0 device :" + str(device)) - # 2. Pre-calculate all 180 directional filters on CPU or GPU with torch.no_grad(): @@ -171,7 +173,7 @@ def filter1Goodwin_as_aspect_v3( buildfilt2_list.append( (filtmatrixbuild == 2).float().unsqueeze(0).unsqueeze(0) ) - + # Stacking kernels on CPU first - Now perfectly defined! all_kernels_walls = torch.cat(filtmatrix_list, dim=0) all_kernels_dsm1 = torch.cat(buildfilt1_list, dim=0) diff --git a/lc_update.py b/lc_update.py index f642367..9b37b06 100644 --- a/lc_update.py +++ b/lc_update.py @@ -4,8 +4,10 @@ import numpy as np # Open the landcover file -lc_filepath = r'/home/lemap/Documents/suede/datasets/gotenburg/cut2/lc_cut.tif' -lc_filepathout = r'/home/lemap/Documents/suede/datasets/gotenburg/cut2/lc_cut_out.tif' +lc_filepath = r"/home/lemap/Documents/suede/datasets/gotenburg/cut2/lc_cut.tif" +lc_filepathout = ( + r"/home/lemap/Documents/suede/datasets/gotenburg/cut2/lc_cut_out.tif" +) landcover = rasterio.open(lc_filepath) crs = landcover.profile lc_data = landcover.read() @@ -13,5 +15,5 @@ lc_data[lc_data == 4] = 2 lc_data[lc_data == 3] = 2 -new_lc = rasterio.open(lc_filepathout, 'w', **crs) -new_lc.write(lc_data) \ No newline at end of file +new_lc = rasterio.open(lc_filepathout, "w", **crs) +new_lc.write(lc_data) diff --git a/preprocessor/skyviewfactor_algorithm.py b/preprocessor/skyviewfactor_algorithm.py index 5ca3c10..1c2be3b 100644 --- a/preprocessor/skyviewfactor_algorithm.py +++ b/preprocessor/skyviewfactor_algorithm.py @@ -63,6 +63,7 @@ from ..functions import svf_functions as svf from ..functions import svf_for_voxels as svfv + class ProcessingSkyViewFactorAlgorithm(QgsProcessingAlgorithm): """ This algorithm is a processing version of SkyViewFactor @@ -288,24 +289,24 @@ def processAlgorithm(self, parameters, context, feedback): if parameters["OUTPUT_DIR"] == "TEMPORARY_OUTPUT": if not (os.path.isdir(outputDir)): os.mkdir(outputDir) - + device = None - + if use_gpu: # Check if torch is the fake version from torch_fallback.py then tells the # user to install the real pip module - if type(torch).__name__ == "MetaMock" or hasattr(torch, "__getattr__"): + if type(torch).__name__ == "MetaMock" or hasattr( + torch, "__getattr__" + ): raise ImportError( "\n[UMEP Error] PyTorch is required to run gpu mode.\n" "Please install it using: pip install torch in your venv or using osgeo4w" - ) - if torch.cuda.is_available(): + ) + if torch.cuda.is_available(): device = torch.device("cuda") else: device = torch.device("cpu") - - provider = dsmlayer.dataProvider() filepath_dsm = str(provider.dataSourceUri()) gdal_dsm = gdal.Open(filepath_dsm) @@ -323,7 +324,7 @@ def processAlgorithm(self, parameters, context, feedback): geotransform = gdal_dsm.GetGeoTransform() pixel_resolution = geotransform[1] scale = 1 / pixel_resolution - + if use_gpu: dsm = torch.from_numpy(dsm).to(device) @@ -406,7 +407,7 @@ def processAlgorithm(self, parameters, context, feedback): if aniso == 1: feedback.setProgressText("Calculating SVF using 153 iterations") - + if use_gpu: print("gpu version") @@ -424,7 +425,7 @@ def processAlgorithm(self, parameters, context, feedback): ) else: print("cpu version") - ret = svf.svfForProcessing153( + ret = svf.svfForProcessing153( dsm, vegdsm, vegdsm2, @@ -438,7 +439,7 @@ def processAlgorithm(self, parameters, context, feedback): else: feedback.setProgressText("Calculating SVF using 655 iterations") - + if use_gpu: print("gpu version") @@ -481,26 +482,26 @@ def processAlgorithm(self, parameters, context, feedback): svftotal = svfbu - (1 - svfveg) * (1 - trans) # Lägg till loop för att lägga till i tabellen svf_array = np.zeros((voxelTable.shape[0])) - svf_height_array = np.zeros( - (voxelTable.shape[0]) - ) + svf_height_array = np.zeros((voxelTable.shape[0])) svfbu_array = np.zeros((voxelTable.shape[0])) svfveg_array = np.zeros((voxelTable.shape[0])) svfaveg_array = np.zeros((voxelTable.shape[0])) - + if use_gpu: svf_array = torch.from_numpy(svf_array).to(device) - svf_height_array = torch.from_numpy(svf_height_array).to(device) + svf_height_array = torch.from_numpy(svf_height_array).to( + device + ) svfbu_array = torch.from_numpy(svfbu_array).to(device) svfveg_array = torch.from_numpy(svfveg_array).to(device) svfaveg_array = torch.from_numpy(svfaveg_array).to(device) - + voxel_y = None if use_gpu: voxel_y = torch.where(voxelTable[:, 2] != 0) else: voxel_y = np.where(voxelTable[:, 2] != 0) - + for temp_y in voxel_y[0]: svf_array[temp_y] = svftotal[ int(voxelTable[temp_y, 5]), int(voxelTable[temp_y, 6]) @@ -517,7 +518,7 @@ def processAlgorithm(self, parameters, context, feedback): svf_height_array[temp_y] = svf_height if kmeans: - + if use_gpu: print("gpu version") @@ -544,9 +545,7 @@ def processAlgorithm(self, parameters, context, feedback): ) # Interpolate for voxels where SVF has not been calculated - voxelTable = svfv_torch.interpolate_svf( - voxelTable - ) + voxelTable = svfv_torch.interpolate_svf(voxelTable) else: voxelTable, cluster_heights = svfv.svf_kmeans( @@ -571,9 +570,7 @@ def processAlgorithm(self, parameters, context, feedback): ) # Interpolate for voxels where SVF has not been calculated - voxelTable = svfv.interpolate_svf( - voxelTable - ) + voxelTable = svfv.interpolate_svf(voxelTable) # Loop for exact SVF at heights (increase DEM) # if demlayer: @@ -623,7 +620,7 @@ def processAlgorithm(self, parameters, context, feedback): svf_height_array, feedback, ) - + # Remove rows where svfbu, sfveg and svfaveg is zero if usevegdem == 1: voxelTable = voxelTable[ @@ -654,7 +651,7 @@ def processAlgorithm(self, parameters, context, feedback): svfbuS = ret["svfS"] svfbuW = ret["svfW"] svfbuN = ret["svfN"] - + if use_gpu: misc.saveraster( @@ -682,7 +679,7 @@ def processAlgorithm(self, parameters, context, feedback): outputDir + "/" + "svfN.tif", svfbuN.cpu().detach().numpy(), ) - + else: misc.saveraster( gdal_dsm, @@ -741,7 +738,7 @@ def processAlgorithm(self, parameters, context, feedback): svfSaveg = ret["svfSaveg"] svfWaveg = ret["svfWaveg"] svfNaveg = ret["svfNaveg"] - + if use_gpu: misc.saveraster( @@ -878,9 +875,7 @@ def processAlgorithm(self, parameters, context, feedback): gdal_dsm, filename, svftotal.cpu().detach().numpy() ) else: - misc.saveraster( - gdal_dsm, filename, svftotal - ) + misc.saveraster(gdal_dsm, filename, svftotal) # Save shadow images for SOLWEIG 2019a if aniso == 1: @@ -903,19 +898,18 @@ def processAlgorithm(self, parameters, context, feedback): vbshmat=vbshvegshmat, ) # , - if wallScheme == 1: voxelId = ret["voxelIds"] voxelTable = ret["voxelTable"] - if use_gpu: + if use_gpu: np.savez_compressed( outputDir + "/" + "wallScheme.npz", voxelId=voxelId.cpu().detach().numpy(), voxelTable=voxelTable.cpu().detach().numpy(), ) else: - np.savez_compressed( + np.savez_compressed( outputDir + "/" + "wallScheme.npz", voxelId=voxelId, voxelTable=voxelTable, diff --git a/preprocessor/wall_heightaspect_algorithm.py b/preprocessor/wall_heightaspect_algorithm.py index 0543938..53c924c 100644 --- a/preprocessor/wall_heightaspect_algorithm.py +++ b/preprocessor/wall_heightaspect_algorithm.py @@ -133,21 +133,23 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText(str(cmd_folder.parent)) device = None if use_gpu: - + # Check if torch is the fake version from torch_fallback.py then tells the # user to install the real pip module - if type(torch).__name__ == "MetaMock" or hasattr(torch, "__getattr__"): + if type(torch).__name__ == "MetaMock" or hasattr( + torch, "__getattr__" + ): raise ImportError( "\n[UMEP Error] PyTorch is required to run gpu mode.\n" "Please install it using: pip install torch in your venv or using osgeo4w" - ) + ) try: torch.set_num_threads(max(1, os.cpu_count())) torch.set_num_interop_threads(max(1, os.cpu_count())) except: pass - - if torch.cuda.is_available(): + + if torch.cuda.is_available(): device = torch.device("cuda") feedback.setProgressText( "GPU detected and will be used for calculations." @@ -156,7 +158,7 @@ def processAlgorithm(self, parameters, context, feedback): device = torch.device("cpu") feedback.setProgressText( "No GPU detected.CPU will be used for calculations." - ) + ) provider = dsmin.dataProvider() filepath_dsm = str(provider.dataSourceUri()) @@ -165,7 +167,7 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText("Calculating wall height") total = 100.0 / (int(dsm.shape[0] * dsm.shape[1])) - + if use_gpu: walls = wa_torch.findwalls_sp(dsm, walllimit, device, False) else: @@ -175,9 +177,7 @@ def processAlgorithm(self, parameters, context, feedback): if use_gpu: wallssave = wallssave.cpu().detach().numpy() - saverasternd( - gdal_dsm, outputFileHeight, wallssave - ) + saverasternd(gdal_dsm, outputFileHeight, wallssave) if outputFileAspect: total = 100.0 / 180.0 @@ -192,14 +192,12 @@ def processAlgorithm(self, parameters, context, feedback): torch.tensor(total, device=device), device, ) - + if use_gpu: dirwalls = dirwalls.cpu().detach().numpy() - saverasternd( - gdal_dsm, outputFileAspect, dirwalls - ) + saverasternd(gdal_dsm, outputFileAspect, dirwalls) else: - + if use_gpu: dirwalls = wa_torch.filter1Goodwin_as_aspect_v3( walls, @@ -216,9 +214,7 @@ def processAlgorithm(self, parameters, context, feedback): feedback, total, ) - saverasternd( - gdal_dsm, outputFileAspect, dirwalls - ) + saverasternd(gdal_dsm, outputFileAspect, dirwalls) else: feedback.setProgressText("Wall aspect not calculated") diff --git a/processor/sebe_algorithm.py b/processor/sebe_algorithm.py index 6be6c58..2b4f702 100644 --- a/processor/sebe_algorithm.py +++ b/processor/sebe_algorithm.py @@ -597,4 +597,4 @@ def icon(self): return icon def createInstance(self): - return ProcessingSEBEAlgorithm() \ No newline at end of file + return ProcessingSEBEAlgorithm() diff --git a/processor/solweig_algorithm.py b/processor/solweig_algorithm.py index ab770c7..96a2a65 100644 --- a/processor/solweig_algorithm.py +++ b/processor/solweig_algorithm.py @@ -90,7 +90,7 @@ class ProcessingSOLWEIGAlgorithm(QgsProcessingAlgorithm): INPUT_DEM = "INPUT_DEM" SAVE_BUILD = "SAVE_BUILD" INPUT_ANISO = "INPUT_ANISO" - USE_GROUNDSCHEME = "USE_GROUNDSCHEME" + USE_GROUNDSCHEME = "USE_GROUNDSCHEME" USE_OUTGOINGLW = "USE_OUTGOINGLW" INPUT_GROUNDSCHEME = "INPUT_GROUNDSCHEME" INPUT_WALLSCHEME = "INPUT_WALLSCHEME" @@ -725,7 +725,7 @@ def processAlgorithm(self, parameters, context, feedback): ) useOutgoingLW = self.parameterAsString( parameters, self.USE_OUTGOINGLW, context - ) + ) groundTempFile = self.parameterAsString( parameters, self.INPUT_GROUNDSCHEME, context ) @@ -1175,7 +1175,7 @@ def processAlgorithm(self, parameters, context, feedback): else: feedback.setProgressText("Isotropic sky") anisotropic_sky = 0 - + ### Ground cover scheme # Surface temperature parameterization if useGroundScheme: @@ -1199,9 +1199,7 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText( "The ground cover scheme described in 2016 is activated" ) - outgoingLW = 0 - - + outgoingLW = 0 # % Ts parameterisation maps if landcover == 1.0: @@ -1324,7 +1322,7 @@ def processAlgorithm(self, parameters, context, feedback): # Main function feedback.setProgressText("Executing main model") - + if gpu_bool: sr_torch.solweig_run(outputDir + "/configsolweig.ini", feedback) else: diff --git a/util/SEBESOLWEIGCommonFiles/Perez_v3.py b/util/SEBESOLWEIGCommonFiles/Perez_v3.py index bf0bd27..c9d60ab 100644 --- a/util/SEBESOLWEIGCommonFiles/Perez_v3.py +++ b/util/SEBESOLWEIGCommonFiles/Perez_v3.py @@ -209,10 +209,7 @@ def Perez_v3(zen, azimuth, radD, radI, jday, patchchoice, patch_option): if Idh <= 10: # m_a=0;m_b=0;m_c=0;m_d=0;m_e=0; PerezBrightness = 0 - # if altitude < 0: - # print("Airmass") - # print(AirMass) - # print(PerezBrightness) + # sky clearness bins if PerezClearness < 1.065: intClearness = 0 diff --git a/util/SEBESOLWEIGCommonFiles/Perez_v3_torch.py b/util/SEBESOLWEIGCommonFiles/Perez_v3_torch.py index 3a2a27b..1b777d7 100644 --- a/util/SEBESOLWEIGCommonFiles/Perez_v3_torch.py +++ b/util/SEBESOLWEIGCommonFiles/Perez_v3_torch.py @@ -1,10 +1,12 @@ from __future__ import division from .create_patches_torch import create_patches + try: import torch except: pass + def Perez_v3( zen, azimuth, radD, radI, jday, patchchoice, patch_option, device ): diff --git a/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload_torch.py b/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload_torch.py index 2ece78b..0625f47 100644 --- a/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload_torch.py +++ b/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload_torch.py @@ -6,11 +6,13 @@ # import sun_position as sp import datetime import calendar + try: import torch except: pass + def Solweig_2015a_metdata_noload(inputdata, location, UTC): """ This function is used to process the input meteorological file. diff --git a/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b_torch.py b/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b_torch.py index 49e9bb6..f42bbf3 100644 --- a/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b_torch.py +++ b/util/SEBESOLWEIGCommonFiles/clearnessindex_2013b_torch.py @@ -4,6 +4,7 @@ from . import sun_distance_torch import math + try: import torch except: diff --git a/util/SEBESOLWEIGCommonFiles/create_patches_torch.py b/util/SEBESOLWEIGCommonFiles/create_patches_torch.py index 4c0945c..4a12162 100644 --- a/util/SEBESOLWEIGCommonFiles/create_patches_torch.py +++ b/util/SEBESOLWEIGCommonFiles/create_patches_torch.py @@ -3,6 +3,7 @@ except: pass + def create_patches(patch_option, device): deg2rad = torch.pi / 180 diff --git a/util/SEBESOLWEIGCommonFiles/diffusefraction_torch.py b/util/SEBESOLWEIGCommonFiles/diffusefraction_torch.py index c352251..ad5f503 100644 --- a/util/SEBESOLWEIGCommonFiles/diffusefraction_torch.py +++ b/util/SEBESOLWEIGCommonFiles/diffusefraction_torch.py @@ -1,9 +1,11 @@ from __future__ import division + try: import torch except: pass + def diffusefraction(radG, altitude, Kt, Ta, RH): """ This function estimates diffuse and directbeam radiation according to diff --git a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13_torch.py b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13_torch.py index 9826b43..14dfae7 100644 --- a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13_torch.py +++ b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13_torch.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import division from math import radians + try: import torch import torch.nn.functional as F diff --git a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py index a8b102c..fa04a08 100644 --- a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py +++ b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23.py @@ -228,22 +228,21 @@ def shadowingfunction_wallheight_23( shvoveg = (shvoveg - a) * vegsh # Vegetation shadow volume vegsh = 1 - vegsh vbshvegsh = 1 - vbshvegsh - # print(np.max(shvoveg)) + wallsh, wallsun, wallshve, facesh, facesun = shade_on_walls( azimuth, aspect, walls, a, f, shvoveg ) - # print(np.max(wallshve)) + if walls_scheme is not False: wallsh_, wallsun_, wallshve_, facesh_, facesun_ = shade_on_walls( azimuth, aspect_scheme, walls_scheme, a, f, shvoveg ) - # print(np.max(wallshve_)) + shade_on_wall = wallsh_.copy() shade_on_wall[shade_on_wall < wallshve_] = wallshve_[ shade_on_wall < wallshve_ ] - # return vegsh, sh, vbshvegsh, wallsh, wallsun, wallshve, facesh, facesun, # shade_on_wall return ( ( diff --git a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23_torch.py b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23_torch.py index d7ff229..879dc9d 100644 --- a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23_torch.py +++ b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23_torch.py @@ -217,16 +217,15 @@ def shadowingfunction_wallheight_23( vegsh = 1 - vegsh vbshvegsh = 1 - vbshvegsh - # print(torch.max(shvoveg)) wallsh, wallsun, wallshve, facesh, facesun = shade_on_walls( azimuth, aspect, walls, a, f, shvoveg, device ) - # print(torch.max(wallshve)) + if walls_scheme is not False: wallsh_, wallsun_, wallshve_, facesh_, facesun_ = shade_on_walls( azimuth, aspect_scheme, walls_scheme, a, f, shvoveg, device ) - # print(torch.max(wallshve_)) + shade_on_wall = wallsh_.clone() shade_on_wall[shade_on_wall < wallshve_] = wallshve_[ shade_on_wall < wallshve_ diff --git a/util/SEBESOLWEIGCommonFiles/sun_distance_torch.py b/util/SEBESOLWEIGCommonFiles/sun_distance_torch.py index 58d8ffd..7a94896 100644 --- a/util/SEBESOLWEIGCommonFiles/sun_distance_torch.py +++ b/util/SEBESOLWEIGCommonFiles/sun_distance_torch.py @@ -4,6 +4,7 @@ except: pass + def sun_distance(jday): """ diff --git a/util/SEBESOLWEIGCommonFiles/sun_position.py b/util/SEBESOLWEIGCommonFiles/sun_position.py index f9664bc..cd24a21 100644 --- a/util/SEBESOLWEIGCommonFiles/sun_position.py +++ b/util/SEBESOLWEIGCommonFiles/sun_position.py @@ -105,7 +105,6 @@ def sun_position(time, location): # 1. Calculate the Julian Day, and Century. Julian Ephemeris day, century # and millenium are calculated using a mean delta_t of 33.184 seconds. julian = julian_calculation(time) - # print(julian) # 2. Calculate the Earth heliocentric longitude, latitude, and radius # vector (L, B, and R) @@ -1203,7 +1202,7 @@ def sun_topocentric_zenith_angle_calculate( apparent_elevation = true_elevation + refraction_corr sun = dict() - sun["zenith"] = 90 - apparent_elevation + sun["zenith"] = (90 - apparent_elevation)[0] # Topocentric azimuth angle. The +180 conversion is to pass from astronomer # notation (westward from south) to navigation notation (eastward from @@ -1219,7 +1218,7 @@ def sun_topocentric_zenith_angle_calculate( sun["azimuth"] = (np.arctan2(nominator, denominator) * 180 / np.pi) + 180 # Set the range to [0-360] - sun["azimuth"] = set_to_range(sun["azimuth"], 0, 360) + sun["azimuth"] = set_to_range(sun["azimuth"], 0, 360)[0] return sun diff --git a/util/SEBESOLWEIGCommonFiles/sun_position_torch.py b/util/SEBESOLWEIGCommonFiles/sun_position_torch.py index f61f4ef..18acfca 100644 --- a/util/SEBESOLWEIGCommonFiles/sun_position_torch.py +++ b/util/SEBESOLWEIGCommonFiles/sun_position_torch.py @@ -1,11 +1,13 @@ # -*- coding: utf-8 -*- from __future__ import division from __future__ import print_function + try: import torch except: pass + def sun_position(time, location): """ % sun = sun_position(time, location) diff --git a/util/landCoverFractions_v2.py b/util/landCoverFractions_v2.py index 6f6da28..47e5608 100644 --- a/util/landCoverFractions_v2.py +++ b/util/landCoverFractions_v2.py @@ -35,7 +35,6 @@ def landcover_v2(lc_grid, mid, dtheta, feedback, imp_point, iter): lc_frac = np.zeros((int(360.0 / dtheta), iter)) deg = np.zeros((int(360.0 / dtheta), 1)) - j = int(0) for angle in np.arange(0, 360, dtheta): if imp_point == 1: diff --git a/util/shadowingfunctions.py b/util/shadowingfunctions.py index 6b8ce49..14732c2 100644 --- a/util/shadowingfunctions.py +++ b/util/shadowingfunctions.py @@ -87,8 +87,6 @@ def shadowingfunctionglobalradiation( return sh - - def shadowingfunction_20( a, vegdem, @@ -517,8 +515,6 @@ def shadowingfunction_findwallID( # yet (saved in previous iteration). buildIDSeen = (temp2 > 0) * temp3 * tempwallID + buildIDSeen - - # voxelHeight = the elevation on a wall that is seen from a pixel with the given altitude and azimuth (only above ground leve, i.e. (temp2 > 0)). # voxelHeight = wall height - descending wall, i.e. temp_wallHeight - # temp2. Only applicable to pixels where there is no value from @@ -557,7 +553,6 @@ def shadowingfunction_findwallID( # Remove rows where both columns are zero d = d[~np.all(d == 0, axis=1)] - not_in_list = 0 in_list = 0 # Fill voxelId matrix with unique voxel IDs @@ -586,4 +581,4 @@ def shadowingfunction_findwallID( voxelHeight = voxelHeight * (1 - sh) voxelId = voxelId * (1 - sh) - return buildIDSeen, voxelHeight, voxelId \ No newline at end of file + return buildIDSeen, voxelHeight, voxelId diff --git a/util/shadowingfunctions_torch.py b/util/shadowingfunctions_torch.py index a3e2c3c..badb699 100644 --- a/util/shadowingfunctions_torch.py +++ b/util/shadowingfunctions_torch.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Ready for python action! from math import radians + try: import torch import torch.nn.functional as F diff --git a/util/ssParms.py b/util/ssParms.py index 2a589ac..92fa444 100644 --- a/util/ssParms.py +++ b/util/ssParms.py @@ -15,7 +15,6 @@ ) - def ss_calc(build, cdsm, walls, numPixels, feedback): walllimit = 0.3 # 30 centimeters height variation identifies a vegetation edge pixel diff --git a/util/torch_fallback.py b/util/torch_fallback.py index edc7562..4c4a1e4 100644 --- a/util/torch_fallback.py +++ b/util/torch_fallback.py @@ -5,9 +5,11 @@ import torch import torch.nn.functional as F except ImportError: + class MetaMock(type): def __getattr__(cls, name): return cls + def __call__(cls, *args, **kwargs): return cls @@ -15,11 +17,11 @@ class MockTorch(metaclass=MetaMock): pass mock_instance = MockTorch - + # 1. Fix for files that DO execute "import torch" - sys.modules['torch'] = mock_instance - sys.modules['torch.nn.functional'] = mock_instance + sys.modules["torch"] = mock_instance + sys.modules["torch.nn.functional"] = mock_instance # 2. Fix for files that DO NOT execute "import torch" but use the word anyway - setattr(builtins, 'torch', mock_instance) - setattr(builtins, 'F', mock_instance) \ No newline at end of file + setattr(builtins, "torch", mock_instance) + setattr(builtins, "F", mock_instance) diff --git a/util/umep_solweig_export_component.py b/util/umep_solweig_export_component.py index 0e12793..19e3a63 100644 --- a/util/umep_solweig_export_component.py +++ b/util/umep_solweig_export_component.py @@ -106,14 +106,12 @@ def write_solweig_config(configDict, refdir): "# use anisotrphic sky (Wallenberg et al. XXXX and Wallenberg et al. XXXX)\n" ) f.write("aniso={}\n".format(configDict["aniso"])) - f.write( - "# use the surface temperature parameterization v2026a\n" - ) + f.write("# use the surface temperature parameterization v2026a\n") f.write("groundmodel={}\n".format(configDict["groundmodel"])) f.write( "# use the outgoing longwave radiation computation scheme v2026a\n" ) - f.write("outgoinglongwave={}\n".format(configDict["outgoingLW"])) + f.write("outgoinglongwave={}\n".format(configDict["outgoingLW"])) f.write( "# use wall surface temperature scheme (Wallenberg et al. 2025, GMD)\n" ) From 1031e30597f13d9f2a665a9f2996c068237cc1f9 Mon Sep 17 00:00:00 2001 From: "Mael.PHILIPPE" Date: Tue, 9 Jun 2026 10:43:06 +0200 Subject: [PATCH 15/20] improved user feedback --- functions/SOLWEIGpython/Solweig_run_torch.py | 10 ++++++---- preprocessor/skyviewfactor_algorithm.py | 6 +++--- preprocessor/wall_heightaspect_algorithm.py | 6 +++--- processor/solweig_algorithm.py | 5 +---- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/functions/SOLWEIGpython/Solweig_run_torch.py b/functions/SOLWEIGpython/Solweig_run_torch.py index f62402b..0bebcf0 100644 --- a/functions/SOLWEIGpython/Solweig_run_torch.py +++ b/functions/SOLWEIGpython/Solweig_run_torch.py @@ -90,11 +90,13 @@ def solweig_run(configPath, feedback): if configDict["calculation_mode"] == "gpu" and torch.cuda.is_available(): device = torch.device("cuda") - print("using gpu") - + feedback.setProgressText( + "PyTorch and GPU found. Initiating GPU mode..." + ) else: - print("using cpu") - + feedback.setProgressText( + "Pytorch found but GPU not found. Initiating CPU mode..." + ) standAlone = int(configDict["standalone"]) # Load DSM diff --git a/preprocessor/skyviewfactor_algorithm.py b/preprocessor/skyviewfactor_algorithm.py index 9d2bb32..f3c8888 100644 --- a/preprocessor/skyviewfactor_algorithm.py +++ b/preprocessor/skyviewfactor_algorithm.py @@ -304,18 +304,18 @@ def processAlgorithm(self, parameters, context, feedback): ): raise QgsProcessingException( "\n[UMEP Error] PyTorch is required to run GPU mode.\n" - "Please install it using: pip install torch" + "Please install it using: pip install torch or with osgeo4w" ) if torch.cuda.is_available(): device = torch.device("cuda") feedback.setProgressText( - "PyTorch found. Initiating GPU mode..." + "PyTorch and GPU found. Initiating GPU mode..." ) else: device = torch.device("cpu") feedback.setProgressText( - "PyTorch not found. Initiating CPU mode..." + "Pytorch found but GPU not found. Initiating CPU mode..." ) else: # Fall back to standard CPU processing diff --git a/preprocessor/wall_heightaspect_algorithm.py b/preprocessor/wall_heightaspect_algorithm.py index 11cbb26..61cad81 100644 --- a/preprocessor/wall_heightaspect_algorithm.py +++ b/preprocessor/wall_heightaspect_algorithm.py @@ -147,18 +147,18 @@ def processAlgorithm(self, parameters, context, feedback): ): raise QgsProcessingException( "\n[UMEP Error] PyTorch is required to run GPU mode.\n" - "Please install it using: pip install torch" + "Please install it using: pip install torch or with osgeo4w" ) if torch.cuda.is_available(): device = torch.device("cuda") feedback.setProgressText( - "PyTorch found. Initiating GPU mode..." + "PyTorch and GPU found. Initiating GPU mode..." ) else: device = torch.device("cpu") feedback.setProgressText( - "PyTorch not found. Initiating CPU mode..." + "Pytorch found but GPU not found. Initiating CPU mode..." ) else: diff --git a/processor/solweig_algorithm.py b/processor/solweig_algorithm.py index 9eb4dd8..059250c 100644 --- a/processor/solweig_algorithm.py +++ b/processor/solweig_algorithm.py @@ -853,7 +853,6 @@ def processAlgorithm(self, parameters, context, feedback): parameters, self.OUTPUT_TREEPLANTER, context ) outputKdiff = False - # outputSstr = False # If "Save necessary rasters for TreePlanter tool" is ticked, save the following raster for TreePlanter or Spatial TC if outputTreeplanter: @@ -865,7 +864,6 @@ def processAlgorithm(self, parameters, context, feedback): outputSh = True saveBuild = True outputKdiff = True - # outputSstr = True calculation_mode = "cpu" if gpu_bool: @@ -876,11 +874,10 @@ def processAlgorithm(self, parameters, context, feedback): ): raise QgsProcessingException( "\n[UMEP Error] PyTorch is required to run GPU mode.\n" - "Please install it using: pip install torch" + "Please install it using: pip install torch or with osgeo4w" ) # If PyTorch is available, execute the GPU path - feedback.setProgressText("PyTorch found. Initiating GPU mode...") calculation_mode = "gpu" else: From 50b9485399e8ce7dfaa7ade927a35b63c9f5b59e Mon Sep 17 00:00:00 2001 From: "Mael.PHILIPPE" Date: Thu, 11 Jun 2026 14:31:52 +0200 Subject: [PATCH 16/20] memory optimisations and bug fixing --- .../SOLWEIGpython/Kside_veg_v2022a_torch.py | 3 + functions/SOLWEIGpython/Lside_veg_torch.py | 8 +- functions/SOLWEIGpython/Lvikt_veg_torch.py | 3 + .../Solweig_2026a_calc_forprocessing_torch.py | 13 +++ functions/SOLWEIGpython/Solweig_run.py | 51 +--------- functions/SOLWEIGpython/Solweig_run_torch.py | 73 +++++++++++--- .../SOLWEIGpython/cylindric_wedge_torch.py | 6 +- functions/SOLWEIGpython/daylen_torch.py | 3 +- .../SOLWEIGpython/ground_surface_torch.py | 2 + functions/dailyshading.py | 1 - functions/svf_for_voxels_torch.py | 4 + functions/svf_functions_torch.py | 53 ++++++++-- functions/wallalgorithms_torch.py | 55 ++++------- preprocessor/skyviewfactor_algorithm.py | 92 +++++++++++++++++- preprocessor/wall_heightaspect_algorithm.py | 13 +++ processor/solweig_algorithm.py | 54 +++------- symbology-style.db | Bin 0 -> 86016 bytes .../Solweig_v2015_metdata_noload_torch.py | 19 +--- .../clearnessindex_2013b_torch.py | 2 + util/SEBESOLWEIGCommonFiles/create_patches.py | 4 - .../create_patches_torch.py | 4 + .../shadowingfunction_wallheight_13_torch.py | 7 +- .../shadowingfunction_wallheight_23_torch.py | 8 +- .../sun_position_torch.py | 13 ++- util/umep_solweig_export_component.py | 8 +- 25 files changed, 311 insertions(+), 188 deletions(-) create mode 100644 symbology-style.db diff --git a/functions/SOLWEIGpython/Kside_veg_v2022a_torch.py b/functions/SOLWEIGpython/Kside_veg_v2022a_torch.py index a774d81..cf6de07 100644 --- a/functions/SOLWEIGpython/Kside_veg_v2022a_torch.py +++ b/functions/SOLWEIGpython/Kside_veg_v2022a_torch.py @@ -568,4 +568,7 @@ def Kside_veg_v2022a( ) * 0.5 Knorth = KnorthI + KnorthDG + if device.type == "cuda": + torch.cuda.empty_cache() + return Keast, Ksouth, Kwest, Knorth, KsideI, KsideD, Kside diff --git a/functions/SOLWEIGpython/Lside_veg_torch.py b/functions/SOLWEIGpython/Lside_veg_torch.py index 91d5347..aab0cc9 100644 --- a/functions/SOLWEIGpython/Lside_veg_torch.py +++ b/functions/SOLWEIGpython/Lside_veg_torch.py @@ -264,8 +264,8 @@ def Lside_veg_v2022a( Lrefl = (Ldown + LupN) * (viktrefl) * (1 - ewall) * 0.5 Lnorth = Lsky + Lwallsun + Lwallsh + Lveg + Lground + Lrefl - # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky - + if device.type == "cuda": + torch.cuda.empty_cache() return Least, Lsouth, Lwest, Lnorth @@ -506,6 +506,6 @@ def Lside_veg_v2026( Lrefl = Ldown * (viktrefl) * (1 - ewall) * 0.5 Lnorth = Lsky + Lwallsun + Lwallsh + Lveg + Lrefl - # clear alfaB betaB betasun Lsky Lwallsh Lwallsun Lveg Lground Lrefl viktveg viktwall viktsky - + if device.type == "cuda": + torch.cuda.empty_cache() return Least, Lsouth, Lwest, Lnorth diff --git a/functions/SOLWEIGpython/Lvikt_veg_torch.py b/functions/SOLWEIGpython/Lvikt_veg_torch.py index 0e42c06..937f10e 100644 --- a/functions/SOLWEIGpython/Lvikt_veg_torch.py +++ b/functions/SOLWEIGpython/Lvikt_veg_torch.py @@ -79,4 +79,7 @@ def Lvikt_veg(svf, svfveg, svfaveg, vikttot): ) / vikttot viktveg = viktveg - viktwall + if device.type == "cuda": + torch.cuda.empty_cache() + return viktveg, viktwall, viktsky, viktrefl diff --git a/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing_torch.py b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing_torch.py index 4109a7c..1aba5fb 100644 --- a/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing_torch.py +++ b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing_torch.py @@ -250,6 +250,7 @@ def Solweig_2026a_calc( ) if (CI > 1) or (CI == torch.inf): CI = 1 + del I0et, CIuncorr # Estimation of radD and radI if not measured after Reindl et al.(1990) if onlyglobal == 1: @@ -258,6 +259,7 @@ def Solweig_2026a_calc( ) if (CI > 1) or (CI == torch.inf): CI = 1 + del I0et, CIuncorr radI, radD = diffusefraction(radG, altitude, Kt, Ta, RH) @@ -285,6 +287,7 @@ def Solweig_2026a_calc( dRad = ( aniLum * radD ) # Total diffuse radiation from sky into each cell + del aniLum, pc_, pb_ else: dRad = radD * svfbuveg patchchoice = 1 @@ -309,6 +312,7 @@ def Solweig_2026a_calc( ) ) shadow = sh - (1 - vegsh) * (1 - psi) + del sh, vegsh, wallsh, wallshve, facesun, wallsh_ else: sh, wallsh, wallsun, facesh, facesun, wallsh_ = ( shadowingfunction_wallheight_13( @@ -323,6 +327,7 @@ def Solweig_2026a_calc( ) ) shadow = sh + del sh, wallsh, facesun, wallsh_ # Building height angle from svf F_sh = cylindric_wedge( @@ -333,10 +338,12 @@ def Solweig_2026a_calc( # New estimation of Tgwall with reduction for non-clear situation based on Reindl et al. 1990 radI0, _ = diffusefraction(I0, altitude, 1.0, Ta, RH) radG0 = radI0 * (torch.sin(altitude * deg2rad)) + _ + del radI0, _ corr = ( 0.1473 * torch.log(90 - (zen / torch.pi * 180)) + 0.3454 ) # 20070329 correction of lat, Lindberg et al. 2008 CI_TgG = (radG / radG0) + (1 - corr) + del radG0 if (CI_TgG > 1) or (CI_TgG == torch.inf): CI_TgG = 1 @@ -425,6 +432,7 @@ def Solweig_2026a_calc( _, timeadd, Tg = TsWaveDelay_2015a( TgTemp, firstdaytime, timeadd, timestepdec, Tg ) # timeadd only here v2021a + del TgTemp, Tgdiff if landcover == 1: Tg[Tg < 0] = ( @@ -803,6 +811,7 @@ def Solweig_2026a_calc( z = torch.transpose(torch.atleast_2d(patch_emissivities)) L_patches = torch.append(torch.append(x, y, axis=1), z, axis=1) + del skyvaultalt, skyvaultazi, patch_emissivities, x, y, z else: L_patches = deepcopy(lv) @@ -810,6 +819,7 @@ def Solweig_2026a_calc( # Calculate steradians for patches if it is the first model iteration if i == 0: steradians, skyalt, patch_altitude = patch_steradians(L_patches) + del skyalt, patch_altitude # Create lv from L_patches if nighttime, i.e. lv does not exist if altitude < 0: @@ -930,6 +940,9 @@ def Solweig_2026a_calc( Lnorth += Lnorth_ Lsouth += Lsouth_ + if device.type == "cuda": + torch.cuda.empty_cache() + return ( Tmrt, Kdown, diff --git a/functions/SOLWEIGpython/Solweig_run.py b/functions/SOLWEIGpython/Solweig_run.py index 07a2db9..b85bf01 100644 --- a/functions/SOLWEIGpython/Solweig_run.py +++ b/functions/SOLWEIGpython/Solweig_run.py @@ -58,23 +58,6 @@ pass -# import numpy as np -# from .daylen import daylen -# from ...util.SEBESOLWEIGCommonFiles.clearnessindex_2013b import clearnessindex_2013b -# from ...util.SEBESOLWEIGCommonFiles.diffusefraction import diffusefraction -# from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_13 import shadowingfunction_wallheight_13 -# from ...util.SEBESOLWEIGCommonFiles.shadowingfunction_wallheight_23 import shadowingfunction_wallheight_23 -# from .gvf_2018a import gvf_2018a -# from .cylindric_wedge import cylindric_wedge -# from .TsWaveDelay_2015a import TsWaveDelay_2015a -# from .Kup_veg_2015a import Kup_veg_2015a -# # from .Lside_veg_v2015a import Lside_veg_v2015a -# # from .Kside_veg_v2019a import Kside_veg_v2019a -# from .Kside_veg_v2022a import Kside_veg_v2022a -# from ...util.SEBESOLWEIGCommonFiles.Perez_v3 import Perez_v3 -# from ...util.SEBESOLWEIGCommonFiles.create_patches import create_patches - - def solweig_run(configPath, feedback): """ Input: @@ -411,26 +394,8 @@ def solweig_run(configPath, feedback): "I0 CI Shadow SVF_b SVF_bv KsideI PET UTCI CI_TgG KsideD Lside diffDown Kside " ) # poiname = [] - poi_field = configDict[ - "poi_field" - ] # self.parameterAsString(parameters, self.POI_FIELD, context) + poi_field = configDict["poi_field"] if standAlone == 0: - # vlayer = QgsVectorLayer(configDict['poi_file'], 'point', 'ogr') - # idx = vlayer.fields().indexFromName(poi_field) - # numfeat = vlayer.featureCount() - # poisxy = np.zeros((numfeat, 3)) - 999 - # ind = 0 - # for f in vlayer.getFeatures(): # looping through each POI - # y = f.geometry().centroid().asPoint().y() - # x = f.geometry().centroid().asPoint().x() - # poiname.append(f.attributes()[idx]) - # poisxy[ind, 0] = ind - # poisxy[ind, 1] = np.round((x - minx) * scale) - # if miny >= 0: - # poisxy[ind, 2] = np.round((miny + rows * (1. / scale) - y) * scale) - # else: - # poisxy[ind, 2] = np.round((miny + rows * (1. / scale) - y) * scale) - # ind += 1 poi_field = configDict[ "woi_field" @@ -649,6 +614,7 @@ def solweig_run(configPath, feedback): # Parameterization for the 2026 ground scheme groundSurface = int(configDict["groundmodel"]) + if groundSurface == 1: # Initiate the maps if the surface temperature is available if configDict["input_surf"] != "": @@ -790,7 +756,6 @@ def solweig_run(configPath, feedback): voxelMaps = 0 voxelTable = 0 timeStep = 0 - # thermal_effusivity = 0 walls_scheme = np.ones((rows, cols)) * 10.0 dirwalls_scheme = np.ones((rows, cols)) * 10.0 @@ -800,10 +765,6 @@ def solweig_run(configPath, feedback): else: timestepdec = dectime[1] - dectime[0] timeadd = 0.0 - # timeaddE = 0. - # timeaddS = 0. - # timeaddW = 0. - # timeaddN = 0. firstdaytime = 1.0 # Save hemispheric image @@ -867,14 +828,6 @@ def solweig_run(configPath, feedback): else: CI = 1.0 - # Only if Kdir is derived from horizontal global shortwave and horizontal diffuse shortwave - # if altitude[0][i] > 0: - # radI[i] = radI[i]/np.sin(altitude[0][i] * np.pi/180) - # else: - # radG[i] = 0. - # radD[i] = 0. - # radI[i] = 0. - # Timestep of the simulation used in the ground scheme calculation first_timestep = ( pd.to_datetime(YYYY[0][0], format="%Y") diff --git a/functions/SOLWEIGpython/Solweig_run_torch.py b/functions/SOLWEIGpython/Solweig_run_torch.py index 0bebcf0..3761f1c 100644 --- a/functions/SOLWEIGpython/Solweig_run_torch.py +++ b/functions/SOLWEIGpython/Solweig_run_torch.py @@ -177,6 +177,10 @@ def solweig_run(configPath, feedback): vegdsm = 0 vegdsm2 = 0 + # Clean up intermediate tensors after vegetation loading + if device.type == "cuda": + torch.cuda.empty_cache() + # Land cover landcover = int(configDict["landcover"]) if landcover == 1: @@ -220,6 +224,10 @@ def solweig_run(configPath, feedback): else: demraise = 0 + # Clean up DEM intermediate tensors + if device.type == "cuda": + torch.cuda.empty_cache() + # SVF zip = zipfile.ZipFile(configDict["input_svf"], "r") zip.extractall(configDict["working_dir"]) @@ -395,6 +403,10 @@ def solweig_run(configPath, feedback): svfEaveg = torch.ones((rows, cols)).to(device) svfWaveg = torch.ones((rows, cols)).to(device) + # Clean up after SVF loading + if device.type == "cuda": + torch.cuda.empty_cache() + tmp = svf + svfveg - 1.0 tmp[tmp < 0.0] = 0.0 # %matlab crazyness around 0 @@ -456,22 +468,6 @@ def solweig_run(configPath, feedback): "poi_field" ] # self.parameterAsString(parameters, self.POI_FIELD, context) if standAlone == 0: - # vlayer = QgsVectorLayer(configDict['poi_file'], 'point', 'ogr') - # idx = vlayer.fields().indexFromName(poi_field) - # numfeat = vlayer.featureCount() - # poisxy = torch.zeros((numfeat, 3)) - 999 - # ind = 0 - # for f in vlayer.getFeatures(): # looping through each POI - # y = f.geometry().centroid().asPoint().y() - # x = f.geometry().centroid().asPoint().x() - # poiname.append(f.attributes()[idx]) - # poisxy[ind, 0] = ind - # poisxy[ind, 1] = torch.round((x - minx) * scale) - # if miny >= 0: - # poisxy[ind, 2] = torch.round((miny + rows * (1. / scale) - y) * scale) - # else: - # poisxy[ind, 2] = torch.round((miny + rows * (1. / scale) - y) * scale) - # ind += 1 poi_field = configDict[ "woi_field" @@ -595,6 +591,10 @@ def solweig_run(configPath, feedback): bush = torch.zeros([rows, cols]).to(device) amaxvalue = 0 + # Clean up vegetation processing tensors + if device.type == "cuda": + torch.cuda.empty_cache() + # Initialization of maps Knight = torch.zeros((rows, cols)).to(device) Tgmap1 = torch.zeros((rows, cols)).to(device) @@ -704,6 +704,7 @@ def solweig_run(configPath, feedback): # Parameterization for the 2026 ground scheme groundSurface = int(configDict["groundmodel"]) + if groundSurface == 1: # Initiate the maps if the surface temperature is available if configDict["input_surf"] != "": @@ -854,6 +855,10 @@ def solweig_run(configPath, feedback): walls_scheme = torch.ones((rows, cols)).to(device) * 10.0 dirwalls_scheme = torch.ones((rows, cols)).to(device) * 10.0 + # Clean up wall scheme setup tensors + if device.type == "cuda": + torch.cuda.empty_cache() + # Initialisation of time related variables if Ta.__len__() == 1: timestepdec = 0 @@ -1205,6 +1210,10 @@ def solweig_run(configPath, feedback): ) f_handle.close() + # Clean up POI tensor after writing + if device.type == "cuda": + torch.cuda.empty_cache() + # If wall temperature parameterization scheme is in use if ( configDict["wallscheme"] == 1 @@ -1302,6 +1311,9 @@ def solweig_run(configPath, feedback): fmt=woi_numformat, ) f_handle.close() + del wall_data, temp_all # Clean up wall data tensors + if device.type == "cuda": + torch.cuda.empty_cache() # Save wall temperature/radiation as NetCDF TODO: fix for standAlone? if configDict["wallnetcdf"] == "1": # wallNetCDF: @@ -1438,6 +1450,32 @@ def solweig_run(configPath, feedback): dsm_crs, ) + # Clean up iteration tensors after output + if device.type == "cuda": + del ( + Tmrt, + Kdown, + Kup, + Ldown, + Lup, + Keast, + Ksouth, + Kwest, + Knorth, + Least, + Lsouth, + Lwest, + Lnorth, + KsideI, + radIout, + radDout, + Lside, + KsideD, + dRad, + Kside, + ) + torch.cuda.empty_cache() + # Sky view image of patches if (anisotropic_sky == 1) & (i == 0) & (not poisxy is None): for k in range(poisxy.shape[0]): @@ -1550,3 +1588,6 @@ def solweig_run(configPath, feedback): dsm_transf, dsm_crs, ) + + if device.type == "cuda": + torch.cuda.empty_cache() diff --git a/functions/SOLWEIGpython/cylindric_wedge_torch.py b/functions/SOLWEIGpython/cylindric_wedge_torch.py index ffd4355..9a5d201 100644 --- a/functions/SOLWEIGpython/cylindric_wedge_torch.py +++ b/functions/SOLWEIGpython/cylindric_wedge_torch.py @@ -57,7 +57,8 @@ def cylindric_wedge(zen, svfalfa, rows, cols): Ssurf = hkil + ukil F_sh = (2 * torch.pi * ba - Ssurf) / (2 * torch.pi * ba) # Xa - + if device.type == "cuda": + torch.cuda.empty_cache() return F_sh @@ -108,5 +109,6 @@ def cylindric_wedge_voxel(zen, svfalfa): Ssurf = hkil + ukil F_sh = (2 * torch.pi * ba - Ssurf) / (2 * torch.pi * ba) # Xa - + if device.type == "cuda": + torch.cuda.empty_cache() return F_sh diff --git a/functions/SOLWEIGpython/daylen_torch.py b/functions/SOLWEIGpython/daylen_torch.py index bcd9f5b..24dbbde 100644 --- a/functions/SOLWEIGpython/daylen_torch.py +++ b/functions/SOLWEIGpython/daylen_torch.py @@ -29,5 +29,6 @@ def daylen(DOY, XLAT): DAYL = 12.0 + 24.0 * torch.arcsin(SOC) / torch.pi SNUP = 12.0 - DAYL / 2.0 SNDN = 12.0 + DAYL / 2.0 - + if device.type == "cuda": + torch.cuda.empty_cache() return DAYL, DEC, SNDN, SNUP diff --git a/functions/SOLWEIGpython/ground_surface_torch.py b/functions/SOLWEIGpython/ground_surface_torch.py index 54cfc5b..87d6f9d 100644 --- a/functions/SOLWEIGpython/ground_surface_torch.py +++ b/functions/SOLWEIGpython/ground_surface_torch.py @@ -218,6 +218,8 @@ def initiate_groundScheme( # For water bodies Tg[Tg == i] = Ta[0] + if device.type == "cuda": + torch.cuda.empty_cache() return ( Tg, Tm, diff --git a/functions/dailyshading.py b/functions/dailyshading.py index b466f0e..cd2f050 100644 --- a/functions/dailyshading.py +++ b/functions/dailyshading.py @@ -241,7 +241,6 @@ def dailyshading( saveraster(gdal_data, filenamewallshve, wallshve) shadowresult = {"shfinal": shfinal, "time_vector": time_vector} - return shadowresult diff --git a/functions/svf_for_voxels_torch.py b/functions/svf_for_voxels_torch.py index c042b1c..06c1d6c 100644 --- a/functions/svf_for_voxels_torch.py +++ b/functions/svf_for_voxels_torch.py @@ -118,6 +118,7 @@ def wallscheme_prepare( wall_dict[0] = 0.0 # Convert specific lists to match original return signature formats if downstream requires it + torch.cuda.empty_cache() return ( voxelTable, voxelId_list, @@ -272,6 +273,7 @@ def svf_for_voxels( svfaveg_array, ] ) + torch.cuda.empty_cache() return voxelTable @@ -550,6 +552,7 @@ def svf_kmeans( svfaveg_array, ] ) + torch.cuda.empty_cache() return voxelTable, cluster_heights @@ -584,5 +587,6 @@ def interpolate_svf(voxelTable): continue voxelTable_np[wall_mask, -4] = new_svf + torch.cuda.empty_cache() return torch.from_numpy(voxelTable_np).to(device) diff --git a/functions/svf_functions_torch.py b/functions/svf_functions_torch.py index f1c7857..aee0521 100644 --- a/functions/svf_functions_torch.py +++ b/functions/svf_functions_torch.py @@ -15,11 +15,6 @@ def _to_tensor(x, device, dtype=torch.float32): return torch.tensor(x, dtype=dtype, device=device) -def _sync_if_cuda(device): - if device is not None and device.type == "cuda": - torch.cuda.synchronize() - - # from ..functions.wallalgorithms import findwalls from . import wallalgorithms_torch as wa from . import svf_for_voxels_torch as svfv @@ -46,6 +41,8 @@ def annulus_weight(altitude, aziinterval, device): * torch.sin((torch.pi * (2.0 * annulus - 1.0)) / (2.0 * n)) ) weight = steprad * w + del n, w + torch.cuda.empty_cache() return weight @@ -126,7 +123,8 @@ def svf_angles_100121(device): device=device, ) angleresult = {"iazimuth": iazimuth, "aziinterval": aziinterval} - + del iazimuth, aziinterval + torch.cuda.empty_cache() return angleresult @@ -482,9 +480,29 @@ def svfForProcessing153( "walls": walls, } - # , - # 'vbshvegshmat': vbshvegshmat, 'wallshmat': wallshmat, 'wallsunmat': wallsunmat, - # 'wallshvemat': wallshvemat, 'facesunmat': facesunmat} + # Delete input and intermediate tensors not needed outside + del dsm, vegdem, vegdem2, demlayer, bush, vegmax, amaxvalue, last + + # Delete patch configuration tensors + del ( + skyvaultalt, + skyvaultazi, + annulino, + skyvaultaltint, + aziinterval, + skyvaultaziint, + azistart, + iazimuth, + aziintervalaniso, + ) + + # Delete wall scheme temporary variables + del (voxelTable, walls) + + # Delete loop-specific matrices already reference-copied into svfresult + del shmat, vegshmat, vbshvegshmat, all_voxelId + if device.type == "cuda": + torch.cuda.empty_cache() return svfresult @@ -676,4 +694,21 @@ def svfForProcessing655( "svfWaveg": svfWaveg, "svfNaveg": svfNaveg, } + + # Delete input and intermediate tensors not needed outside + del dsm, vegdem, vegdem2, bush, vegmax, amaxvalue, last + + # Delete angle and patch tensors + del ( + noa, + step, + iangle, + annulino, + angleresult, + aziinterval, + iazimuth, + aziintervalaniso, + ) + if device.type == "cuda": + torch.cuda.empty_cache() return svfresult diff --git a/functions/wallalgorithms_torch.py b/functions/wallalgorithms_torch.py index 18082ec..04947dd 100644 --- a/functions/wallalgorithms_torch.py +++ b/functions/wallalgorithms_torch.py @@ -59,41 +59,6 @@ def findwalls_sp(arr_dsm, walllimit, device, footprint=None): return walls -def findwalls(a, walllimit, feedback, total): - # This function identifies walls based on a DSM and a wall-height limit - # Walls are represented by outer pixels within building footprints - # - # Fredrik Lindberg, Goteborg Urban Climate Group - # fredrikl@gvc.gu.se - # 20150625 - - col, row = a.shape - walls = torch.zeros((col, row)) - domain = torch.tensor([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) - index = 0 - for i in torch.arange(1, row - 1): - if feedback.isCanceled(): - feedback.setProgressText("Calculation cancelled") - break - for j in torch.arange(1, col - 1): - dom = a[j - 1 : j + 2, i - 1 : i + 2] - walls[j, i] = torch.max( - dom[torch.where(domain == 1)] - ) # new 20171006 - index = index + 1 - feedback.setProgress(int(index * total)) - - walls = torch.clone(walls - a) # new 20171006 - walls[(walls < walllimit)] = 0 - - walls[0 : walls.shape[0], 0] = 0 - walls[0 : walls.shape[0], walls.shape[1] - 1] = 0 - walls[0, 0 : walls.shape[0]] = 0 - walls[walls.shape[0] - 1, 0 : walls.shape[1]] = 0 - - return walls - - def filter1Goodwin_as_aspect_v3( walls_for_dir, scale, a, feedback, total, device, tile_size=2048 ): @@ -362,6 +327,26 @@ def filter1Goodwin_as_aspect_v3( if feedback is not None: feedback.setProgress(int(total)) + del ( + filtmatrix, + buildfilt, + filtmatrix_list, + buildfilt1_list, + buildfilt2_list, + ) + del all_kernels_walls, all_kernels_dsm1, all_kernels_dsm2 + del ( + final_x, + walls_binary, + a_device, + border_mask, + valid_mask, + grad, + asp, + asp_device, + ) + if device.type == "cuda": + torch.cuda.empty_cache() return final_y diff --git a/preprocessor/skyviewfactor_algorithm.py b/preprocessor/skyviewfactor_algorithm.py index f3c8888..7ae308c 100644 --- a/preprocessor/skyviewfactor_algorithm.py +++ b/preprocessor/skyviewfactor_algorithm.py @@ -30,6 +30,8 @@ __revision__ = "$Format:%H$" +import gc + from qgis.PyQt.QtCore import QCoreApplication, QVariant from qgis.core import ( QgsProcessingAlgorithm, @@ -389,10 +391,6 @@ def processAlgorithm(self, parameters, context, feedback): ) if vegdsm2: - # vegdsm2 = self.parameterAsRasterLayer(parameters, self.INPUT_TDSM, context) - # if vegdsm2 is None: - # raise QgsProcessingException("Error: No valid Trunk zone DSM selected") - # load raster gdal.AllRegister() provider = vegdsm2.dataProvider() @@ -466,6 +464,9 @@ def processAlgorithm(self, parameters, context, feedback): feedback, device=device, ) + + if device.type == "cuda": + torch.cuda.empty_cache() else: print("cpu version") ret = svf.svfForProcessing655( @@ -477,6 +478,14 @@ def processAlgorithm(self, parameters, context, feedback): feedback, ) + # Clean up large intermediate tensors after SVF calculation + if use_gpu: + del dsm, vegdsm, vegdsm2 + if dem is not None: + del dem + if device.type == "cuda": + torch.cuda.empty_cache() + # print('Time to finish first SVF calculation = ' + str(run_time)) if wallScheme == 1: voxelTable = ret["voxelTable"] @@ -531,6 +540,12 @@ def processAlgorithm(self, parameters, context, feedback): ] svf_height_array[temp_y] = svf_height + # Clean up large SVF arrays after extraction + if use_gpu: + del svftotal, svfbu, svfveg, svfaveg, voxel_y + if device.type == "cuda": + torch.cuda.empty_cache() + if kmeans: if use_gpu: @@ -586,6 +601,18 @@ def processAlgorithm(self, parameters, context, feedback): # Interpolate for voxels where SVF has not been calculated voxelTable = svfv.interpolate_svf(voxelTable) + # Clean up k-means result tensors + if use_gpu: + del ( + svf_array, + svfbu_array, + svfveg_array, + svfaveg_array, + svf_height_array, + ) + if device.type == "cuda": + torch.cuda.empty_cache() + # Loop for exact SVF at heights (increase DEM) # if demlayer: else: @@ -635,6 +662,18 @@ def processAlgorithm(self, parameters, context, feedback): feedback, ) + # Clean up voxel processing result tensors + if use_gpu: + del ( + svf_array, + svfbu_array, + svfveg_array, + svfaveg_array, + svf_height_array, + ) + if device.type == "cuda": + torch.cuda.empty_cache() + # Remove rows where svfbu, sfveg and svfaveg is zero if usevegdem == 1: voxelTable = voxelTable[ @@ -721,6 +760,12 @@ def processAlgorithm(self, parameters, context, feedback): svfbuN, ) + # Clean up main SVF tensors after saving + if use_gpu: + del svfbuE, svfbuS, svfbuW, svfbuN + if device.type == "cuda": + torch.cuda.empty_cache() + if os.path.isfile(outputDir + "/" + "svfs.zip"): os.remove(outputDir + "/" + "svfs.zip") @@ -857,6 +902,22 @@ def processAlgorithm(self, parameters, context, feedback): svfNaveg, ) + # Clean up vegetation SVF tensors after saving + if use_gpu: + del ( + svfEveg, + svfSveg, + svfWveg, + svfNveg, + svfaveg, + svfEaveg, + svfSaveg, + svfWaveg, + svfNaveg, + ) + if device.type == "cuda": + torch.cuda.empty_cache() + zippo = zipfile.ZipFile(outputDir + "/" + "svfs.zip", "a") zippo.write(outputDir + "/" + "svfveg.tif", "svfveg.tif") zippo.write(outputDir + "/" + "svfEveg.tif", "svfEveg.tif") @@ -888,6 +949,9 @@ def processAlgorithm(self, parameters, context, feedback): misc.saveraster( gdal_dsm, filename, svftotal.cpu().detach().numpy() ) + del svftotal, svfbu, svfveg + if device.type == "cuda": + torch.cuda.empty_cache() else: misc.saveraster(gdal_dsm, filename, svftotal) @@ -904,6 +968,9 @@ def processAlgorithm(self, parameters, context, feedback): vegshadowmat=vegshmat.cpu().detach().numpy(), vbshmat=vbshvegshmat.cpu().detach().numpy(), ) # , + del shmat, vegshmat, vbshvegshmat + if device.type == "cuda": + torch.cuda.empty_cache() else: np.savez_compressed( outputDir + "/" + "shadowmats.npz", @@ -922,6 +989,9 @@ def processAlgorithm(self, parameters, context, feedback): voxelId=voxelId.cpu().detach().numpy(), voxelTable=voxelTable.cpu().detach().numpy(), ) + del voxelId, voxelTable + if device.type == "cuda": + torch.cuda.empty_cache() else: np.savez_compressed( outputDir + "/" + "wallScheme.npz", @@ -929,6 +999,20 @@ def processAlgorithm(self, parameters, context, feedback): voxelTable=voxelTable, ) + # Final GPU memory cleanup + if use_gpu: + if device.type == "cuda": + torch.cuda.empty_cache() + + # Aggressive GPU memory cleanup + if use_gpu and torch.cuda.is_available(): + feedback.setProgressText("Clearing GPU memory...") + torch.cuda.synchronize() # Ensure all GPU operations are complete + torch.cuda.empty_cache() # Clear unused GPU memory + torch.cuda.reset_peak_memory_stats() # Reset peak memory tracking + torch.cuda.empty_cache() # Clear again to be sure + gc.collect() # Force Python garbage collection + feedback.setProgressText( "Sky View Factor: SVF grid(s) successfully generated" ) diff --git a/preprocessor/wall_heightaspect_algorithm.py b/preprocessor/wall_heightaspect_algorithm.py index 61cad81..e2cb9f6 100644 --- a/preprocessor/wall_heightaspect_algorithm.py +++ b/preprocessor/wall_heightaspect_algorithm.py @@ -30,6 +30,10 @@ __revision__ = "$Format:%H$" +import gc + +import gc + from qgis.PyQt.QtCore import QCoreApplication, QVariant from qgis.core import ( QgsProcessingAlgorithm, @@ -223,6 +227,15 @@ def processAlgorithm(self, parameters, context, feedback): else: feedback.setProgressText("Wall aspect not calculated") + # Aggressive GPU memory cleanup + if use_gpu and torch.cuda.is_available(): + feedback.setProgressText("Clearing GPU memory...") + torch.cuda.synchronize() # Ensure all GPU operations are complete + torch.cuda.empty_cache() # Clear unused GPU memory + torch.cuda.reset_peak_memory_stats() # Reset peak memory tracking + torch.cuda.empty_cache() # Clear again to be sure + gc.collect() # Force Python garbage collection + return { self.OUTPUT_HEIGHT: outputFileHeight, self.OUTPUT_ASPECT: outputFileAspect, diff --git a/processor/solweig_algorithm.py b/processor/solweig_algorithm.py index 059250c..a6cf4c8 100644 --- a/processor/solweig_algorithm.py +++ b/processor/solweig_algorithm.py @@ -34,7 +34,6 @@ from qgis.core import ( QgsProcessing, QgsProcessingAlgorithm, - QgsProcessingParameterString, QgsProcessingParameterBoolean, QgsProcessingParameterNumber, QgsProcessingParameterFolderDestination, @@ -58,6 +57,7 @@ except ImportError: torch = None TORCH_AVAILABLE = False + # import pandas as pd from osgeo import gdal from osgeo.gdalconst import * @@ -67,6 +67,7 @@ from pathlib import Path from ..util.misc import xy2latlon_fromraster import zipfile +import gc from ..util.umep_solweig_export_component import ( read_solweig_config, @@ -631,7 +632,7 @@ def initAlgorithm(self, config): QgsProcessingParameterBoolean( self.USE_GPU, self.tr( - "Use GPU for calculations (if not ticked, CPU will be used)" + "Use GPU for SOLWEIG 2026 calculations (if not ticked, CPU will be used)" ), defaultValue=False, optional=False, @@ -737,15 +738,6 @@ def processAlgorithm(self, parameters, context, feedback): walayer = self.parameterAsRasterLayer( parameters, self.INPUT_ASPECT, context ) - useGroundScheme = self.parameterAsString( - parameters, self.USE_GROUNDSCHEME, context - ) - useOutgoingLW = self.parameterAsString( - parameters, self.USE_OUTGOINGLW, context - ) - groundTempFile = self.parameterAsString( - parameters, self.INPUT_GROUNDSCHEME, context - ) trunkr = self.parameterAsDouble( parameters, self.INPUT_THEIGHT, context ) @@ -866,7 +858,8 @@ def processAlgorithm(self, parameters, context, feedback): outputKdiff = True calculation_mode = "cpu" - if gpu_bool: + + if gpu_bool and (useGroundScheme == "true" or useOutgoingLW == "true"): # Safely identify if we are dealing with the local mock class if ( type(torch).__name__ == "MetaMock" @@ -1244,31 +1237,6 @@ def processAlgorithm(self, parameters, context, feedback): ) outgoingLW = 0 - ### Ground cover scheme - # Surface temperature parameterization - if useGroundScheme: - groundscheme = 1 - feedback.setProgressText( - "The surface temperature parameterization from v2026 is activated" - ) - else: - groundscheme = 0 - feedback.setProgressText( - "The surface temperature scheme described in 2016 is activated" - ) - - # Outgoing longwave calculation - if useOutgoingLW: - feedback.setProgressText( - "The upwelling longwave radiation from v2026 is activated" - ) - outgoingLW = 1 - else: - feedback.setProgressText( - "The ground cover scheme described in 2016 is activated" - ) - outgoingLW = 0 - # % Ts parameterisation maps if landcover == 1.0: if folderWallScheme: @@ -1392,13 +1360,23 @@ def processAlgorithm(self, parameters, context, feedback): # Main function feedback.setProgressText("Executing main model") - if gpu_bool: + if gpu_bool and (useGroundScheme == "true" or useOutgoingLW == "true"): + sr_torch.solweig_run(outputDir + "/configsolweig.ini", feedback) else: sr.solweig_run(outputDir + "/configsolweig.ini", feedback) feedback.setProgressText("SOLWEIG: Model calculation finished.") + # Aggressive GPU memory cleanup + if gpu_bool and torch.cuda.is_available(): + feedback.setProgressText("Clearing GPU memory...") + torch.cuda.synchronize() # Ensure all GPU operations are complete + torch.cuda.empty_cache() # Clear unused GPU memory + torch.cuda.reset_peak_memory_stats() # Reset peak memory tracking + torch.cuda.empty_cache() # Clear again to be sure + gc.collect() # Force Python garbage collection + return {self.OUTPUT_DIR: outputDir} def name(self): diff --git a/symbology-style.db b/symbology-style.db new file mode 100644 index 0000000000000000000000000000000000000000..4222679afcb1f723eec257e0d151d1efc5027d98 GIT binary patch literal 86016 zcmeI%O>5go7{GCUx(^M3t9Q-T?KhbM^KNsBuS2a=2DwdUdpT3kT|0+xKe<+pLm9ORBE1RYN z%A?Y5>91nD_)p<;@qXc|@I(I3{OjD`xsSQ!;1_uzfB*srAbEAuDo)z*dG zbR zJ~-Hlnn*mXy}hj)>+7ojV;Iuy4@I=Nu$T%IS7*j>oipd6)3?3$o8cR~?<8Y&7Ib56 zP4!nrtSHpvdOICyQkxyfK5^Wk<9X*7XTxL^yDFkAhf%y!kx+D9NJWWiGoyIU$P@pg zCPVyI(T&yBXfm`#gvq6v3NWe6Y`@)edxL@9?I)XmS=Nn}71hs)48q|TF)FFXMO|jw zVz`qHKP_p-;-Y#PwHq{0{$Apn~5e>bSZqM#D=tc-FG7yk2dCfT9&dT9h#8&-URl%Rrbz^f=_4h~Xwi=KmY**5I_I{1Q0*~0R#|`Ux4#}`Nx 0).float() sh = 1.0 - sh + if device.type == "cuda": + torch.cuda.empty_cache() return sh, wallsh, wallsun, facesh, facesun @@ -162,6 +164,9 @@ def shadowingfunction_wallheight_13( azimuth, aspect_scheme, walls_scheme, a, f, device ) shade_on_wall = wallsh_.clone() + if device.type == "cuda": + torch.cuda.empty_cache() return (sh, wallsh, wallsun, facesh, facesun, shade_on_wall) - + if device.type == "cuda": + torch.cuda.empty_cache() return (sh, wallsh, wallsun, facesh, facesun) diff --git a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23_torch.py b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23_torch.py index 879dc9d..3184f5c 100644 --- a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23_torch.py +++ b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23_torch.py @@ -6,8 +6,6 @@ except: pass -# import matplotlib.pylab as plt - def shade_on_walls(azimuth, aspect, walls, dsm, f, shvoveg, device): cos_incidence = torch.cos(aspect - azimuth) @@ -36,6 +34,9 @@ def shade_on_walls(azimuth, aspect, walls, dsm, f, shvoveg, device): wallshve[mask_neg] = 0 wallsun[mask_neg] = 0 + if device.type == "cuda": + torch.cuda.empty_cache() + return wallsh, wallsun, wallshve, facesh, facesun @@ -231,6 +232,9 @@ def shadowingfunction_wallheight_23( shade_on_wall < wallshve_ ] + if device.type == "cuda": + torch.cuda.empty_cache() + return ( ( vegsh, diff --git a/util/SEBESOLWEIGCommonFiles/sun_position_torch.py b/util/SEBESOLWEIGCommonFiles/sun_position_torch.py index 18acfca..bbe54c8 100644 --- a/util/SEBESOLWEIGCommonFiles/sun_position_torch.py +++ b/util/SEBESOLWEIGCommonFiles/sun_position_torch.py @@ -267,7 +267,8 @@ def julian_calculation(t_input): julian["century"] = (julian["day"] - 2451545) / 36525 julian["ephemeris_century"] = (julian["ephemeris_day"] - 2451545) / 36525 julian["ephemeris_millenium"] = (julian["ephemeris_century"]) / 10 - + if device.type == "cuda": + torch.cuda.empty_cache() return julian @@ -655,6 +656,9 @@ def earth_heliocentric_position_calculation(julian): + (L4 * torch.pow(JME, 4)) ) / 1e8 + if device.type == "cuda": + torch.cuda.empty_cache() + return earth_heliocentric_position @@ -678,6 +682,7 @@ def sun_geocentric_position_calculation(earth_heliocentric_position): sun_geocentric_position["latitude"] = set_to_range( sun_geocentric_position["latitude"], 0, 360 ) + return sun_geocentric_position @@ -930,6 +935,9 @@ def nutation_calculation(julian): # Nutation in obliquity nutation["obliquity"] = torch.sum(delta_obliquity) / 36000000 + if device.type == "cuda": + torch.cuda.empty_cache() + return nutation @@ -980,6 +988,9 @@ def true_obliquity_calculation(julian, nutation): true_obliquity = (mean_obliquity / 3600) + nutation["obliquity"] + if device.type == "cuda": + torch.cuda.empty_cache() + return true_obliquity diff --git a/util/umep_solweig_export_component.py b/util/umep_solweig_export_component.py index 42390cf..12a6581 100644 --- a/util/umep_solweig_export_component.py +++ b/util/umep_solweig_export_component.py @@ -10,14 +10,13 @@ def read_solweig_config(config_file_path): config = ConfigParser() config.read(config_file_path) - return config.defaults() """ -input: +input: TargetDict: dictonary with all relevant inputs -refdir = outputfolder +refdir = outputfolder """ @@ -107,12 +106,11 @@ def write_solweig_config(configDict, refdir): ) f.write("aniso={}\n".format(configDict["aniso"])) f.write("# use the surface temperature parameterization v2026a\n") - f.write("outgoinglongwave={}\n".format(configDict["outgoingLW"])) - f.write("# use the surface temperature parameterization v2026a\n") f.write("groundmodel={}\n".format(configDict["groundmodel"])) f.write( "# use the outgoing longwave radiation computation scheme v2026a\n" ) + f.write("outgoinglongwave={}\n".format(configDict["outgoingLW"])) f.write( "# use wall surface temperature scheme (Wallenberg et al. 2025, GMD)\n" ) From b7fcf509e7dce6bf21a6916914a3ede46eee00d9 Mon Sep 17 00:00:00 2001 From: "Mael.PHILIPPE" Date: Fri, 12 Jun 2026 09:45:52 +0200 Subject: [PATCH 17/20] feat/added-intel-gpu-support --- .../SOLWEIGpython/Kside_veg_v2022a_torch.py | 5 ++- functions/SOLWEIGpython/Lside_veg_torch.py | 8 +++- functions/SOLWEIGpython/Lvikt_veg_torch.py | 5 ++- .../Solweig_2026a_calc_forprocessing_torch.py | 4 +- functions/SOLWEIGpython/Solweig_run_torch.py | 35 ++++++++++++----- .../SOLWEIGpython/cylindric_wedge_torch.py | 8 +++- functions/SOLWEIGpython/daylen_torch.py | 4 +- .../SOLWEIGpython/ground_surface_torch.py | 2 + functions/svf_for_voxels_torch.py | 24 ++++++++---- functions/svf_functions_torch.py | 15 +++++-- functions/wallalgorithms_torch.py | 2 + preprocessor/skyviewfactor_algorithm.py | 39 ++++++++++++++++--- preprocessor/wall_heightaspect_algorithm.py | 14 ++++++- processor/solweig_algorithm.py | 7 ++++ .../Solweig_v2015_metdata_noload_torch.py | 2 + .../create_patches_torch.py | 2 + .../shadowingfunction_wallheight_13_torch.py | 6 +++ .../shadowingfunction_wallheight_23_torch.py | 4 ++ .../sun_position_torch.py | 18 ++++++--- 19 files changed, 163 insertions(+), 41 deletions(-) diff --git a/functions/SOLWEIGpython/Kside_veg_v2022a_torch.py b/functions/SOLWEIGpython/Kside_veg_v2022a_torch.py index cf6de07..3ae0dd9 100644 --- a/functions/SOLWEIGpython/Kside_veg_v2022a_torch.py +++ b/functions/SOLWEIGpython/Kside_veg_v2022a_torch.py @@ -47,7 +47,7 @@ def Kside_veg_v2022a( else ( azimuth.device if isinstance(azimuth, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "cpu") + else torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") ) ) @@ -570,5 +570,6 @@ def Kside_veg_v2022a( if device.type == "cuda": torch.cuda.empty_cache() - + elif device.type == "xpu": + torch.xpu.empty_cache() return Keast, Ksouth, Kwest, Knorth, KsideI, KsideD, Kside diff --git a/functions/SOLWEIGpython/Lside_veg_torch.py b/functions/SOLWEIGpython/Lside_veg_torch.py index aab0cc9..0635d19 100644 --- a/functions/SOLWEIGpython/Lside_veg_torch.py +++ b/functions/SOLWEIGpython/Lside_veg_torch.py @@ -45,7 +45,7 @@ def Lside_veg_v2022a( else ( Ta.device if isinstance(Ta, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "cpu") + else torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") ) ) @@ -266,6 +266,8 @@ def Lside_veg_v2022a( if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return Least, Lsouth, Lwest, Lnorth @@ -303,7 +305,7 @@ def Lside_veg_v2026( else ( Ta.device if isinstance(Ta, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "cpu") + else torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") ) ) @@ -508,4 +510,6 @@ def Lside_veg_v2026( if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return Least, Lsouth, Lwest, Lnorth diff --git a/functions/SOLWEIGpython/Lvikt_veg_torch.py b/functions/SOLWEIGpython/Lvikt_veg_torch.py index 937f10e..7fd8234 100644 --- a/functions/SOLWEIGpython/Lvikt_veg_torch.py +++ b/functions/SOLWEIGpython/Lvikt_veg_torch.py @@ -13,7 +13,7 @@ def Lvikt_veg(svf, svfveg, svfaveg, vikttot): elif isinstance(svfaveg, torch.Tensor): device = svfaveg.device else: - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + device = torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") svf = torch.as_tensor(svf, device=device) svfveg = torch.as_tensor(svfveg, device=device) @@ -81,5 +81,6 @@ def Lvikt_veg(svf, svfveg, svfaveg, vikttot): if device.type == "cuda": torch.cuda.empty_cache() - + elif device.type == "xpu": + torch.xpu.empty_cache() return viktveg, viktwall, viktsky, viktrefl diff --git a/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing_torch.py b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing_torch.py index 1aba5fb..2012288 100644 --- a/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing_torch.py +++ b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing_torch.py @@ -226,7 +226,7 @@ def Solweig_2026a_calc( else ( Ta.device if isinstance(Ta, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "cpu") + else torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") ) ) @@ -942,6 +942,8 @@ def Solweig_2026a_calc( if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return ( Tmrt, diff --git a/functions/SOLWEIGpython/Solweig_run_torch.py b/functions/SOLWEIGpython/Solweig_run_torch.py index 3761f1c..ad729d6 100644 --- a/functions/SOLWEIGpython/Solweig_run_torch.py +++ b/functions/SOLWEIGpython/Solweig_run_torch.py @@ -93,6 +93,11 @@ def solweig_run(configPath, feedback): feedback.setProgressText( "PyTorch and GPU found. Initiating GPU mode..." ) + elif configDict["calculation_mode"] == "gpu" and torch.xpu.is_available(): + device = torch.device("xpu") + feedback.setProgressText( + "PyTorch and GPU found. Initiating GPU mode..." + ) else: feedback.setProgressText( "Pytorch found but GPU not found. Initiating CPU mode..." @@ -180,7 +185,8 @@ def solweig_run(configPath, feedback): # Clean up intermediate tensors after vegetation loading if device.type == "cuda": torch.cuda.empty_cache() - + elif device.type == "xpu": + torch.xpu.empty_cache() # Land cover landcover = int(configDict["landcover"]) if landcover == 1: @@ -227,7 +233,8 @@ def solweig_run(configPath, feedback): # Clean up DEM intermediate tensors if device.type == "cuda": torch.cuda.empty_cache() - + elif device.type == "xpu": + torch.xpu.empty_cache() # SVF zip = zipfile.ZipFile(configDict["input_svf"], "r") zip.extractall(configDict["working_dir"]) @@ -406,7 +413,8 @@ def solweig_run(configPath, feedback): # Clean up after SVF loading if device.type == "cuda": torch.cuda.empty_cache() - + elif device.type == "xpu": + torch.xpu.empty_cache() tmp = svf + svfveg - 1.0 tmp[tmp < 0.0] = 0.0 # %matlab crazyness around 0 @@ -594,7 +602,8 @@ def solweig_run(configPath, feedback): # Clean up vegetation processing tensors if device.type == "cuda": torch.cuda.empty_cache() - + elif device.type == "xpu": + torch.xpu.empty_cache() # Initialization of maps Knight = torch.zeros((rows, cols)).to(device) Tgmap1 = torch.zeros((rows, cols)).to(device) @@ -858,7 +867,8 @@ def solweig_run(configPath, feedback): # Clean up wall scheme setup tensors if device.type == "cuda": torch.cuda.empty_cache() - + elif device.type == "xpu": + torch.xpu.empty_cache() # Initialisation of time related variables if Ta.__len__() == 1: timestepdec = 0 @@ -1213,7 +1223,8 @@ def solweig_run(configPath, feedback): # Clean up POI tensor after writing if device.type == "cuda": torch.cuda.empty_cache() - + elif device.type == "xpu": + torch.xpu.empty_cache() # If wall temperature parameterization scheme is in use if ( configDict["wallscheme"] == 1 @@ -1314,7 +1325,8 @@ def solweig_run(configPath, feedback): del wall_data, temp_all # Clean up wall data tensors if device.type == "cuda": torch.cuda.empty_cache() - + elif device.type == "xpu": + torch.xpu.empty_cache() # Save wall temperature/radiation as NetCDF TODO: fix for standAlone? if configDict["wallnetcdf"] == "1": # wallNetCDF: netcdf_output = configDict["outputDir"] + "/walls.nc" @@ -1451,7 +1463,7 @@ def solweig_run(configPath, feedback): ) # Clean up iteration tensors after output - if device.type == "cuda": + if device.type == "cuda" or device.type == "xpu": del ( Tmrt, Kdown, @@ -1474,7 +1486,10 @@ def solweig_run(configPath, feedback): dRad, Kside, ) - torch.cuda.empty_cache() + if device.type == "cuda": + torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() # Sky view image of patches if (anisotropic_sky == 1) & (i == 0) & (not poisxy is None): @@ -1591,3 +1606,5 @@ def solweig_run(configPath, feedback): if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() diff --git a/functions/SOLWEIGpython/cylindric_wedge_torch.py b/functions/SOLWEIGpython/cylindric_wedge_torch.py index 9a5d201..dd2a187 100644 --- a/functions/SOLWEIGpython/cylindric_wedge_torch.py +++ b/functions/SOLWEIGpython/cylindric_wedge_torch.py @@ -18,7 +18,7 @@ def cylindric_wedge(zen, svfalfa, rows, cols): device = ( svfalfa.device if isinstance(svfalfa, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "cpu") + else torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") ) beta = zen # alfa=svfalfa @@ -59,6 +59,8 @@ def cylindric_wedge(zen, svfalfa, rows, cols): F_sh = (2 * torch.pi * ba - Ssurf) / (2 * torch.pi * ba) # Xa if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return F_sh @@ -81,7 +83,7 @@ def cylindric_wedge_voxel(zen, svfalfa): device = ( svfalfa.device if isinstance(svfalfa, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "cpu") + else torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") ) qa = torch.zeros((svfalfa.shape[0]), device=device) @@ -111,4 +113,6 @@ def cylindric_wedge_voxel(zen, svfalfa): F_sh = (2 * torch.pi * ba - Ssurf) / (2 * torch.pi * ba) # Xa if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return F_sh diff --git a/functions/SOLWEIGpython/daylen_torch.py b/functions/SOLWEIGpython/daylen_torch.py index 24dbbde..51a2786 100644 --- a/functions/SOLWEIGpython/daylen_torch.py +++ b/functions/SOLWEIGpython/daylen_torch.py @@ -13,7 +13,7 @@ def daylen(DOY, XLAT): device = ( DOY.device if isinstance(DOY, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "cpu") + else torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") ) if not isinstance(XLAT, torch.Tensor): XLAT = torch.tensor(XLAT, device=device) @@ -31,4 +31,6 @@ def daylen(DOY, XLAT): SNDN = 12.0 + DAYL / 2.0 if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return DAYL, DEC, SNDN, SNUP diff --git a/functions/SOLWEIGpython/ground_surface_torch.py b/functions/SOLWEIGpython/ground_surface_torch.py index 87d6f9d..3ef5522 100644 --- a/functions/SOLWEIGpython/ground_surface_torch.py +++ b/functions/SOLWEIGpython/ground_surface_torch.py @@ -220,6 +220,8 @@ def initiate_groundScheme( if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return ( Tg, Tm, diff --git a/functions/svf_for_voxels_torch.py b/functions/svf_for_voxels_torch.py index 06c1d6c..b15383f 100644 --- a/functions/svf_for_voxels_torch.py +++ b/functions/svf_for_voxels_torch.py @@ -118,7 +118,11 @@ def wallscheme_prepare( wall_dict[0] = 0.0 # Convert specific lists to match original return signature formats if downstream requires it - torch.cuda.empty_cache() + + if device.type == "cuda": + torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return ( voxelTable, voxelId_list, @@ -273,8 +277,10 @@ def svf_for_voxels( svfaveg_array, ] ) - torch.cuda.empty_cache() - + if device.type == "cuda": + torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return voxelTable @@ -552,8 +558,10 @@ def svf_kmeans( svfaveg_array, ] ) - torch.cuda.empty_cache() - + if device.type == "cuda": + torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return voxelTable, cluster_heights @@ -587,6 +595,8 @@ def interpolate_svf(voxelTable): continue voxelTable_np[wall_mask, -4] = new_svf - torch.cuda.empty_cache() - + if device.type == "cuda": + torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return torch.from_numpy(voxelTable_np).to(device) diff --git a/functions/svf_functions_torch.py b/functions/svf_functions_torch.py index aee0521..b6fc42e 100644 --- a/functions/svf_functions_torch.py +++ b/functions/svf_functions_torch.py @@ -42,8 +42,10 @@ def annulus_weight(altitude, aziinterval, device): ) weight = steprad * w del n, w - torch.cuda.empty_cache() - + if device.type == "cuda": + torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return weight @@ -124,7 +126,10 @@ def svf_angles_100121(device): ) angleresult = {"iazimuth": iazimuth, "aziinterval": aziinterval} del iazimuth, aziinterval - torch.cuda.empty_cache() + if device.type == "cuda": + torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return angleresult @@ -503,6 +508,8 @@ def svfForProcessing153( del shmat, vegshmat, vbshvegshmat, all_voxelId if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return svfresult @@ -711,4 +718,6 @@ def svfForProcessing655( ) if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return svfresult diff --git a/functions/wallalgorithms_torch.py b/functions/wallalgorithms_torch.py index 04947dd..c0e3742 100644 --- a/functions/wallalgorithms_torch.py +++ b/functions/wallalgorithms_torch.py @@ -347,6 +347,8 @@ def filter1Goodwin_as_aspect_v3( ) if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return final_y diff --git a/preprocessor/skyviewfactor_algorithm.py b/preprocessor/skyviewfactor_algorithm.py index 7ae308c..15e55a8 100644 --- a/preprocessor/skyviewfactor_algorithm.py +++ b/preprocessor/skyviewfactor_algorithm.py @@ -314,6 +314,11 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText( "PyTorch and GPU found. Initiating GPU mode..." ) + elif torch.xpu.is_available(): + device = torch.device("xpu") + feedback.setProgressText( + "PyTorch and GPU found. Initiating GPU mode..." + ) else: device = torch.device("cpu") feedback.setProgressText( @@ -467,6 +472,8 @@ def processAlgorithm(self, parameters, context, feedback): if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() else: print("cpu version") ret = svf.svfForProcessing655( @@ -545,7 +552,9 @@ def processAlgorithm(self, parameters, context, feedback): del svftotal, svfbu, svfveg, svfaveg, voxel_y if device.type == "cuda": torch.cuda.empty_cache() - + elif device.type == "xpu": + torch.xpu.empty_cache() + if kmeans: if use_gpu: @@ -612,6 +621,8 @@ def processAlgorithm(self, parameters, context, feedback): ) if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() # Loop for exact SVF at heights (increase DEM) # if demlayer: @@ -673,7 +684,8 @@ def processAlgorithm(self, parameters, context, feedback): ) if device.type == "cuda": torch.cuda.empty_cache() - + elif device.type == "xpu": + torch.xpu.empty_cache() # Remove rows where svfbu, sfveg and svfaveg is zero if usevegdem == 1: voxelTable = voxelTable[ @@ -765,7 +777,8 @@ def processAlgorithm(self, parameters, context, feedback): del svfbuE, svfbuS, svfbuW, svfbuN if device.type == "cuda": torch.cuda.empty_cache() - + elif device.type == "xpu": + torch.xpu.empty_cache() if os.path.isfile(outputDir + "/" + "svfs.zip"): os.remove(outputDir + "/" + "svfs.zip") @@ -917,7 +930,8 @@ def processAlgorithm(self, parameters, context, feedback): ) if device.type == "cuda": torch.cuda.empty_cache() - + elif device.type == "xpu": + torch.xpu.empty_cache() zippo = zipfile.ZipFile(outputDir + "/" + "svfs.zip", "a") zippo.write(outputDir + "/" + "svfveg.tif", "svfveg.tif") zippo.write(outputDir + "/" + "svfEveg.tif", "svfEveg.tif") @@ -952,6 +966,8 @@ def processAlgorithm(self, parameters, context, feedback): del svftotal, svfbu, svfveg if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() else: misc.saveraster(gdal_dsm, filename, svftotal) @@ -971,6 +987,8 @@ def processAlgorithm(self, parameters, context, feedback): del shmat, vegshmat, vbshvegshmat if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() else: np.savez_compressed( outputDir + "/" + "shadowmats.npz", @@ -992,6 +1010,8 @@ def processAlgorithm(self, parameters, context, feedback): del voxelId, voxelTable if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() else: np.savez_compressed( outputDir + "/" + "wallScheme.npz", @@ -1003,6 +1023,8 @@ def processAlgorithm(self, parameters, context, feedback): if use_gpu: if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() # Aggressive GPU memory cleanup if use_gpu and torch.cuda.is_available(): @@ -1012,7 +1034,14 @@ def processAlgorithm(self, parameters, context, feedback): torch.cuda.reset_peak_memory_stats() # Reset peak memory tracking torch.cuda.empty_cache() # Clear again to be sure gc.collect() # Force Python garbage collection - + elif use_gpu and torch.xpu.is_available(): + feedback.setProgressText("Clearing GPU memory...") + torch.xpu.synchronize() # Ensure all GPU operations are complete + torch.xpu.empty_cache() # Clear unused GPU memory + torch.xpu.reset_peak_memory_stats() # Reset peak memory tracking + torch.xpu.empty_cache() # Clear again to be sure + gc.collect() # Force Python garbage collection + feedback.setProgressText( "Sky View Factor: SVF grid(s) successfully generated" ) diff --git a/preprocessor/wall_heightaspect_algorithm.py b/preprocessor/wall_heightaspect_algorithm.py index e2cb9f6..a43d795 100644 --- a/preprocessor/wall_heightaspect_algorithm.py +++ b/preprocessor/wall_heightaspect_algorithm.py @@ -159,6 +159,11 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText( "PyTorch and GPU found. Initiating GPU mode..." ) + elif torch.xpu.is_available(): + device = torch.device("xpu") + feedback.setProgressText( + "PyTorch and GPU found. Initiating GPU mode..." + ) else: device = torch.device("cpu") feedback.setProgressText( @@ -235,7 +240,14 @@ def processAlgorithm(self, parameters, context, feedback): torch.cuda.reset_peak_memory_stats() # Reset peak memory tracking torch.cuda.empty_cache() # Clear again to be sure gc.collect() # Force Python garbage collection - + elif use_gpu and torch.xpu.is_available(): + feedback.setProgressText("Clearing GPU memory...") + torch.xpu.synchronize() # Ensure all GPU operations are complete + torch.xpu.empty_cache() # Clear unused GPU memory + torch.xpu.reset_peak_memory_stats() # Reset peak memory tracking + torch.xpu.empty_cache() # Clear again to be sure + gc.collect() # Force Python garbage collection + return { self.OUTPUT_HEIGHT: outputFileHeight, self.OUTPUT_ASPECT: outputFileAspect, diff --git a/processor/solweig_algorithm.py b/processor/solweig_algorithm.py index a6cf4c8..e79b502 100644 --- a/processor/solweig_algorithm.py +++ b/processor/solweig_algorithm.py @@ -1376,6 +1376,13 @@ def processAlgorithm(self, parameters, context, feedback): torch.cuda.reset_peak_memory_stats() # Reset peak memory tracking torch.cuda.empty_cache() # Clear again to be sure gc.collect() # Force Python garbage collection + elif gpu_bool and torch.xpu.is_available(): + feedback.setProgressText("Clearing GPU memory...") + torch.xpu.synchronize() # Ensure all GPU operations are complete + torch.xpu.empty_cache() # Clear unused GPU memory + torch.xpu.reset_peak_memory_stats() # Reset peak memory tracking + torch.xpu.empty_cache() # Clear again to be sure + gc.collect() # Force Python garbage collection return {self.OUTPUT_DIR: outputDir} diff --git a/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload_torch.py b/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload_torch.py index f6a4ea3..0d563a0 100644 --- a/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload_torch.py +++ b/util/SEBESOLWEIGCommonFiles/Solweig_v2015_metdata_noload_torch.py @@ -111,5 +111,7 @@ def Solweig_2015a_metdata_noload(inputdata, location, UTC): if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return YYYY, altitude, azimuth, zen, jday, leafon, dectime, altmax diff --git a/util/SEBESOLWEIGCommonFiles/create_patches_torch.py b/util/SEBESOLWEIGCommonFiles/create_patches_torch.py index da2dc13..fbf9cb9 100644 --- a/util/SEBESOLWEIGCommonFiles/create_patches_torch.py +++ b/util/SEBESOLWEIGCommonFiles/create_patches_torch.py @@ -106,6 +106,8 @@ def create_patches(patch_option, device): del deg2rad if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return ( skyvaultalt, diff --git a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13_torch.py b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13_torch.py index 9926e92..004fcdf 100644 --- a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13_torch.py +++ b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_13_torch.py @@ -58,6 +58,8 @@ def shade_on_walls(azimuth, aspect, walls, dsm, f, device): if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return sh, wallsh, wallsun, facesh, facesun @@ -166,7 +168,11 @@ def shadowingfunction_wallheight_13( shade_on_wall = wallsh_.clone() if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return (sh, wallsh, wallsun, facesh, facesun, shade_on_wall) if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return (sh, wallsh, wallsun, facesh, facesun) diff --git a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23_torch.py b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23_torch.py index 3184f5c..672fd1e 100644 --- a/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23_torch.py +++ b/util/SEBESOLWEIGCommonFiles/shadowingfunction_wallheight_23_torch.py @@ -36,6 +36,8 @@ def shade_on_walls(azimuth, aspect, walls, dsm, f, shvoveg, device): if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return wallsh, wallsun, wallshve, facesh, facesun @@ -234,6 +236,8 @@ def shadowingfunction_wallheight_23( if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return ( ( diff --git a/util/SEBESOLWEIGCommonFiles/sun_position_torch.py b/util/SEBESOLWEIGCommonFiles/sun_position_torch.py index bbe54c8..06aeb51 100644 --- a/util/SEBESOLWEIGCommonFiles/sun_position_torch.py +++ b/util/SEBESOLWEIGCommonFiles/sun_position_torch.py @@ -183,7 +183,7 @@ def sun_position(time, location): def julian_calculation(t_input): - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + device = torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") """ % This function compute the julian day and julian century from the local % time and timezone information. Ephemeris are calculated with a delta_t=0 @@ -269,11 +269,13 @@ def julian_calculation(t_input): julian["ephemeris_millenium"] = (julian["ephemeris_century"]) / 10 if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return julian def earth_heliocentric_position_calculation(julian): - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + device = torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") """ % This function compute the earth position relative to the sun, using % tabulated values. @@ -658,7 +660,8 @@ def earth_heliocentric_position_calculation(julian): if device.type == "cuda": torch.cuda.empty_cache() - + elif device.type == "xpu": + torch.xpu.empty_cache() return earth_heliocentric_position @@ -687,7 +690,7 @@ def sun_geocentric_position_calculation(earth_heliocentric_position): def nutation_calculation(julian): - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + device = torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") """ % This function compute the nutation in longtitude and in obliquity, in % degrees. @@ -937,12 +940,14 @@ def nutation_calculation(julian): if device.type == "cuda": torch.cuda.empty_cache() + elif device.type == "xpu": + torch.xpu.empty_cache() return nutation def true_obliquity_calculation(julian, nutation): - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + device = torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") """ This function compute the true obliquity of the ecliptic. @@ -990,7 +995,8 @@ def true_obliquity_calculation(julian, nutation): if device.type == "cuda": torch.cuda.empty_cache() - + elif device.type == "xpu": + torch.xpu.empty_cache() return true_obliquity From 9d380597dc7ddf455539913904c35d5899e2fcda Mon Sep 17 00:00:00 2001 From: "Mael.PHILIPPE" Date: Fri, 12 Jun 2026 10:27:25 +0200 Subject: [PATCH 18/20] corrected bug in intel gpu management --- __init__.py | 3 +- .../SOLWEIGpython/Kside_veg_v2022a_torch.py | 11 ++++- functions/SOLWEIGpython/Lside_veg_torch.py | 21 +++++++++- functions/SOLWEIGpython/Lvikt_veg_torch.py | 11 ++++- .../Solweig_2026a_calc_forprocessing_torch.py | 11 ++++- functions/SOLWEIGpython/Solweig_run_torch.py | 15 ++++--- .../SOLWEIGpython/cylindric_wedge_torch.py | 20 ++++++++- functions/SOLWEIGpython/daylen_torch.py | 11 ++++- functions/svf_for_voxels_torch.py | 2 +- preprocessor/skyviewfactor_algorithm.py | 16 ++++---- preprocessor/wall_heightaspect_algorithm.py | 16 ++++---- processor/solweig_algorithm.py | 10 ++--- .../sun_position_torch.py | 41 +++++++++++++++++-- 13 files changed, 148 insertions(+), 40 deletions(-) diff --git a/__init__.py b/__init__.py index 091fc17..dfbc93c 100644 --- a/__init__.py +++ b/__init__.py @@ -37,6 +37,8 @@ # Create the local mock right here class MetaMock(type): def __getattr__(cls, name): + if name == "is_available": + return lambda: False return cls def __call__(cls, *args, **kwargs): @@ -47,7 +49,6 @@ class LocalMockTorch(metaclass=MetaMock): # Inject it into Python's module registry BEFORE QGIS loads any sub-files sys.modules["torch"] = LocalMockTorch - __author__ = "Fredrik Lindberg" __date__ = "2020-04-02" __copyright__ = "(C) 2020 by Fredrik Lindberg" diff --git a/functions/SOLWEIGpython/Kside_veg_v2022a_torch.py b/functions/SOLWEIGpython/Kside_veg_v2022a_torch.py index 3ae0dd9..0d7ffe4 100644 --- a/functions/SOLWEIGpython/Kside_veg_v2022a_torch.py +++ b/functions/SOLWEIGpython/Kside_veg_v2022a_torch.py @@ -3,6 +3,7 @@ try: import torch + except: pass @@ -47,7 +48,15 @@ def Kside_veg_v2022a( else ( azimuth.device if isinstance(azimuth, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") + else torch.device( + "cuda" + if torch.cuda.is_available() + else ( + "xpu" + if (hasattr(torch, "xpu") and torch.xpu.is_available()) + else "cpu" + ) + ) ) ) diff --git a/functions/SOLWEIGpython/Lside_veg_torch.py b/functions/SOLWEIGpython/Lside_veg_torch.py index 0635d19..d32834e 100644 --- a/functions/SOLWEIGpython/Lside_veg_torch.py +++ b/functions/SOLWEIGpython/Lside_veg_torch.py @@ -3,6 +3,7 @@ try: import torch + except: pass @@ -45,7 +46,15 @@ def Lside_veg_v2022a( else ( Ta.device if isinstance(Ta, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") + else torch.device( + "cuda" + if torch.cuda.is_available() + else ( + "xpu" + if (hasattr(torch, "xpu") and torch.xpu.is_available()) + else "cpu" + ) + ) ) ) @@ -305,7 +314,15 @@ def Lside_veg_v2026( else ( Ta.device if isinstance(Ta, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") + else torch.device( + "cuda" + if torch.cuda.is_available() + else ( + "xpu" + if (hasattr(torch, "xpu") and torch.xpu.is_available()) + else "cpu" + ) + ) ) ) diff --git a/functions/SOLWEIGpython/Lvikt_veg_torch.py b/functions/SOLWEIGpython/Lvikt_veg_torch.py index 7fd8234..c7fb1d8 100644 --- a/functions/SOLWEIGpython/Lvikt_veg_torch.py +++ b/functions/SOLWEIGpython/Lvikt_veg_torch.py @@ -1,5 +1,6 @@ try: import torch + except: pass @@ -13,7 +14,15 @@ def Lvikt_veg(svf, svfveg, svfaveg, vikttot): elif isinstance(svfaveg, torch.Tensor): device = svfaveg.device else: - device = torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") + torch.device( + "cuda" + if torch.cuda.is_available() + else ( + "xpu" + if (hasattr(torch, "xpu") and torch.xpu.is_available()) + else "cpu" + ) + ) svf = torch.as_tensor(svf, device=device) svfveg = torch.as_tensor(svfveg, device=device) diff --git a/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing_torch.py b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing_torch.py index 2012288..a7842f2 100644 --- a/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing_torch.py +++ b/functions/SOLWEIGpython/Solweig_2026a_calc_forprocessing_torch.py @@ -34,6 +34,7 @@ try: import torch + except: pass @@ -226,7 +227,15 @@ def Solweig_2026a_calc( else ( Ta.device if isinstance(Ta, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") + else torch.device( + "cuda" + if torch.cuda.is_available() + else ( + "xpu" + if (hasattr(torch, "xpu") and torch.xpu.is_available()) + else "cpu" + ) + ) ) ) diff --git a/functions/SOLWEIGpython/Solweig_run_torch.py b/functions/SOLWEIGpython/Solweig_run_torch.py index ad729d6..627d2af 100644 --- a/functions/SOLWEIGpython/Solweig_run_torch.py +++ b/functions/SOLWEIGpython/Solweig_run_torch.py @@ -87,20 +87,25 @@ def solweig_run(configPath, feedback): # --- Load on CPU or GPU config device = torch.device("cpu") - if configDict["calculation_mode"] == "gpu" and torch.cuda.is_available(): device = torch.device("cuda") feedback.setProgressText( - "PyTorch and GPU found. Initiating GPU mode..." + "PyTorch and NVIDIA/AMD GPU found. Initiating CUDA mode..." ) - elif configDict["calculation_mode"] == "gpu" and torch.xpu.is_available(): + + elif ( + configDict["calculation_mode"] == "gpu" + and hasattr(torch, "xpu") + and torch.xpu.is_available() + ): device = torch.device("xpu") feedback.setProgressText( - "PyTorch and GPU found. Initiating GPU mode..." + "PyTorch and Intel GPU found. Initiating XPU mode..." ) + else: feedback.setProgressText( - "Pytorch found but GPU not found. Initiating CPU mode..." + "PyTorch found but compatible GPU not found. Initiating CPU mode..." ) standAlone = int(configDict["standalone"]) diff --git a/functions/SOLWEIGpython/cylindric_wedge_torch.py b/functions/SOLWEIGpython/cylindric_wedge_torch.py index dd2a187..3e3d799 100644 --- a/functions/SOLWEIGpython/cylindric_wedge_torch.py +++ b/functions/SOLWEIGpython/cylindric_wedge_torch.py @@ -18,7 +18,15 @@ def cylindric_wedge(zen, svfalfa, rows, cols): device = ( svfalfa.device if isinstance(svfalfa, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") + else torch.device( + "cuda" + if torch.cuda.is_available() + else ( + "xpu" + if (hasattr(torch, "xpu") and torch.xpu.is_available()) + else "cpu" + ) + ) ) beta = zen # alfa=svfalfa @@ -83,7 +91,15 @@ def cylindric_wedge_voxel(zen, svfalfa): device = ( svfalfa.device if isinstance(svfalfa, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") + else torch.device( + "cuda" + if torch.cuda.is_available() + else ( + "xpu" + if (hasattr(torch, "xpu") and torch.xpu.is_available()) + else "cpu" + ) + ) ) qa = torch.zeros((svfalfa.shape[0]), device=device) diff --git a/functions/SOLWEIGpython/daylen_torch.py b/functions/SOLWEIGpython/daylen_torch.py index 51a2786..3a86f0d 100644 --- a/functions/SOLWEIGpython/daylen_torch.py +++ b/functions/SOLWEIGpython/daylen_torch.py @@ -1,5 +1,6 @@ try: import torch + except: pass @@ -13,7 +14,15 @@ def daylen(DOY, XLAT): device = ( DOY.device if isinstance(DOY, torch.Tensor) - else torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") + else torch.device( + "cuda" + if torch.cuda.is_available() + else ( + "xpu" + if (hasattr(torch, "xpu") and torch.xpu.is_available()) + else "cpu" + ) + ) ) if not isinstance(XLAT, torch.Tensor): XLAT = torch.tensor(XLAT, device=device) diff --git a/functions/svf_for_voxels_torch.py b/functions/svf_for_voxels_torch.py index b15383f..236d690 100644 --- a/functions/svf_for_voxels_torch.py +++ b/functions/svf_for_voxels_torch.py @@ -118,7 +118,7 @@ def wallscheme_prepare( wall_dict[0] = 0.0 # Convert specific lists to match original return signature formats if downstream requires it - + if device.type == "cuda": torch.cuda.empty_cache() elif device.type == "xpu": diff --git a/preprocessor/skyviewfactor_algorithm.py b/preprocessor/skyviewfactor_algorithm.py index 15e55a8..072ea39 100644 --- a/preprocessor/skyviewfactor_algorithm.py +++ b/preprocessor/skyviewfactor_algorithm.py @@ -54,11 +54,9 @@ try: import torch - - TORCH_AVAILABLE = True except ImportError: torch = None - TORCH_AVAILABLE = False + ipex = None from pathlib import Path import zipfile @@ -306,7 +304,9 @@ def processAlgorithm(self, parameters, context, feedback): ): raise QgsProcessingException( "\n[UMEP Error] PyTorch is required to run GPU mode.\n" - "Please install it using: pip install torch or with osgeo4w" + "Please install it using: pip install torch or with osgeo4w.\n" + "Note: setup for intel GPU require a more complex setup :\n" + "pip install torch --index-url https://download.pytorch.org/whl/xpu" ) if torch.cuda.is_available(): @@ -314,11 +314,11 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText( "PyTorch and GPU found. Initiating GPU mode..." ) - elif torch.xpu.is_available(): + elif hasattr(torch, "xpu") and torch.xpu.is_available(): device = torch.device("xpu") feedback.setProgressText( "PyTorch and GPU found. Initiating GPU mode..." - ) + ) else: device = torch.device("cpu") feedback.setProgressText( @@ -554,7 +554,7 @@ def processAlgorithm(self, parameters, context, feedback): torch.cuda.empty_cache() elif device.type == "xpu": torch.xpu.empty_cache() - + if kmeans: if use_gpu: @@ -1041,7 +1041,7 @@ def processAlgorithm(self, parameters, context, feedback): torch.xpu.reset_peak_memory_stats() # Reset peak memory tracking torch.xpu.empty_cache() # Clear again to be sure gc.collect() # Force Python garbage collection - + feedback.setProgressText( "Sky View Factor: SVF grid(s) successfully generated" ) diff --git a/preprocessor/wall_heightaspect_algorithm.py b/preprocessor/wall_heightaspect_algorithm.py index a43d795..66fb646 100644 --- a/preprocessor/wall_heightaspect_algorithm.py +++ b/preprocessor/wall_heightaspect_algorithm.py @@ -49,11 +49,9 @@ try: import torch - - TORCH_AVAILABLE = True except ImportError: torch = None - TORCH_AVAILABLE = False + ipex = None import os from ..functions import wallalgorithms_torch as wa_torch @@ -151,7 +149,9 @@ def processAlgorithm(self, parameters, context, feedback): ): raise QgsProcessingException( "\n[UMEP Error] PyTorch is required to run GPU mode.\n" - "Please install it using: pip install torch or with osgeo4w" + "Please install it using: pip install torch or with osgeo4w.\n" + "Note: setup for intel GPU require a more complex setup :\n" + "pip install torch --index-url https://download.pytorch.org/whl/xpu" ) if torch.cuda.is_available(): @@ -159,11 +159,11 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgressText( "PyTorch and GPU found. Initiating GPU mode..." ) - elif torch.xpu.is_available(): + elif hasattr(torch, "xpu") and torch.xpu.is_available(): device = torch.device("xpu") feedback.setProgressText( - "PyTorch and GPU found. Initiating GPU mode..." - ) + "PyTorch and GPU found. Initiating intel GPU mode..." + ) else: device = torch.device("cpu") feedback.setProgressText( @@ -247,7 +247,7 @@ def processAlgorithm(self, parameters, context, feedback): torch.xpu.reset_peak_memory_stats() # Reset peak memory tracking torch.xpu.empty_cache() # Clear again to be sure gc.collect() # Force Python garbage collection - + return { self.OUTPUT_HEIGHT: outputFileHeight, self.OUTPUT_ASPECT: outputFileAspect, diff --git a/processor/solweig_algorithm.py b/processor/solweig_algorithm.py index e79b502..8f2ee55 100644 --- a/processor/solweig_algorithm.py +++ b/processor/solweig_algorithm.py @@ -52,11 +52,9 @@ try: import torch - - TORCH_AVAILABLE = True except ImportError: torch = None - TORCH_AVAILABLE = False + ipex = None # import pandas as pd from osgeo import gdal @@ -867,7 +865,9 @@ def processAlgorithm(self, parameters, context, feedback): ): raise QgsProcessingException( "\n[UMEP Error] PyTorch is required to run GPU mode.\n" - "Please install it using: pip install torch or with osgeo4w" + "Please install it using: pip install torch or with osgeo4w.\n" + "Note: setup for intel GPU require a more complex setup :\n" + "pip install torch --index-url https://download.pytorch.org/whl/xpu" ) # If PyTorch is available, execute the GPU path @@ -1382,7 +1382,7 @@ def processAlgorithm(self, parameters, context, feedback): torch.xpu.empty_cache() # Clear unused GPU memory torch.xpu.reset_peak_memory_stats() # Reset peak memory tracking torch.xpu.empty_cache() # Clear again to be sure - gc.collect() # Force Python garbage collection + gc.collect() # Force Python garbage collection return {self.OUTPUT_DIR: outputDir} diff --git a/util/SEBESOLWEIGCommonFiles/sun_position_torch.py b/util/SEBESOLWEIGCommonFiles/sun_position_torch.py index 06aeb51..d202cf9 100644 --- a/util/SEBESOLWEIGCommonFiles/sun_position_torch.py +++ b/util/SEBESOLWEIGCommonFiles/sun_position_torch.py @@ -4,6 +4,7 @@ try: import torch + except: pass @@ -183,7 +184,15 @@ def sun_position(time, location): def julian_calculation(t_input): - device = torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") + device = torch.device( + "cuda" + if torch.cuda.is_available() + else ( + "xpu" + if (hasattr(torch, "xpu") and torch.xpu.is_available()) + else "cpu" + ) + ) """ % This function compute the julian day and julian century from the local % time and timezone information. Ephemeris are calculated with a delta_t=0 @@ -275,7 +284,15 @@ def julian_calculation(t_input): def earth_heliocentric_position_calculation(julian): - device = torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") + device = torch.device( + "cuda" + if torch.cuda.is_available() + else ( + "xpu" + if (hasattr(torch, "xpu") and torch.xpu.is_available()) + else "cpu" + ) + ) """ % This function compute the earth position relative to the sun, using % tabulated values. @@ -690,7 +707,15 @@ def sun_geocentric_position_calculation(earth_heliocentric_position): def nutation_calculation(julian): - device = torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") + device = torch.device( + "cuda" + if torch.cuda.is_available() + else ( + "xpu" + if (hasattr(torch, "xpu") and torch.xpu.is_available()) + else "cpu" + ) + ) """ % This function compute the nutation in longtitude and in obliquity, in % degrees. @@ -947,7 +972,15 @@ def nutation_calculation(julian): def true_obliquity_calculation(julian, nutation): - device = torch.device("cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu") + device = torch.device( + "cuda" + if torch.cuda.is_available() + else ( + "xpu" + if (hasattr(torch, "xpu") and torch.xpu.is_available()) + else "cpu" + ) + ) """ This function compute the true obliquity of the ecliptic. From 173e5d64d66b66585599cb2e4c6150f285c52ad5 Mon Sep 17 00:00:00 2001 From: "Mael.PHILIPPE" Date: Tue, 16 Jun 2026 10:41:04 +0200 Subject: [PATCH 19/20] updated doc of skyview factor and SOLWEIG Updated the small documentation displayed on the left pf the popups of sky view factor and SOLWEIG to document GPU support --- preprocessor/skyviewfactor_algorithm.py | 7 +++++++ processor/solweig_algorithm.py | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/preprocessor/skyviewfactor_algorithm.py b/preprocessor/skyviewfactor_algorithm.py index 072ea39..27454dc 100644 --- a/preprocessor/skyviewfactor_algorithm.py +++ b/preprocessor/skyviewfactor_algorithm.py @@ -1068,6 +1068,13 @@ def shortHelpString(self): "radiation emitted (or received) by the entire hemispheric environment (Watson and Johnson 1987). " "It is a dimensionless measure between zero and one, representing totally obstructed and free spaces, " "respectively. The methodology that is used to generate SVF here is described in Lindberg and Grimmond (2010).\n" + "\n" + "Sky View Factor supports GPU acceleration via PyTorch for faster calculations. " + "Performance gains are most significant for high-resolution input data or large geographic areas (over a few km²). " + "It requires a compatible NVIDIA or Intel graphics or AMD (with ROCM) card with up-to-date drivers. " + "If out-of-memory errors occur on large datasets, disable acceleration or tile your input data. " + "The system is fully backward compatible and automatically falls back to CPU if hardware or PyTorch is missing.\n" + "\n" "-------------\n" "Lindberg F, Grimmond CSB (2010) Continuous sky view factor maps from high resolution urban digital elevation models. Clim Res 42:177–183\n" "Watson ID, Johnson GT (1987) Graphical estimation of skyview-factors in urban environments. J Climatol 7: 193–197" diff --git a/processor/solweig_algorithm.py b/processor/solweig_algorithm.py index 8f2ee55..1078341 100644 --- a/processor/solweig_algorithm.py +++ b/processor/solweig_algorithm.py @@ -1432,6 +1432,12 @@ def shortHelpString(self): "\n" "Tools to generate sky view factors, wall height and aspect etc. is available in the pre-processing past in UMEP\n" "\n" + "SOLWEIG supports GPU acceleration via PyTorch for faster solar radiation calculations. " + "Performance gains are most significant for high-resolution input data or large geographic areas (over a few km²). " + "It requires a compatible NVIDIA or Intel graphics or AMD (with ROCM) card with up-to-date drivers. " + "If out-of-memory errors occur on large datasets, disable acceleration or tile your input data. " + "The system is fully backward compatible and automatically falls back to CPU if hardware or PyTorch is missing.\n" + "\n" "------------\n" "\n" "Full manual available via the Help-button." From 1f7dbae8fa86b1da803fdc0e7e75f41f843bd3a4 Mon Sep 17 00:00:00 2001 From: "Mael.PHILIPPE" Date: Tue, 16 Jun 2026 11:35:39 +0200 Subject: [PATCH 20/20] fixed feedback bugs --- functions/SOLWEIGpython/Solweig_run_torch.py | 22 ++++++++++------- preprocessor/skyviewfactor_algorithm.py | 24 ++++++++++-------- preprocessor/wall_heightaspect_algorithm.py | 26 ++++++++++++-------- 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/functions/SOLWEIGpython/Solweig_run_torch.py b/functions/SOLWEIGpython/Solweig_run_torch.py index 627d2af..0fa986f 100644 --- a/functions/SOLWEIGpython/Solweig_run_torch.py +++ b/functions/SOLWEIGpython/Solweig_run_torch.py @@ -89,9 +89,10 @@ def solweig_run(configPath, feedback): device = torch.device("cpu") if configDict["calculation_mode"] == "gpu" and torch.cuda.is_available(): device = torch.device("cuda") - feedback.setProgressText( - "PyTorch and NVIDIA/AMD GPU found. Initiating CUDA mode..." - ) + if feedback is not None: + feedback.setProgressText( + "PyTorch and NVIDIA/AMD GPU found. Initiating CUDA mode..." + ) elif ( configDict["calculation_mode"] == "gpu" @@ -99,14 +100,17 @@ def solweig_run(configPath, feedback): and torch.xpu.is_available() ): device = torch.device("xpu") - feedback.setProgressText( - "PyTorch and Intel GPU found. Initiating XPU mode..." - ) + if feedback is not None: + + feedback.setProgressText( + "PyTorch and Intel GPU found. Initiating XPU mode..." + ) else: - feedback.setProgressText( - "PyTorch found but compatible GPU not found. Initiating CPU mode..." - ) + if feedback is not None: + feedback.setProgressText( + "PyTorch found but compatible GPU not found. Initiating CPU mode..." + ) standAlone = int(configDict["standalone"]) # Load DSM diff --git a/preprocessor/skyviewfactor_algorithm.py b/preprocessor/skyviewfactor_algorithm.py index 27454dc..e03a54d 100644 --- a/preprocessor/skyviewfactor_algorithm.py +++ b/preprocessor/skyviewfactor_algorithm.py @@ -311,22 +311,26 @@ def processAlgorithm(self, parameters, context, feedback): if torch.cuda.is_available(): device = torch.device("cuda") - feedback.setProgressText( - "PyTorch and GPU found. Initiating GPU mode..." - ) + if feedback is not None: + feedback.setProgressText( + "PyTorch and GPU found. Initiating GPU mode..." + ) elif hasattr(torch, "xpu") and torch.xpu.is_available(): device = torch.device("xpu") - feedback.setProgressText( - "PyTorch and GPU found. Initiating GPU mode..." - ) + if feedback is not None: + feedback.setProgressText( + "PyTorch and GPU found. Initiating GPU mode..." + ) else: device = torch.device("cpu") - feedback.setProgressText( - "Pytorch found but GPU not found. Initiating CPU mode..." - ) + if feedback is not None: + feedback.setProgressText( + "Pytorch found but GPU not found. Initiating CPU mode..." + ) else: # Fall back to standard CPU processing - feedback.setProgressText("Running in CPU mode...") + if feedback is not None: + feedback.setProgressText("Running in CPU mode...") provider = dsmlayer.dataProvider() filepath_dsm = str(provider.dataSourceUri()) diff --git a/preprocessor/wall_heightaspect_algorithm.py b/preprocessor/wall_heightaspect_algorithm.py index 66fb646..c0ee07e 100644 --- a/preprocessor/wall_heightaspect_algorithm.py +++ b/preprocessor/wall_heightaspect_algorithm.py @@ -156,23 +156,29 @@ def processAlgorithm(self, parameters, context, feedback): if torch.cuda.is_available(): device = torch.device("cuda") - feedback.setProgressText( - "PyTorch and GPU found. Initiating GPU mode..." - ) + if feedback is not None: + + feedback.setProgressText( + "PyTorch and GPU found. Initiating GPU mode..." + ) elif hasattr(torch, "xpu") and torch.xpu.is_available(): device = torch.device("xpu") - feedback.setProgressText( - "PyTorch and GPU found. Initiating intel GPU mode..." - ) + if feedback is not None: + feedback.setProgressText( + "PyTorch and GPU found. Initiating intel GPU mode..." + ) else: device = torch.device("cpu") - feedback.setProgressText( - "Pytorch found but GPU not found. Initiating CPU mode..." - ) + if feedback is not None: + + feedback.setProgressText( + "Pytorch found but GPU not found. Initiating CPU mode..." + ) else: # Fall back to standard CPU processing - feedback.setProgressText("Running in CPU mode...") + if feedback is not None: + feedback.setProgressText("Running in CPU mode...") provider = dsmin.dataProvider() filepath_dsm = str(provider.dataSourceUri())