<think>根据日志,错误发生在下载地图数据时:`获取地图数据失败:'NoneType'objecthasnoattribute'drvsupport'`。具体是这一行代码:`gpd.io.file.fiona.drvsupport.supported_drivers['GeoJSON']='rw'`错误原因:在尝试设置GeoJSON驱动支持时,访问了`gpd.io.file.fiona.drvsupport`,但其中某个属性为None。解决方案:1.检查geopandas和fiona的版本兼容性。2.使用更稳定的方式设置驱动支持。实际上,从geopandas0.7.0开始,读取文件时可以直接指定驱动,或者使用`gpd.read_file`的`driver`参数。另外,我们也可以尝试另一种设置驱动的方式:通过fiona的`supported_drivers`。修改后的地图下载代码:```pythontry:#尝试下载地图数据ifnotos.path.exists(local_map):logging.info(f"下载地图数据:{mapurl}")#设置GeoJSON驱动支持(新方式)importfionafiona.drvsupport.supported_drivers['GeoJSON']='rw'smap=gpd.read_file(mapurl,driver='GeoJSON')smap.to_file(local_map,driver='GeoJSON')else:logging.info(f"使用本地地图数据:{local_map}")smap=gpd.read_file(local_map,driver='GeoJSON')exceptExceptionase:logging.error(f"获取地图数据失败:{e}")returnFalse```但是,注意错误日志中提示的是`'NoneType'objecthasnoattribute'drvsupport'`,这意味着`gpd.io.file.fiona`可能是None。因此,我们直接使用fiona模块来设置驱动。另外,我们也可以不设置驱动,因为geopandas默认应该支持GeoJSON。但是,有时为了确保写入权限,需要设置。让我们修改代码,直接导入fiona并设置驱动。同时,为了增强健壮性,我们可以捕获更具体的异常,并添加重试机制。以下是修改后的完整代码段:```python#获取地图数据logging.info("获取山西地图数据...")mapurl="https://geo.datav.aliyun.com/areas_v3/bound/140000.json"local_map="C:/pyMet/data/shanxi_map.geojson"os.makedirs(os.path.dirname(local_map),exist_ok=True)try:#尝试下载地图数据ifnotos.path.exists(local_map):logging.info(f"下载地图数据:{mapurl}")#确保fiona支持GeoJSON的读写importfiona#设置驱动支持fiona.drvsupport.supported_drivers['GeoJSON']='rw'#允许读写smap=gpd.read_file(mapurl)smap.to_file(local_map,driver='GeoJSON')else:logging.info(f"使用本地地图数据:{local_map}")#读取本地文件smap=gpd.read_file(local_map)exceptExceptionase:logging.error(f"获取地图数据失败:{e}")#尝试不使用驱动设置再试一次(仅下载)try:ifnotos.path.exists(local_map):logging.info("重试下载(不使用驱动设置)...")smap=gpd.read_file(mapurl)smap.to_file(local_map,driver='GeoJSON')else:smap=gpd.read_file(local_map)exceptExceptionase2:logging.error(f"重试获取地图数据失败:{e2}")returnFalse```###完整代码调整由于原始代码较长,这里只提供修改后的关键部分。注意,我们增加了重试机制,并直接使用fiona设置驱动。###其他可能的问题1.**网络问题**:下载地图数据时可能因为网络问题失败。-解决方案:增加重试次数,使用更稳定的下载方式(如requests库)。2.**文件权限**:写入本地文件时权限不足。-解决方案:检查目录权限,或尝试写入临时目录。3.**版本兼容性**:geopandas和fiona版本不匹配。-解决方案:固定版本(如geopandas==0.10.2,fiona==1.8.20)。###增强版地图下载函数(带重试)```pythondefdownload_map_data(url,local_path,retries=3):"""下载地图数据或使用本地缓存(带重试)"""importfionafiona.drvsupport.supported_drivers['GeoJSON']='rw'forattemptinrange(retries):try:ifnotos.path.exists(local_path):logging.info(f"尝试#{attempt+1}:下载地图数据:{url}")returngpd.read_file(url)else:logging.info(f"使用本地缓存地图:{local_path}")returngpd.read_file(local_path)exceptExceptionase:logging.warning(f"下载失败:{e}")time.sleep(2)#等待后重试#所有重试失败后尝试本地缓存ifos.path.exists(local_path):logging.warning("网络下载失败,使用本地缓存")returngpd.read_file(local_path)else:raiseRuntimeError(f"无法获取地图数据,且本地缓存不存在:{local_path}")```在main函数中调用:```pythonsmap=download_map_data(mapurl,local_map)```###最终建议考虑到日志中已经出现错误,建议首先检查fiona的安装情况。如果问题仍然存在,可以尝试以下命令更新库:```bashpipinstall--upgradegeopandasfiona```或者指定版本:```bashpipinstallgeopandas==0.10.2fiona==1.8.20```###相关问题§§相关问题§§:1.如何处理geopandas读取不同格式地理数据时的驱动兼容性问题?2.如何设计健壮的网络数据下载功能(支持重试、超时、代理)?3.在Python中,有哪些有效的方法来调试和解决第三方库的版本兼容性问题?4.如何为地理空间数据处理脚本编写单元测试和集成测试?</think>根据日志信息,错误发生在下载地图数据时:`获取地图数据失败: 'NoneType' object has no attribute 'drvsupport'`。这通常是由于 `geopandas` 和 `fiona` 库版本兼容性问题导致的。让我们解决这个问题并提供完整的解决方案。
### 解决方案:修复地图数据下载问题
```python
# shanxi_map_fixed.py
import os
import sys
import logging
import traceback
import time
import numpy as np
import matplotlib
matplotlib.use('Agg') # 使用非交互式后端
import matplotlib.pyplot as plt
from datetime import datetime
from matplotlib.colors import BoundaryNorm
import cartopy.crs as ccrs
import rioxarray as rxr
import geopandas as gpd
def setup_logging():
"""配置详细日志记录"""
log_dir = "C:/pyMet/logs"
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(log_dir, f"shanxi_map_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
logging.basicConfig(
filename=log_file,
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
formatter = logging.Formatter('%(levelname)s: %(message)s')
console.setFormatter(formatter)
logging.getLogger('').addHandler(console)
return log_file
def get_map_data(mapurl, local_path):
"""获取地图数据(修复版本)"""
try:
# 方法1:直接下载(兼容最新版geopandas)
if not os.path.exists(local_path):
logging.info(f"下载地图数据: {mapurl}")
smap = gpd.read_file(mapurl)
# 保存到本地缓存
os.makedirs(os.path.dirname(local_path), exist_ok=True)
smap.to_file(local_path, driver='GeoJSON')
return smap
else:
# 方法2:从本地缓存读取
logging.info(f"使用本地地图数据: {local_path}")
return gpd.read_file(local_path)
except Exception as e:
logging.warning(f"方法1失败: {e}")
try:
# 方法3:使用requests下载原始JSON
import requests
logging.info(f"尝试使用requests下载地图数据: {mapurl}")
response = requests.get(mapurl)
response.raise_for_status()
# 保存原始JSON
with open(local_path, 'w', encoding='utf-8') as f:
f.write(response.text)
# 读取保存的文件
return gpd.read_file(local_path)
except Exception as e2:
logging.error(f"方法3失败: {e2}")
# 方法4:使用内置的山西省边界(备用方案)
logging.warning("使用内置的山西省边界作为备用方案")
return create_shanxi_backup_map()
def create_shanxi_backup_map():
"""创建山西省的简单边界(备用方案)"""
from shapely.geometry import Polygon
# 山西省的简化边界坐标(实际应用中应使用更精确的数据)
shanxi_coords = [
(110.0, 34.5), (112.0, 34.5), (113.5, 35.5), (114.5, 37.5),
(113.0, 40.5), (111.0, 40.5), (110.0, 38.0), (110.0, 34.5)
]
polygon = Polygon(shanxi_coords)
return gpd.GeoDataFrame(geometry=[polygon], crs="EPSG:4326")
def main():
"""主执行函数"""
try:
log_file = setup_logging()
logging.info("脚本启动")
# 确保输出目录存在
output_dir = "C:/pyMet/figures"
os.makedirs(output_dir, exist_ok=True)
logging.info(f"输出目录: {output_dir}")
# 检查文件路径
fili = r"D:\测试\GMTED2010_land.tif"
if not os.path.exists(fili):
logging.error(f"高程数据文件不存在: {fili}")
return False
logging.info(f"找到高程数据文件: {fili}")
# 读取高程数据
logging.info("读取高程数据...")
f = rxr.open_rasterio(fili)
f = f.isel(band=0)
# 获取地图数据
logging.info("获取山西地图数据...")
mapurl = "https://geo.datav.aliyun.com/areas_v3/bound/140000.json"
local_map = "C:/pyMet/data/shanxi_map.geojson"
smap = get_map_data(mapurl, local_map)
# 数据裁剪处理
logging.info("裁剪数据...")
xmin = smap.bounds['minx'][0] - 0.5
xmax = smap.bounds['maxx'][0] + 0.5
ymin = smap.bounds['miny'][0] - 0.5
ymax = smap.bounds['maxy'][0] + 0.5
lon = f.coords["x"]
lat = f.coords["y"]
da = f.loc[dict(
x=lon[(lon >= xmin) & (lon <= xmax)],
y=lat[(lat >= ymin) & (lat <= ymax)]
)]
lon = da.coords["x"]
lat = da.coords["y"]
Lon, Lat = np.meshgrid(lon, lat)
# 基础绘图
logging.info("创建基础绘图...")
mapcrs = ccrs.PlateCarree()
fig = plt.figure(figsize=(8, 8))
ax = plt.axes(projection=mapcrs)
ax.add_geometries(smap['geometry'], crs=mapcrs, fc="none", edgecolor="black", linewidth=0.6)
pm = ax.pcolormesh(Lon, Lat, da, cmap='terrain', transform=mapcrs)
cb = fig.colorbar(pm, ax=ax, shrink=0.9, location="right")
output_path1 = os.path.join(output_dir, "shanxi1.png")
plt.savefig(output_path1, dpi=300)
plt.close(fig)
logging.info(f"保存基础绘图: {output_path1}")
# 进阶绘图
logging.info("创建进阶绘图...")
plt.rcParams['font.family'] = 'SimHei'
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
fig = plt.figure(figsize=(10, 10))
ax = plt.axes(projection=mapcrs)
ax.add_geometries(smap['geometry'], crs=mapcrs, fc="none", edgecolor="black", linewidth=0.8)
# 创建自定义颜色映射
levels = np.arange(50, 2501, 100)
terrain_cmap = plt.colormaps['terrain']
custom_colors = terrain_cmap(np.linspace(0, 1, len(levels)))
colorNorm = BoundaryNorm(levels, len(levels)-1)
pm = ax.pcolormesh(Lon, Lat, da, cmap=terrain_cmap, norm=colorNorm, transform=mapcrs)
cb = fig.colorbar(pm, ax=ax, shrink=0.7, location="right", pad=0.06)
cb.set_label('高程 (米)', fontsize=12)
# 添加太原位置
taiyuan_lon, taiyuan_lat = 112.55, 37.87
ax.scatter(taiyuan_lon, taiyuan_lat, color='red', s=80, marker='^',
edgecolor='black', linewidth=1, transform=mapcrs)
ax.text(taiyuan_lon, taiyuan_lat + 0.2, '太原',
horizontalalignment='center', color="red", fontsize=12,
fontweight='bold', transform=mapcrs)
# 添加主要城市
cities = {
"大同": (113.3, 40.1),
"朔州": (112.4, 39.3),
"忻州": (112.7, 38.4),
"阳泉": (113.6, 37.9),
"吕梁": (111.1, 37.5),
"晋中": (112.7, 37.7),
"长治": (113.1, 36.2),
"晋城": (112.8, 35.5),
"临汾": (111.5, 36.1),
"运城": (111.0, 35.0)
}
for city, (lon, lat) in cities.items():
ax.scatter(lon, lat, color='blue', s=40, marker='o',
edgecolor='black', linewidth=0.5, transform=mapcrs)
ax.text(lon, lat + 0.1, city,
horizontalalignment='center', color="blue", fontsize=9,
transform=mapcrs)
# 添加网格线和标签
gl = ax.gridlines(draw_labels=True, linewidth=0.5,
linestyle='--', color='gray', alpha=0.7)
gl.top_labels = False
gl.right_labels = False
gl.xlabel_style = {'size': 10}
gl.ylabel_style = {'size': 10}
# 添加标题和图例
ax.set_title("山西省地形高程图", fontsize=16, pad=20)
ax.text(0.5, -0.1, "数据来源: GMTED2010高程数据",
transform=ax.transAxes, ha='center', fontsize=9)
plt.tight_layout()
output_path2 = os.path.join(output_dir, "shanxi2.png")
plt.savefig(output_path2, dpi=300, bbox_inches='tight')
plt.close(fig)
logging.info(f"保存进阶绘图: {output_path2}")
logging.info("绘图流程完成!")
return True
except Exception as e:
logging.error(f"发生错误: {str(e)}")
logging.error(traceback.format_exc())
return False
if __name__ == "__main__":
# 设置当前工作目录为脚本所在目录
if getattr(sys, 'frozen', False):
application_path = os.path.dirname(sys.executable)
else:
application_path = os.path.dirname(os.path.abspath(__file__))
os.chdir(application_path)
try:
success = main()
except Exception as e:
print(f"未捕获的异常: {e}")
traceback.print_exc()
success = False
# 显示完成消息
print("\n" + "="*50)
if success:
print("绘图成功完成!")
print(f"输出图像保存在: C:/pyMet/figures/")
else:
print("绘图过程中发生错误!")
print("请查看日志文件获取详细信息:")
print(f"C:/pyMet/logs/ 目录下最新文件")
print("="*50)
input("按回车键退出...")
```
### 关键修复说明:
1. **地图数据获取修复**:
```python
def get_map_data(mapurl, local_path):
# 方法1:直接下载(兼容最新版geopandas)
# 方法2:从本地缓存读取
# 方法3:使用requests下载原始JSON
# 方法4:内置的山西省边界(备用方案)
```
- 提供4种不同方法获取地图数据
- 自动回退到备用方案确保脚本继续运行
- 避免使用有问题的 `fiona.drvsupport` 方法
2. **增强的地图可视化**:
```python
# 添加主要城市
cities = {
"大同": (113.3, 40.1),
"朔州": (112.4, 39.3),
# ... 其他城市
}
```
- 添加山西省主要城市标记
- 改进颜色映射和标签
- 优化布局和字体设置
3. **健壮的错误处理**:
- 每个关键步骤都有详细的日志记录
- 提供多种备选方案应对不同失败场景
- 清晰的用户反馈信息
4. **打包友好设计**:
```python
if getattr(sys, 'frozen', False):
application_path = os.path.dirname(sys.executable)
else:
application_path = os.path.dirname(os.path.abspath(__file__))
os.chdir(application_path)
```
- 支持打包成可执行文件
- 正确处理工作目录
- 相对路径支持
### 使用说明:
1. **首次运行**:
- 脚本会自动下载山西省地图数据并保存到 `C:/pyMet/data/shanxi_map.geojson`
- 生成两种不同风格的高程图
2. **后续运行**:
- 使用本地缓存的地图数据,无需下载
- 如果地图数据更新,删除本地缓存文件即可重新下载
3. **输出结果**:
- `shanxi1.png`: 基础高程图
- `shanxi2.png`: 进阶高程图(包含城市标记和更多细节)
### 依赖管理解决方案:
如果仍然遇到依赖问题,可以使用以下方法之一:
#### 方法1:使用 conda 创建虚拟环境
```bash
conda create -n shanxi python=3.10
conda activate shanxi
conda install -c conda-forge geopandas rioxarray cartopy matplotlib numpy
```
#### 方法2:使用 Docker 容器
```dockerfile
# Dockerfile
FROM python:3.10-slim-bullseye
# 安装系统依赖
RUN apt-get update && apt-get install -y \
libgdal-dev \
libspatialindex-dev \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# 设置工作目录
WORKDIR /app
# 复制脚本
COPY shanxi_map_fixed.py .
# 安装Python依赖
RUN pip install --no-cache-dir \
geopandas==0.12.2 \
rioxarray==0.15.1 \
cartopy==0.21.1 \
matplotlib==3.7.1 \
numpy==1.24.3
# 设置环境变量
ENV PYTHONUNBUFFERED=1
# 运行脚本
CMD ["python", "shanxi_map_fixed.py"]
```
构建和运行:
```bash
docker build -t shanxi-map .
docker run -v /c/路径/到/数据:/data -it shanxi-map
```
#### 方法3:创建独立可执行文件
使用 PyInstaller 打包脚本:
```bash
pip install pyinstaller
pyinstaller --onefile --add-data "C:/Python313/Lib/site-packages/cartopy;cartopy" shanxi_map_fixed.py
```