-
Notifications
You must be signed in to change notification settings - Fork 2
allow outputing the grid when creating regional maps #193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -62,25 +62,48 @@ class Grid: | |||||||||||||||
| filename = "" | ||||||||||||||||
| lat_name = "" # Name for lat coordinate in file | ||||||||||||||||
| lon_name = "" # Name for lon coordinate in file | ||||||||||||||||
| grid_type = "" # Type of grid file | ||||||||||||||||
| # Additional SCRIP variables | ||||||||||||||||
| area = np.array([]) | ||||||||||||||||
| corner_lat = np.array([]) | ||||||||||||||||
| corner_lon = np.array([]) | ||||||||||||||||
| imask = np.array([]) | ||||||||||||||||
| dims = np.array([]) | ||||||||||||||||
| num_corners = 0 | ||||||||||||||||
|
|
||||||||||||||||
| def __init__(self,filename_,grid_filetype_): | ||||||||||||||||
| self.filename = filename_ | ||||||||||||||||
| self.grid_type = grid_filetype_ | ||||||||||||||||
| f = netCDF4.Dataset(self.filename,"r") | ||||||||||||||||
| if grid_filetype_ == "scrip": | ||||||||||||||||
| self.size = f.dimensions["grid_size"].size | ||||||||||||||||
| self.lat_name = "grid_center_lat" | ||||||||||||||||
| self.lon_name = "grid_center_lon" | ||||||||||||||||
| if "grid_corners" in f.dimensions: | ||||||||||||||||
| self.num_corners = f.dimensions["grid_corners"].size | ||||||||||||||||
| else: | ||||||||||||||||
| self.size = f.dimensions["ncol"].size | ||||||||||||||||
| self.lat_name = "lat" | ||||||||||||||||
| self.lon_name = "lon" | ||||||||||||||||
| f.close() | ||||||||||||||||
|
|
||||||||||||||||
| def grab_chunk(self,chunk): | ||||||||||||||||
| def grab_chunk(self,chunk,load_full_scrip=False): | ||||||||||||||||
| f = netCDF4.Dataset(self.filename,"r") | ||||||||||||||||
| chunk_end = np.min([self.size,self.chunk_idx+chunk]); | ||||||||||||||||
| self.lat = f[self.lat_name][self.chunk_idx:chunk_end].data | ||||||||||||||||
| self.lon = f[self.lon_name][self.chunk_idx:chunk_end].data | ||||||||||||||||
|
|
||||||||||||||||
| # Load additional SCRIP variables if requested | ||||||||||||||||
| if load_full_scrip and self.grid_type == "scrip": | ||||||||||||||||
| self.area = f["grid_area"][self.chunk_idx:chunk_end].data | ||||||||||||||||
| if "grid_corner_lat" in f.variables: | ||||||||||||||||
| self.corner_lat = f["grid_corner_lat"][self.chunk_idx:chunk_end,:].data | ||||||||||||||||
| self.corner_lon = f["grid_corner_lon"][self.chunk_idx:chunk_end,:].data | ||||||||||||||||
| if "grid_imask" in f.variables: | ||||||||||||||||
| self.imask = f["grid_imask"][self.chunk_idx:chunk_end].data | ||||||||||||||||
| if self.chunk_idx == 0 and "grid_dims" in f.variables: | ||||||||||||||||
| self.dims = f["grid_dims"][:].data | ||||||||||||||||
|
|
||||||||||||||||
| self.chunk_idx = chunk_end | ||||||||||||||||
| f.close() | ||||||||||||||||
|
|
||||||||||||||||
|
|
@@ -97,6 +120,20 @@ class Site: | |||||||||||||||
| offset = 0 | ||||||||||||||||
| length = 0 | ||||||||||||||||
| my_id = -1 | ||||||||||||||||
| # Additional SCRIP grid data for regional output (separate for lon and lat filtering) | ||||||||||||||||
| lon_areas = np.array([]) | ||||||||||||||||
| lon_imasks = np.array([]) | ||||||||||||||||
| lon_corner_lats = np.array([]) | ||||||||||||||||
| lon_corner_lons = np.array([]) | ||||||||||||||||
| lat_areas = np.array([]) | ||||||||||||||||
| lat_imasks = np.array([]) | ||||||||||||||||
| lat_corner_lats = np.array([]) | ||||||||||||||||
| lat_corner_lons = np.array([]) | ||||||||||||||||
| # Final filtered SCRIP data | ||||||||||||||||
| areas = np.array([]) | ||||||||||||||||
| corner_lats = np.array([]) | ||||||||||||||||
| corner_lons = np.array([]) | ||||||||||||||||
| imasks = np.array([]) | ||||||||||||||||
|
|
||||||||||||||||
| def __init__(self,name_,bnds,bnds_type): | ||||||||||||||||
| self.name = name_ | ||||||||||||||||
|
|
@@ -110,12 +147,30 @@ def __init__(self,name_,bnds,bnds_type): | |||||||||||||||
| self.lat_bnds = slat + np.array([-1,1])*srad | ||||||||||||||||
| self.lon_bnds = slon + np.array([-1,1])*srad | ||||||||||||||||
|
|
||||||||||||||||
| def filter_gids(self): | ||||||||||||||||
| mask_lon = np.in1d(self.lon_gids,self.lat_gids) | ||||||||||||||||
| mask_lat = np.in1d(self.lat_gids,self.lon_gids) | ||||||||||||||||
| def filter_gids(self,filter_scrip=False): | ||||||||||||||||
| mask_lon = np.isin(self.lon_gids,self.lat_gids) | ||||||||||||||||
| mask_lat = np.isin(self.lat_gids,self.lon_gids) | ||||||||||||||||
| self.gids = self.lon_gids[mask_lon] | ||||||||||||||||
| self.lons = self.lons[mask_lon] | ||||||||||||||||
| self.lats = self.lats[mask_lat] | ||||||||||||||||
| if filter_scrip: | ||||||||||||||||
| self.areas = self.lon_areas[mask_lon] | ||||||||||||||||
| self.imasks = self.lon_imasks[mask_lon] | ||||||||||||||||
| if len(self.lon_corner_lats) > 0: | ||||||||||||||||
| self.corner_lats = self.lon_corner_lats[mask_lon,:] | ||||||||||||||||
| self.corner_lons = self.lon_corner_lons[mask_lon,:] | ||||||||||||||||
|
|
||||||||||||||||
| # Sort by global IDs to preserve original grid order | ||||||||||||||||
| sort_idx = np.argsort(self.gids) | ||||||||||||||||
| self.gids = self.gids[sort_idx] | ||||||||||||||||
| self.lons = self.lons[sort_idx] | ||||||||||||||||
| self.lats = self.lats[sort_idx] | ||||||||||||||||
| if filter_scrip: | ||||||||||||||||
| self.areas = self.areas[sort_idx] | ||||||||||||||||
| self.imasks = self.imasks[sort_idx] | ||||||||||||||||
| if len(self.corner_lats) > 0: | ||||||||||||||||
| self.corner_lats = self.corner_lats[sort_idx,:] | ||||||||||||||||
| self.corner_lons = self.corner_lons[sort_idx,:] | ||||||||||||||||
|
|
||||||||||||||||
| class Sites: | ||||||||||||||||
| m_sites = [] | ||||||||||||||||
|
|
@@ -186,6 +241,82 @@ def construct_remap(casename,sites,grid): | |||||||||||||||
| csvwriter = csv.writer(csvfile) | ||||||||||||||||
| for s in sites: | ||||||||||||||||
| csvwriter.writerow([s.my_id,s.name,str(s.offset+1),str(s.offset+s.length)]) | ||||||||||||||||
|
|
||||||||||||||||
| def construct_regional_grid(casename,sites,grid): | ||||||||||||||||
| """Create a SCRIP format grid file containing only the regional subset.""" | ||||||||||||||||
| # Combine all sites into a single regional grid | ||||||||||||||||
| total_size = sum(s.length for s in sites) | ||||||||||||||||
|
|
||||||||||||||||
|
||||||||||||||||
| # If no grid cells are found, warn and do not create an empty grid file | |
| if total_size == 0: | |
| print(f"No grid cells found for any site; regional grid file '{casename}_regional_grid.nc' will not be created.", file=sys.stderr) | |
| return | |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential IndexError if no sites are provided. The code accesses sites[0].corner_lats.shape[1] without first checking if the sites list is empty. Consider adding a check: if not sites: raise ValueError("No sites provided") at the beginning of the function, or add a guard before accessing sites[0].
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing validation: The function doesn't validate that each site has the required SCRIP data (areas, imasks) before accessing them. If filter_scrip was False for a site (which shouldn't happen based on current code flow, but could if the API is used differently), accessing s.areas or s.imasks at lines 266-267 would use empty arrays, leading to silent data corruption. Consider adding a validation check or assertion at the start of the function to ensure all sites have populated SCRIP data.
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent handling of empty corner data: At line 255, has_corners checks if any site has corner data. However, at lines 159-161 and 269-270, the code assumes that if one site has corners, the array will have a valid shape. If a site matches no grid cells (empty after filtering), s.corner_lats could be an empty array without a second dimension, causing an error when trying to access .shape[1] or when trying to assign to corner_lats[offset:offset+n,:]. Consider adding checks to handle sites with no matched cells gracefully.
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code assumes all sites have the same number of corners when has_corners is True, but doesn't validate this assumption. If sites have different corner counts (e.g., one site has 4 corners and another has 3), this could lead to shape mismatch errors when filling the corner arrays at lines 269-270. Consider adding validation to ensure all sites with corners have the same shape, or handle sites with different corner counts appropriately.
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing documentation: The file header documentation (lines 32-49) describes the outputs of this script but doesn't mention the new optional regional grid output file that can be generated with the --output-regional-grid flag. Consider adding a third output to the documentation describing the regional_grid.nc file and its format.
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Performance issue: Using np.vstack in a loop (lines 376, 377, 393, 394) repeatedly reallocates and copies arrays, which is inefficient for large datasets. Since you're already using chunked reading for the main grid, consider either: (1) using a list and converting to array at the end with np.vstack(list) once, or (2) pre-allocating arrays and using index-based assignment, similar to how lons and lon_gids are handled with np.append.
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type safety concern: lon_imasks and lat_imasks are accumulated using np.append without explicit dtype, but the final imasks is expected to be int32 (line 254). If the source grid_imask has a different integer type, this could lead to type inconsistencies. Consider explicitly casting when appending: isite.lon_imasks = np.append(isite.lon_imasks, src_grid.imask[lids[0]]).astype(np.int32) or initializing the arrays with the correct dtype in the Site class.
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Performance issue: Using np.vstack in a loop repeatedly reallocates and copies arrays, which is inefficient for large datasets. Since you're already using chunked reading for the main grid, consider either: (1) using a list and converting to array at the end with np.vstack(list) once, or (2) pre-allocating arrays and using index-based assignment, similar to how lats and lat_gids are handled with np.append.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
load_scripis True butgrid_areavariable doesn't exist in the netCDF file, this will raise a KeyError. While SCRIP format files should have this variable, consider adding a check similar to the optional variables (grid_corner_lat, grid_imask) to handle files that might be missing this field, or at minimum provide a more informative error message.