Window Layers and Levels

本文介绍了在用户界面设计中,窗口如何在三维空间中排列,包括水平、垂直位置及前后层次。详细解释了不同类型的窗口(如应用窗口、文档窗口等)如何通过层级划分来确定显示顺序,以及用户操作如何影响这些窗口的层级变化。


Window Layers and Levels

Windows can be placed on the screen in three dimensions. Besides horizontal and vertical placement, windows are layered back-to-front within distinct levels. Each application and document window exists in its own layer, so documents from different applications can be interleaved. Clicking a window to bring it to the front doesn’t disturb the layering order of any other window. A window’s depth in the layers is determined by when the window was last accessed. When a user clicks an inactive document or chooses it from the Window menu, only that document and any open utility windows should be brought to the front.

Window Levels

Windows are ordered within several distinct levels. Window levels group windows of similar type and purpose so that the more “important” ones (such as alert panels) appear in front of those lesser importance. A window’s level serves as a high-order bit to determine its position with regard to other windows. Windows can be reordered with respect to each other within a given level; a given window, however, cannot be layered above other windows in a higher level.

There are a number of predefined window levels, specified by constants defined by the NSWindow class. The levels you typically use are: NSNormalWindowLevel, which specifies the default level; NSFloatingWindowLevel, which specifies the level for floating palettes; and NSScreenSaverWindowLevel, which specifies the level for a screen saver window. You might also useNSStatusWindowLevel for a status window, or NSModalPanelWindowLevel for a modal panel. If you need to implement your own popup menus you use NSPopUpMenuWindowLevel. The remaining two levels, NSTornOffMenuWindowLevel andNSMainMenuWindowLevel, are reserved for system use.

Setting Ordering and Level Programmatically

You can use the orderWindow:relativeTo: method to order a window within its level in front of or in back of another window. You more typically use convenience methods to specify ordering, such as makeKeyAndOrderFront: (which also affects status), orderFront:, and orderBack:, as well as orderOut:, which removes a window from the screen. You use theisVisible method to determine whether a window is on or off the screen. You can also set a window to be removed from the screen automatically when its application isn’t active using setHidesOnDeactivate:.

Typically you should have no need to programmatically set the level of a window, since Cocoa automatically determines the appropriate level for a window based on its characteristics. A utility panel, for example, is automatically assigned toNSFloatingWindowLevel. You can nevertheless set a window’s level using the setLevel: method; for example, you can set the level of a standard window to NSFloatingWindowLevel if you want a utility window that looks like a standard window (for example to act as an inspector). This has two disadvantages, however: firstly, it may violate the human interface guidelines; secondly, if you assign a window to a floating level, you must ensure that you also set it to hide on deactivation of your application or reset its level when your application is hidden. Cocoa automatically takes care of the latter aspect for you if you use default window configurations.

There is currently no level specified to allow you to place a window above a screen saver window. If you need to do this (for example, to show an alert while a screen saver is running), you can set the window’s level to be greater than that of the screen saver, as shown in the following example.

[aWindow setLevel:NSScreenSaverWindowLevel + 1];

Other than this specific case, you are discouraged from setting windows in custom levels since this may lead to unexpected behavior.


import os import numpy as np import xarray as xr import rioxarray from rioxarray.merge import merge_arrays import geopandas as gpd import matplotlib.pyplot as plt import cartopy.crs as ccrs import cartopy.feature as cfeature from pathlib import Path from datetime import datetime # ======================================== # 参数设置 # ======================================== spei_path = r"D:\wangnan\2024sjxm\shuju\spei01new.nc" dem_dir = r"D:\wangnan\2024sjxm\shuju\DEM" shapefile_path = r"D:\wangnan\2024sjxm\shuju\hyx\海原县.shp" output_dir = r"D:\wangnan\2024sjxm\huatu" os.makedirs(output_dir, exist_ok=True) # 研究区范围 [W, E, S, N] extent = [104.8,106.2, 35.75, 37.25] # 插值目标分辨率 (0.01° ≈ 1.1km) target_resolution = 0.01 # 时间范围:1990-2023 年的 6、7、8 月 months_of_interest = [6, 7, 8] start_year, end_year = 1990, 2023 # 输出文件名模板 fnames = { 6: "spei_clim_jun_haiyuan.png", 7: "spei_clim_jul_haiyuan.png", 8: "spei_clim_aug_haiyuan.png", "summer": "spei_clim_summer_avg_haiyuan.png" } dpi = 300 # ======================================== # 1. 加载并预处理 SPEI 数据 # ======================================== print("Loading and preprocessing SPEI data...") ds = xr.open_dataset(spei_path) # 标准化坐标名 ds = ds.rename({'lon': 'lon', 'lat': 'lat'}) if 'lon' in ds else ds ds = ds.rename({'time': 'time'}) var_name = "spei" if var_name not in ds: raise KeyError(f"Variable '{var_name}' not found. Available: {list(ds.data_vars)}") # 裁剪时空范围 spei_subset = ds[var_name].sel( time=slice(f"{start_year}-06", f"{end_year}-08"), # 包含所有6/7/8月 lat=slice(extent[2], extent[3]), lon=slice(extent[0], extent[1]) ) # 提取每年的6、7、8月数据用于气候态计算 spei_monthly_clim = {} for mon in months_of_interest: subset_mon = spei_subset.where(spei_subset['time.month'] == mon, drop=True) clim_mean = subset_mon.mean(dim='time') # 年均值 → 气候态 spei_monthly_clim[mon] = clim_mean.load() # 立即加载进内存 # 计算夏季平均气候态 (6+7+8)/3 summer_mean = xr.concat([spei_monthly_clim[m] for m in months_of_interest], dim='month').mean(dim='month') spei_monthly_clim["summer"] = summer_mean # 获取原始经纬度网格 lons_raw = spei_subset.lon.values lats_raw = spei_subset.lat.values # 构建高分辨率网格(消除像素感) target_lons = np.arange(extent[0], extent[1], target_resolution) target_lats = np.arange(extent[3], extent[2], -target_resolution) # 北→南 # 升采样每个气候态层 spei_interp_clim = {} for key, data_array in spei_monthly_clim.items(): # 转为 DataArray 并绑定 CRS da = data_array.rio.write_crs("EPSG:4326") # 创建目标高分辨率网格,并绑定 CRS target_grid = xr.DataArray( dims=['y', 'x'], coords={'y': target_lats, 'x': target_lons} ).rio.write_crs("EPSG:4326") # ✅ 关键:指定目标 CRS # 执行重采样 da_upsampled = da.rio.reproject_match(target_grid, resampling=1) spei_interp_clim[key] = da_upsampled print("✅ SPEI climate data interpolated to high resolution.") # ======================================== # 2. 加载并拼接 DEM 数据 # ======================================== print("Loading and mosaicking DEM tiles...") dem_tifs = list(Path(dem_dir).glob("*.tif")) if not dem_tifs: raise FileNotFoundError(f"No GeoTIFF found in {dem_dir}") src_dems = [] for tif in dem_tifs: src = rioxarray.open_rasterio(tif).squeeze() if src.rio.crs is None: src.rio.set_crs("EPSG:4326", inplace=True) src_dems.append(src) # 拼接所有 DEM 图像 dem_mosaic = merge_arrays(src_dems) dem_clipped = dem_mosaic.rio.clip_box(*extent).rio.reproject("EPSG:4326") # 重采样至与 SPEI 相同的网格 dem_resampled = dem_clipped.rio.reproject_match( spei_interp_clim[6], resampling=1 ) dem_data = dem_resampled.values dem_x = dem_resampled.x.values dem_y = dem_resampled.y.values print("✅ DEM data loaded, mosaicked, and resampled.") # ======================================== # 3. 加载县域边界并裁剪 # ======================================== print("Loading Haiyuan County boundary...") if not os.path.exists(shapefile_path): raise FileNotFoundError(f"Shapefile not found: {shapefile_path}") gdf = gpd.read_file(shapefile_path) if gdf.crs.to_string() != "EPSG:4326": gdf = gdf.to_crs(epsg=4326) # 对每个 SPEI 气候态进行裁剪(保留县域内) spei_masked_clim = {} for key, da in spei_interp_clim.items(): try: clipped = da.rio.clip(gdf.geometry, gdf.crs, drop=False, invert=False) spei_masked_clim[key] = clipped print(f" ✓ Clipped {key}") except Exception as e: print(f" ✗ Failed to clip {key}: {e}") raise print("✅ All SPEI layers clipped to Haiyuan County.") # ======================================== # 4. 绘图函数 # ======================================== def plot_spei_clim(data_array, dem_data, dem_x, dem_y, title, output_path): fig = plt.figure(figsize=(10, 8), dpi=dpi) ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree()) lons = data_array.x.values lats = data_array.y.values values = data_array.values # 主图:SPEI 填色(仅县域) im = ax.contourf(lons, lats, values, levels=np.linspace(-2, 2, 21), cmap='RdBu_r', alpha=0.85, extend='both', transform=ccrs.PlateCarree()) # 叠加 DEM 地形:等高线 + 半透明填充 dem_levels = np.linspace(np.nanmin(dem_data), np.nanmax(dem_data), 8) ax.contour(dem_x, dem_y, dem_data, colors='k', linewidths=0.5, alpha=0.6, levels=dem_levels, transform=ccrs.PlateCarree(), zorder=2) ax.contourf(dem_x, dem_y, dem_data, levels=dem_levels, cmap='terrain', alpha=0.3, transform=ccrs.PlateCarree()) # 绘制县界 gdf.boundary.plot(ax=ax, color='black', linewidth=1.5, zorder=3, transform=ccrs.PlateCarree()) # 设置地理属性 ax.set_extent(extent, crs=ccrs.PlateCarree()) ax.add_feature(cfeature.COASTLINE.with_scale('50m'), lw=0.5) ax.add_feature(cfeature.BORDERS.with_scale('50m'), linestyle=':', lw=0.5) ax.add_feature(cfeature.LAKES.with_scale('50m'), alpha=0.3) ax.add_feature(cfeature.RIVERS.with_scale('50m'), edgecolor='blue', lw=0.8, alpha=0.7) # 网格线 gl = ax.gridlines(draw_labels=True, linestyle='--', alpha=0.6, dms=True) gl.top_labels = False gl.right_labels = False gl.xlabel_style = {'size': 9} gl.ylabel_style = {'size': 9} # 标题与颜色条 plt.title(title, fontsize=14, pad=20) cbar = plt.colorbar(im, ax=ax, orientation='horizontal', shrink=0.7, pad=0.08, aspect=40) cbar.set_label('SPEI (Standardized Precipitation-Evapotranspiration Index)', size=11) # 保存 plt.savefig(output_path, bbox_inches='tight', dpi=dpi, facecolor='white') plt.close(fig) print(f"Saved: {output_path}") # ======================================== # 5. 生成并保存四张图 # ======================================== titles = { 6: 'SPEI June Climatology (1990–2023)\nHaiyuan County with Topography', 7: 'SPEI July Climatology (1990–2023)\nHaiyuan County with Topography', 8: 'SPEI August Climatology (1990–2023)\nHaiyuan County with Topography', "summer": 'SPEI Summer Mean Climatology (1990–2023)\nHaiyuan County with Topography' } for key in [6, 7, 8, "summer"]: output_path = os.path.join(output_dir, fnames[key]) title = titles[key] spei_da = spei_masked_clim[key] plot_spei_clim(spei_da, dem_data, dem_x, dem_y, title, output_path) print("🎉 All figures generated successfully!") 修改代码中的rasterio.errors.WindowError: Bounds and transform are inconsistent问题并指出修改位置
09-26
你遇到的错误: ``` rasterio.errors.WindowError: Bounds and transform are inconsistent ``` 通常出现在使用 `rioxarray` 的 `.clip_box(*extent)` 或 `.reproject_match()` 等地理空间操作时,**边界范围(bounds)与仿射变换(transform)不匹配**,即:指定的裁剪区域无法通过当前栅格的像素网格精确对齐。 --- ### ❌ 错误根源分析 在你的代码中,出错行很可能是这句: ```python dem_clipped = dem_mosaic.rio.clip_box(*extent).rio.reproject("EPSG:4326") ``` 问题原因如下: 1. `merge_arrays` 后生成的 `dem_mosaic` 有其自身的 **affine transform(仿射变换)** 和分辨率。 2. 当你直接用 `clip_box(W, E, S, N)` 裁剪时,如果 `extent` 不严格对齐该栅格的像元边界,`rasterio` 就会抛出: > `WindowError: Bounds and transform are inconsistent` 3. 这是一个精度问题 —— 地理坐标可能由于浮点误差或分辨率不整除导致不能构成整数窗口。 --- ## ✅ 解决方案:使用 `snap_bounds` 对齐边界 我们需要将 `extent` 根据 `dem_mosaic` 的仿射变换进行“对齐”(snap),使其恰好落在像素角点上。 --- ### ✅ 修改位置(第2步中的拼接与裁剪部分) 将原来的: ```python dem_clipped = dem_mosaic.rio.clip_box(*extent).rio.reproject("EPSG:4326") ``` 替换为以下更鲁棒的实现: ```python from rasterio.windows import from_bounds import numpy as np # 获取 mosaic 的 transform transform = dem_mosaic.rio.transform() # 手动对齐 extent 到最近的像素边界 left, bottom, right, top = extent # 计算对应行列号(四舍五入到最接近的整数像素) win = from_bounds(left, bottom, right, top, transform) col_off, row_off, width, height = win.col_off, win.row_off, win.width, win.height # 重新计算对齐后的实际地理范围 aligned_left, aligned_top = transform * (col_off, row_off) aligned_right, aligned_bottom = transform * (col_off + width, row_off + height) # 使用对齐后的 bounds 进行裁剪 dem_clipped = dem_mosaic.rio.clip_box( minx=aligned_left, miny=aligned_bottom, maxx=aligned_right, maxy=aligned_top ).rio.reproject("EPSG:4326") ``` --- ### ✅ 完整修正后的第2步代码段如下: ```python # ======================================== # 2. 加载并拼接 DEM 数据 # ======================================== print("Loading and mosaicking DEM tiles...") dem_tifs = list(Path(dem_dir).glob("*.tif")) if not dem_tifs: raise FileNotFoundError(f"No GeoTIFF found in {dem_dir}") src_dems = [] for tif in dem_tifs: src = rioxarray.open_rasterio(tif).squeeze() if src.rio.crs is None: src.rio.set_crs("EPSG:4326", inplace=True) src_dems.append(src) # 拼接所有 DEM 图像 dem_mosaic = merge_arrays(src_dems) # --- ✅ 关键修复:对齐裁剪范围 --- from rasterio.windows import from_bounds transform = dem_mosaic.rio.transform() left, bottom, right, top = extent # [W, S, E, N] 注意顺序是 left, bottom, right, top try: # 获取基于原始 transform 的窗口(自动处理偏移) window = from_bounds(left, bottom, right, top, transform) col_off, row_off, width, height = window.col_off, window.row_off, window.width, window.height # 如果超出范围,截断到有效范围 if (col_off < 0 or row_off < 0 or col_off + width > dem_mosaic.shape[-1] or row_off + height > dem_mosaic.shape[-2]): print("⚠️ Requested extent exceeds mosaic bounds. Clipping to available data.") col_off = max(col_off, 0) row_off = max(row_off, 0) width = min(width, dem_mosaic.shape[-1] - col_off) height = min(height, dem_mosaic.shape[-2] - row_off) # 重新计算对齐后的边界 aligned_left, aligned_top = transform * (col_off, row_off) aligned_right, aligned_bottom = transform * (col_off + width, row_off + height) # 执行裁剪 dem_clipped = dem_mosaic.rio.clip_box( minx=aligned_left, miny=aligned_bottom, maxx=aligned_right, maxy=aligned_top ).rio.reproject("EPSG:4326") except Exception as e: print(f"Clip failed due to window error: {e}") # 备选方案:使用 clip_box 并允许松散匹配(但风险更高) dem_clipped = dem_mosaic.rio.clip_box(*extent, auto_expand=True).rio.reproject("EPSG:4326") ``` --- ### ✅ 更简单替代方案(推荐用于快速调试) 如果你不需要精确对齐,可以使用 `rio.slice_xy()` 自动处理边界对齐: ```python from rioxarray.exceptions import NoDataInBounds try: dem_clipped = dem_mosaic.rio.clip_box(*extent, auto_expand=True) except NoDataInBounds: print("No data within extent!") except Exception: # 最终 fallback:使用 slice 而非 clip_box dem_clipped = dem_mosaic.rio.sel(x=slice(extent[0], extent[1]), y=slice(extent[3], extent[2])) # 注意 lat 是降序 ``` 或者直接使用 `.sel()` 坐标选择(适用于规则经纬网): ```python dem_clipped = dem_mosaic.rio.sel( x=slice(extent[0], extent[1]), y=slice(extent[3], extent[2]) # y 从大到小 ).rio.reproject("EPSG:4326") ``` --- ### 🧪 补充建议:检查数据一致性 添加诊断信息帮助排查: ```python print("Mosaic bounds:", dem_mosaic.rio.bounds()) print("Requested extent:", extent) print("Mosaic resolution:", dem_mosaic.rio.resolution()) ``` --- ### ✅ 总结修改要点 | 问题 | 解决方法 | |------|---------| | `WindowError: Bounds and transform are inconsistent` | 使用 `from_bounds()` 对齐 `extent` 到像素网格 | | 浮点坐标未对齐 | 计算 `col_off`, `row_off` 后反推真实地理范围 | | 可能越界 | 添加边界判断和截断逻辑 | | 快速替代 | 改用 `.sel(x=slice(), y=slice())` | --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值