Skip to content

Commit

Permalink
Merge pull request #22 from fuadyassin/main
Browse files Browse the repository at this point in the history
added function to generate mesh hydrology ini file from excel and its documentations
  • Loading branch information
fuadyassin authored Feb 9, 2025
2 parents 4fcb271 + b58d897 commit 82adc4a
Show file tree
Hide file tree
Showing 8 changed files with 566 additions and 43 deletions.
121 changes: 79 additions & 42 deletions MESHpyPreProcessing/generate_mesh_class_ini_from_excel.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,56 @@
import os
import pandas as pd

def generate_mesh_class_ini_from_excel(excel_file, output_file, selected_land_covers):
def generate_mesh_class_ini_from_excel(excel_file, output_file, selected_land_covers, num_cels, lat, lon):
"""
Generates a MESH parameter file (.ini) using values from an Excel file.
This function reads parameter values from an Excel sheet and structures them into
a MESH-compatible `.ini` file, following the format of vegetation, one-to-one,
and multi-value assignments.
Parameters:
------------
excel_file : str
Path to the Excel file containing parameter values.
output_file : str
Path to the output `.ini` file where processed values will be written.
selected_land_covers : list of str
A list of land cover types to include (case-insensitive). These land covers
should match column names in the Excel file.
num_cels : int
Number of grid cells in the model domain.
lat : float
Latitude of the location.
lon : float
Longitude of the location.
Overview:
------------
This function extracts land cover parameter values from an Excel file and writes them into a MESH-compatible `.ini` file.
Only active land covers are included, as indicated by the 'status' row in the Excel sheet.
Processing Steps:
1. Load the Excel file and normalize column names.
2. Identify active land covers (status > 0).
3. Verify required rows such as 'colum'.
4. Extract vegetation and land cover parameters.
5. Write formatted values into an `.ini` file with the required MESH structure.
Output Format:
------------
The generated `.ini` file follows MESH parameter conventions with:
- Header defining basin information.
- Land cover-specific vegetation and hydrological parameters.
- Footer containing model time initialization values.
File Structure:
----------------
The output file consists of:
1. **Header Information**: Includes metadata such as location, author, and details.
2. **Land Cover Blocks**: Each selected land cover is written separately, including:
- Vegetation parameters (written in pairs)
- One-to-One parameter assignments (written in pairs)
- Multi-value parameter assignments (written in structured format)
3. **Final Footer**: Contains three mandatory lines required for MESH processing.
1. **Header Information**: Includes metadata such as location, author, and details.
2. **Land Cover Blocks**: Each selected land cover is written separately, including:
- Vegetation parameters (written in pairs)
- One-to-One parameter assignments (written in pairs)
- Multi-value parameter assignments (written in structured format)
3. **Final Footer**: Contains three mandatory lines required for MESH processing.
Example Usage:
--------------
>>> generate_mesh_ini_from_excel("meshparameters.xlsx", "MESH_output.ini", ["Forest", "crop"])
>>> pip install git+https://github.com/MESH-Model/MESH-Scripts-PyLib.git
>>> generate_mesh_ini_from_excel("meshparameters.xlsx", "MESH_output.ini", ["Forest", "crop"],num_cels=7408, lat=53.18, lon=-99.25)
"""
# Load Excel file
df = pd.read_excel(excel_file, sheet_name='Sheet1')
Expand All @@ -42,25 +60,31 @@ def generate_mesh_class_ini_from_excel(excel_file, output_file, selected_land_co
# Define vegetation columns
vegetation_cols = ['v_nforest', 'v_bforest', 'v_crop', 'v_grass', 'v_bare']

# Ensure colum row exists
# Parameters that should be replaced with empty space when assign_col == 'v_bare'
empty_space_params = {'lamx', 'lamn', 'cmas', 'root', 'qa50', 'vpdp', 'psgb', 'psga', 'vpda', 'rsmn'}
empty_space = " " * 8 # Fixed length of empty space

# Ensure 'colum' row exists
colum_row = df[df['par'] == 'colum']
if colum_row.empty:
raise ValueError("The 'colum' row is missing in the provided Excel file.")

# Compute the length dynamically
land_cover_count = len(selected_land_covers)
#print(land_cover_count)
with open(output_file, 'w') as f:
# Write header
f.write("NCRB\n")
f.write("FuadYassin\n")
f.write("ECCC\n")
f.write(" 53.18 -99.25 40.00 40.00 50.00 -1.0 1 6408 16\n\n")
f.write("Basin\n")
f.write("Author\n")
f.write("Org\n")
f.write(f" {lat} {lon} 40.00 40.00 50.00 -1.0 1 {num_cels} {land_cover_count}\n")

for lc in selected_land_covers:
lc_lower = lc.lower()
if lc_lower not in df.columns:
raise ValueError(f"Land cover '{lc}' is not found in the Excel columns.")
f.write(f"# Land Cover: {lc}\n")

#f.write(f"# Land Cover: {lc}\n")

# Block 1: Vegetation Parameters
vegetation_pairs = [
('fcan', 'lamx'),
Expand All @@ -71,35 +95,48 @@ def generate_mesh_class_ini_from_excel(excel_file, output_file, selected_land_co
('vpda', 'vpdp'),
('psga', 'psgb')
]

for pair in vegetation_pairs:
values_pair = []
for param in pair:
values = {col: 0.000 for col in vegetation_cols} # Default zero
values = {col: f"{0.000:8.3f}" for col in vegetation_cols} # Default is zero

if param in df['par'].values:
if param in empty_space_params:
values['v_bare'] = empty_space
assigned_col = colum_row[lc_lower].values[0].lower()
if assigned_col in vegetation_cols:
#print(assigned_col)
try:
values[assigned_col] = float(df[df['par'] == param][lc_lower].values[0])
if assigned_col == 'v_bare' and param in empty_space_params:
# values[assigned_col] = empty_space # Replace with empty space for specific parameters
print(values['v_bare'])
else:
values[assigned_col] = f"{float(df[df['par'] == param][lc_lower].values[0]):8.3f}"
except (ValueError, IndexError):
values[assigned_col] = 0.000
values[assigned_col] = f"{0.000:8.3f}"
values_pair.append(values)

# Write both parameters in one line
f.write(" " + " ".join(f"{values_pair[i][col]:8.3f}" for i in range(len(pair)) for col in vegetation_cols) + f" # {', '.join(pair)}\n")
f.write(" " + " ".join(values_pair[i][col] for i in range(len(pair)) for col in vegetation_cols) + f" # {', '.join(pair)}\n")

# Block 2: One-to-One Parameter Assignments
one_to_one_pairs = [
('drn', 'sdep', 'fare', 'dden'),
('xslp', 'grkf', 'man', 'wfci', 'mid', 'name ')
('xslp', 'grkf', 'man', 'wfci', 'mid', 'name')
]
for pair in one_to_one_pairs:
values_pair = []
for param in pair:
if param in df['par'].values:
try:
param_value = float(df[df['par'] == param][lc_lower].values[0]) if lc_lower in df.columns else 0.000
if param == "name":
param_value = str(df[df['par'] == param][lc_lower].values[0]) if lc_lower in df.columns else "N/A"
else:
param_value = float(df[df['par'] == param][lc_lower].values[0]) if lc_lower in df.columns else 0.000
except (ValueError, IndexError):
param_value = 0.000
values_pair.append(f"{param_value:8.3f}")
param_value = "N/A" if param == "name" else 0.000
values_pair.append(f"{param_value:8.3f}" if param != "name" else f"{param_value:>8}")
else:
values_pair.append(f"{0.000:8.3f}")
f.write(" " + " ".join(values_pair) + f" # {', '.join(pair)}\n")
Expand Down Expand Up @@ -133,15 +170,15 @@ def generate_mesh_class_ini_from_excel(excel_file, output_file, selected_land_co
else:
values_pair.append(" " + f"{0.000:8.3f}")
f.write(" " + " ".join(values_pair) + f" # {', '.join(pair)}\n")

f.write("\n") # Add a blank line to separate land cover blocks

# Footer
f.write(" 0 0 0 0 20 (not used, but 4x integer values are required)\n")
f.write(" 0 0 0 0 21 (not used, but 4x integer values are required)\n")
f.write(" 0 0 0 0 22 IHOUR/IMINS/IJDAY/IYEAR\n")
print(f"MESH parameter file '{output_file}' created successfully!")

print(f"MESH parameter file '{output_file}' created successfully!")

# Example usage
# generate_mesh_class_ini_from_excel("/content/drive/MyDrive/ColabNotebook_FY/meshparametersvalues1.xlsx", "MESH_output.ini", ["Forest", "crop"])
# Example usage , num_cels, lat, lon
#generate_mesh_class_ini_from_excel("/content/drive/MyDrive/ColabNotebook_FY/meshparametersvalues1.xlsx", "MESH_output.ini", ["Forest", "crop"],num_cels=7408, lat=53.18, lon=-99.25)
176 changes: 176 additions & 0 deletions MESHpyPreProcessing/generate_mesh_hydrology_ini_from_excel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import pandas as pd

def generate_mesh_hydrology_ini_from_excel(excel_file, sheet_name, output_file):
"""
---------------------------------------------------------------------------------
Description:
---------------------------------------------------------------------------------
This function reads hydrology parameter values from an Excel file and generates
a formatted MESH Hydrology `.ini` file. The generated file is structured into
various parameter categories with appropriate headers, formatting, and spacing.
---------------------------------------------------------------------------------
Parameters:
---------------------------------------------------------------------------------
- excel_file : str -> Path to the input Excel file containing parameter values.
- sheet_name : str -> The specific sheet in the Excel file that holds the data.
- output_file : str -> Path to the output `.ini` file.
---------------------------------------------------------------------------------
Function Overview:
---------------------------------------------------------------------------------
1. Reads the Excel file and extracts three main parameter categories:
- **Channel Routing Parameters**
- **GRU-Independent Parameters**
- **GRU-Dependent Parameters**
2. Filters parameters where `status == 1` (active parameters).
3. Sorts and formats values for proper alignment:
- Parameter names are **left-aligned** (12 characters).
- Values are **right-aligned** (10 characters, formatted as `.3f` if float).
4. Writes the output `.ini` file with section headers, formatted values,
and comments where necessary.
---------------------------------------------------------------------------------
Output Format:
---------------------------------------------------------------------------------
- **Channel Routing Parameters**:
```
##### Channel routing parameters #####
-----#
3 # Number of channel routing parameters
R2N 0.250 0.250 0.250
```
- **GRU-Independent Parameters**:
```
##### GRU-independent parameters #####
-------#
5 # Number of GRU independent hydrologic parameters
SOIL_DEPTH 4
```
- **GRU-Dependent Parameters**:
```
##### GRU-dependent parameters #####
-------#
!> Active headers in sorted order: Forest, Grass, Wetland
4 # Number of GRU-dependent parameters
ZSNL 0.100 0.200 0.300
```
---------------------------------------------------------------------------------
File Structure:
---------------------------------------------------------------------------------
- **Header section**: Explains format rules.
- **Option Flags**: Indicates no optional flags.
- **Channel Routing Parameters**: Defines routing-related parameters.
- **GRU-Independent Parameters**: Contains parameters that apply to all land units.
- **GRU-Dependent Parameters**: Contains parameters that vary by land category.
---------------------------------------------------------------------------------
Example Usage:
---------------------------------------------------------------------------------
>>> excel_file = "/content/drive/MyDrive/ColabNotebook_FY/meshparametersvalues2.xlsx"
>>> sheet_name = "hydrology_ini"
>>> output_file = "MeshHydrology.ini"
>>> generate_mesh_hydrology_ini_from_excel(excel_file, sheet_name, output_file)
- This will generate a properly formatted `MeshHydrology.ini` file in the specified path.
---------------------------------------------------------------------------------
"""

# Read the Excel file and specified sheet
data = pd.read_excel(excel_file, sheet_name=sheet_name)

# Filter rows for 'channel_routing_header' to get channel routing parameters
channel_routing_data = data[data['channel_routing_header'] == 'channel_routing']

# Further filter rows where 'status' is 1 for channel routing
active_channel_routing = channel_routing_data[channel_routing_data['status'] == 1]

# Count the number of active channel routing parameters
num_active_channel_routing = len(active_channel_routing)

# Define the header content
header = """2.0: MESH Hydrology parameters input file (Version 2.0)
!> Any line leading with '!' is ignored
!> Spacing, whether by tabs or spaces doesn't matter
!> Parameter/field names are not case sensitive
##### Option Flags #####
----#
0 # Number of option flags
##### Channel routing parameters #####
-----#
"""

# Add the number of active channel routing parameters
content = header + f"{num_active_channel_routing:<12}# Number of channel routing parameters\n"

# Write the active channel routing parameters
for idx, row in active_channel_routing.iterrows():
param_name = row['par']
param_values = row['value']
if isinstance(param_values, str) and param_values.startswith("{"):
values_list = param_values.strip("{}").split(",")
formatted_values = "".join([f"{float(val):>10.3f}" if '.' in val else f"{int(val):>10}" for val in values_list])
else:
formatted_values = f"{float(param_values):>10.3f}" if isinstance(param_values, float) else f"{int(param_values):>10}"
content += f"{param_name:<12}{formatted_values}"
if idx != active_channel_routing.index[-1]:
content += "\n"

# Add GRU-independent parameters section
content += "\n##### GRU-independent parameters #####\n-------#\n"

# Filter GRU-independent parameters
gru_independent_data = data[data['channel_routing_header'] == 'GRU_class_independent']
active_gru_independent = gru_independent_data[gru_independent_data['status'] == 1]
num_active_gru_independent = len(active_gru_independent)

# Add count and parameters
content += f"{num_active_gru_independent:<12}# Number of GRU independent hydrologic parameters\n"
for idx, row in active_gru_independent.iterrows():
param_name = row['par']
param_values = row['value']
if isinstance(param_values, str) and param_values.startswith("{"):
values_list = param_values.strip("{}").split(",")
formatted_values = "".join([f"{float(val):>10.3f}" if '.' in val else f"{int(val):>10}" for val in values_list])
else:
formatted_values = f"{float(param_values):>10.3f}" if isinstance(param_values, float) else f"{int(param_values):>10}"
content += f"{param_name:<12}{formatted_values}"
if idx != active_gru_independent.index[-1]:
content += "\n"

# Add GRU-dependent parameters section
content += "\n##### GRU-dependent parameters #####\n-------#\n"

# Process GRU-dependent parameters
header_row = data[data.iloc[:, 0] == 'GRU_class_dependent_header']
if not header_row.empty:
new_headers = header_row.iloc[0].values
data.columns = new_headers
data = data[data.iloc[:, 0] != 'GRU_class_dependent_header']
active_row = data[data.iloc[:, 0] == 'GRU_class_dependent_active']
if not active_row.empty:
active_row = active_row.iloc[0].apply(pd.to_numeric, errors='coerce')
active_columns = active_row[active_row > 0].index
sorted_active_columns = active_row[active_columns].sort_values(ascending=True)
active_header_names = ", ".join(sorted_active_columns.index)
content += f"!> Active headers in sorted order: {active_header_names}\n"
dependent_params = data[data.iloc[:, 0] == 'GRU_class_dependent']
active_params = dependent_params[dependent_params['status'] == 1]
num_active_params = len(active_params)
content += f"{num_active_params:<12}# Number of GRU-dependent parameters\n"
for idx, row in active_params.iterrows():
param_name = row['par']
values = [row[col] for col in sorted_active_columns.index]
formatted_values = "".join([
f"{float(val):>10.3f}" if isinstance(val, float) or "." in str(val) else f"{int(val):>10}"
for val in values
])
content += f"{param_name:<12}{formatted_values}"
if idx != active_params.index[-1]:
content += "\n"

with open(output_file, 'w') as f:
f.write(content)
1 change: 1 addition & 0 deletions docs/requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ shapely
geopandas
shapely
graphviz
ipykernel
2 changes: 2 additions & 0 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#
# pip-compile requirements.in
#
ipykernel
jupyter
alabaster==0.7.16
# via sphinx
attrs==23.2.0
Expand Down
Loading

0 comments on commit 82adc4a

Please sign in to comment.