ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

历史台风路径SHP数据怎么用?Python+GeoPandas全流程解析

历史台风路径SHP数据怎么用?Python+GeoPandas全流程解析

简介:1950-2018年中国沿海台风历史数据以Shapefile格式打包提供,适合地理信息、气象研究与灾害风险管理从业者使用,可用于分析台风登陆位置、路径轨迹、频率与强度变化。压缩包共10个文件,包含.shp矢量数据、.dbf属性表、.shx索引、.prj投影信息及.xml元数据等完整要素,大小约1005KB,可直接在ArcGIS、QGIS中打开并叠加底图分析。资源内含TyphoonPoint与TyphoonLine两个图层,分别对应台风登陆点和移动路径,便于研究者开展频率统计、强度分级、登陆地点分布、路径特征等专项分析;结合时间与空间维度,还可进一步用于风暴潮模拟和防灾规划研究。目前已有1782人学习下载,对于需要历史台风数据的科研人员而言,是一份结构完整、可直接使用的数据基础。

1. 打开这个压缩包前,先想清楚要拿这六十多年的台风路径SHP做什么

拿到「1950-2018中国台风SHP数据.rar」这个压缩包的人,多半和我当初一样:需要历史台风路径来做点正经事,而不是只画一张好看的轨迹图。这个SHP数据集覆盖了西北太平洋和中国近海将近七十年、上千条台风路径,每条路径包含观测时间、中心位置、中心气压和最大风速等关键属性。可以拿它做登陆台风频次统计、风暴潮灾害评估、城市内涝风险区划,甚至用来验证气候变暖背景下台风路径是否存在北移趋势。但我要先说一个反直觉的结论:这份数据不是解压就能直接用的,早期年份的路径稀疏程度、字段编码混乱、坐标系统一问题,都会在第一步拦住你。适合读这篇文章的是GIS工程师、做气象数据分析和防灾规划的研究人员,以及需要给项目配历史台风底图的开发者——我会把读取、清洗、统计和出图的路子完整走一遍,并告诉你坑在哪。

2. 先弄明白这套SHP的底细:文件家族、坐标系与字段命名

2.1 SHP不是单个文件,而是一个文件家族

SHP是ESRI Shapefile的缩写,从业二十年的人习惯把它叫作“一个shapefile”,但严格来说它是多个文件的集合。解压rar后,你会看到至少四类文件:.shp存放几何坐标、.shx存放几何索引、.dbf存放属性表、.prj存放坐标系定义。缺了任何一个,数据都可能打不开或坐标错乱。很多人拿到压缩包后只拖.shp进软件,其余文件不管,这是第一处隐患。.dbf被拖拽时如果和.shp不同名或缺失,属性表会直接显示为空,台风编号、风速这些字段全部读不出来。

另外一个常被忽略的是.cpg文件,它记录了属性表的字符编码。早年国内发布的SHP,dbf里的中文大多用GBK或CP936编码,如果没有.cpg,QGIS会默认按UTF-8解码,台风名称变成乱码就是从这里来的。我拿到任何历史SHP,第一步永远是打开文件目录确认四个基础文件都在,再用文本编辑器瞄一眼.cpg内容,这能省下后面一大半排查时间。

2.2 坐标系:直接叠底图偏到海里的原因在这里

这套台风SHP大概率是WGS84经纬度坐标系(也就是EPSG:4326),经纬度单位是度,适合全球范围显示,不适合做距离和面积计算。如果你要叠加本地政务底图或城规坐标系的图层,不做投影转换就会出现几百米的系统性偏移,看起来像台风路径整体漂到了海面上。判断方法很简单:在QGIS右下角看坐标显示,如果光标移动时数值在小数点后六位变化,那就是经纬度;如果显示的是几百万米的数值,那就是投影坐标。更靠谱的做法是用GeoPandas读一下.crs属性,输出<Geographic 2D CRS: EPSG:4326>就是WGS84。

做投影转换时要记住一个原则:做密度分析和距离计算必须用投影坐标系,不能直接用经纬度。我一般会把数据转成EPSG:3857做全球尺度的显示,或者根据研究区域转成CGCS2000 / 高斯-克吕格投影做局部计算。EPSG:3857会导致高纬度面积失真,所以如果只算中国近海的风场影响范围,转成阿尔伯斯等积投影更合适。

2.3 字段命名:每个来源的SHP都有自己的一套缩写

不同机构发布的台风SHP,字段命名差异极大。常见的字段包括台风编号、观测时间、中心经度、中心纬度、中心气压、最大风速,但名字可能是Number、TYID、TC_NO、FX_LON、LON、JD、PRES、P、VMAX、WS。早年SHP属性表的dbf格式对字段名长度有10字符限制,所以字段名都是一堆缩写,猜含义全靠经验。这里我给一张常见字段对照表,帮你少走弯路:

字段含义常见命名字段类型
台风编号Number / TYID / TC_NO整数或字符串
观测时间Time / Date / OBS_TIME日期或字符串
中心经度LON / FX_LON / JD浮点
中心纬度LAT / FX_LAT / WD浮点
中心气压PRES / P / CORE_P浮点
最大风速VMAX / WS / WIND浮点
台风名称NAME / TY_NAME / CNAME字符串

这里要特别提醒:Time字段在不同版本的SHP里可能是日期类型,也可能是字符串“195008310600”这样的数字串,后面做时间过滤时这一步会卡住很多人。我的习惯是读取数据后先执行一行columns和dtypes打印,把字段类型摸清楚再写业务逻辑,绝不凭经验猜。字段名不同的问题会在第5章展开,这里先记住一个概念:拿到数据先做字段摸底,而不是直接画图。

3. 用GeoPandas把台风路径SHP读进内存:环境、代码与首次验证

3.1 环境准备:GeoPandas安装与rar解压

读取SHP最顺手的方式是用Python的GeoPandas库,它是Pandas和Shapely的封装,对矢量数据的读写、坐标转换和空间操作都做了高度简化。装GeoPandas建议用conda,直接从源码安装常被GDAL版本问题卡住:

conda create -n typhoon python=3.10 conda activate typhoon conda install -c conda-forge geopandas matplotlib

这个命令会同时装好GDAL、Fiona、Shapely等依赖,避免单独安装时版本冲突。关于rar压缩包,这里必须说清:GeoPandas的read_file支持直接读取zip压缩包里的shp,但不支持rar。所以需要先用系统工具解压,Linux和macOS可以用unar命令行:

unar 1950-2018中国台风SHP数据.rar # 或者 unrar x 1950-2018中国台风SHP数据.rar ./typhoon_data/

参数说明:unar对中文文件名支持更好,乱码概率低;unrar需要单独安装,但进度显示更清晰。解压完成后,确认目录下有.shp、.shx、.dbf、.prj四个文件同名同目录,再进入下一步。

3.2 读取与首轮验证:用代码检查数据能不能用

读取代码本身很简单,但验证逻辑才是关键:

import geopandas as gpd # 读取SHP,encoding参数按dbf实际编码调整 gdf = gpd.read_file( "./typhoon_data/Typhoon_1950_2018.shp", encoding="utf-8" ) # 首轮验证:看一眼数据规模、字段、坐标系、几何类型 print("数据行数:", gdf.shape[0]) print("字段列表:", gdf.columns.tolist()) print("坐标系:", gdf.crs) print("几何类型:", gdf.geometry.geom_type.unique()) # 打印前5行,检查字段内容是否正常 print(gdf.head())

逻辑说明:read_file会把SHP内的Geometry和dbf属性表一并读入。encoding参数是排查乱码的关键,如果这句代码保留utf-8读出来中文名是乱码,就改成encoding="gbk"再读。打印几何类型的目的是确认数据是点还是线——台风SHP常见两种形态:逐六小时观测点组成的点数据,和按时间顺序连成的线数据。如果是点数据,后面要做路径线需要自己按台风编号和时间排序连成LineString,这一步很多人一开始没意识到。

3.3 不写代码也能验数据:把矢量拖进QGIS看看

如果只是想知道数据行不行,不一定要写Python。QGIS直接拖.shp进图层窗口就能显示。我更推荐打开属性表,盯着三个地方看:时间字段的值到底是“1950-08-31 06:00”还是“1950083106”;经纬度字段名分别是什么;台风名称有没有乱码。QGIS属性表右上角的编码切换按钮可以快速换编码验证,不用重启软件。这个环节的产出是一张“字段清单+编码类型+时间格式”的检查表,后面写脚本全按这个表来。

检查完格式,顺手在QGIS里按年份做一次符号化分层设色,看不同年代的台风路径颜色分布。如果某几个年代的颜色集中在狭窄海域,说明那段时期观测能力有限,后面做统计分析时要分阶段处理,不能直接拉一条全时段趋势线。

4. 从原始线段到能用的统计结果:合并、过滤与密度图

4.1 合并线段:把同一场台风的所有分段合成一条完整路径

SHP里的台风路径通常不是一整条LineString,而是按观测点逐段记录,一场台风几十条线段是常态。直接统计路径数量会把一场台风数成几十场。合并线段的思路是按台风编号做Dissolve:

# 按台风编号合并,台风编号字段以实际数据为准 typhoon_lines = gdf.dissolve( by="TYID", as_index=False ) # 计算每条路径的总长度(单位:米,因为做了投影转换) typhoon_lines_m = typhoon_lines.to_crs(epsg=3857) typhoon_lines_m["length_km"] = typhoon_lines_m.geometry.length / 1000 # 按长度排序,看最大的几个台风编号 print(typhoon_lines_m[["TYID", "length_km"]].sort_values("length_km", ascending=False).head(10))

参数说明:dissolve的by参数指定台风编号列,合并后的属性表只保留用于聚合的字段和几何信息,所以想保留风速、编号等信息要在合并前想好策略——要么合并后再关联,要么把关键字段放进聚合函数里。这里我先把数据转成EPSG:3857再算长度,是因为EPSG:4326下geometry.length算出的是度,不是米。长度排序能帮你快速发现异常:如果某条路径长度超过一万公里,多半是数据拼接错误或跨日期界线导致的点位错乱。

4.2 过滤出登陆与近海台风:用经纬度范围做空间筛选

研究台风对陆地的影响时,不需要分析所有生成于远海的台风,只保留进入中国近海或登陆的路径即可。常见做法是用经纬度范围做边界框过滤:

# 用边界框过滤:覆盖华南、华东及南海方向的关键区域 bbox = (105, 5, 135, 45) # minx, miny, maxx, maxy nearby = typhoon_lines_m.cx[bbox[0]:bbox[2], bbox[1]:bbox[3]] # 从时间字段提取年份,统计每年进入近海的台风数量 nearby["year"] = nearby["Time"].astype(str).str[:4].astype(int) yearly_count = nearby.groupby("year").size().reset_index(name="count") # 输出最近10年的统计,检查数据是否完整 print(yearly_count.tail(10))

参数说明:cx方法是GeoPandas基于边界框的裁剪,比clip快很多,适合粗筛。这里的bbox坐标按照西北太平洋台风影响中国的主要路径确定——北边到45度覆盖东北方向影响路径,南边到5度覆盖南海生成区域,东边到135度覆盖西北太平洋。时间字段如果已经是日期类型,直接用.dt.year提取年份;如果是字符串,先用str切片取前4位再转整数,这是最稳妥的做法。

4.3 路径密度图:不做核密度分析,就看不出台风走廊在哪

只看路径线条,密集区域会被后来的线条完全遮挡,视觉上很难判断哪里是台风走廊。QGIS里自带Heatmap工具,它是核密度估计的实现,操作路径为:图层 → 创建栅格 → 热力图。参数设置上有两个关键:半径(Bandwidth)和像素大小(Pixel size)。对台风路径这种覆盖几百公里的对象,半径设500公里、像素大小设5公里比较合适。半径太短,栅格会变得星星点点,看不出走廊效果;半径太长,整个近海都被糊成一个大色斑,失去分析意义。

如果想在Python里复现这个效果,可以用scipy或kdeplot,但QGIS的Heatmap更直观,参数调试即时反馈。输出栅格后,用色带从蓝到红渲染,你会看到一条从菲律宾方向转向华东沿海的高密度带,以及南海方向的次级密集区。这张图配合4.2的年度频次统计,基本就能支撑一个区域台风灾害风险评估的前期论证了。

5. 台风SHP数据避坑:六个最常见翻车现场与处理办法

5.1 早期年份路径稀疏到做不了逐年统计

现象:按年度统计时发现1950年代每年只有十几个台风记录,到了2010年代每年二十多个,前后差距大到让人怀疑数据缺失。 原因:那个年代没有气象卫星,远海台风只能靠船舶和岛屿观测站发现,大量未登陆的远海台风根本没有被记录。这和数据损坏无关,是观测能力的时代限制。 解决:做统计分析时把数据明确分成两段:1950-1979年只能做登陆台风的频次分析,1980年之后才适合做全路径和生成数量的研究。如果分析的是登陆频次这种受观测能力影响较小的指标,全时段可用,但要注明早期年份存在低估。

5.2 属性表中文乱码,台风名称变成一堆符号

现象:读入GeoPandas后,台风名称字段显示为“鈥欌€”或“锟斤拷”这类乱码。 原因:dbf文件用了GBK编码,GeoPandas默认按UTF-8解码。部分SHP有.cpg文件标注编码,但没有的话全靠猜。 解决:用encoding="gbk"重新读取。如果一半正常一半乱码,说明来源数据本身编码就混了,这种情况下只能把正常字段保留、乱码字段弃用,别指望统一转码救回来。我的习惯是读完第一时间打印前五行名称字段,乱码问题半小时内解决,拖到后面全是玄学。

5.3 prj文件缺失导致坐标系未知

现象:SHP加载进软件后能显示,但叠加本地底图位置明显偏移,经纬度读数不对。 原因:压缩包里只有.shp、.shx、.dbf,缺了.prj。没有.prj,软件无法获知坐标系,默认按无投影处理,偏移不可避免。 解决:用GeoPandas检查gdf.crs,输出None就说明真的缺。此时不要瞎指定坐标系——如果能从数据文档或字段分布判断坐标范围(经度在100-135之间、纬度在5-45之间),可尝试指定EPSG:4326,但必须用已知台风点位做交叉验证。验证方法:找一次近年知名登陆台风,把路径的关键转折点与公开路径资料对一下,偏差在0.5度内算干净。偏差大就直接放弃这份数据,别硬修。

5.4 同一场台风被拆成几十段,统计数量翻了十倍

现象:按台风编号统计时,一场台风对应几十条记录,画图时每条路径颜色不同,视觉上像是几十条轨迹挤在一起。 原因:SHP存储的就是分段线,每条观测记录之间的线段独立存在,几何上被切断了。 解决:参照4.1用dissolve按台风编号合并。合并前先确认编号字段没有空值——空值会生成一条“无编号路径”,统计时容易混进结果。如果一场台风的路径在空间上首尾相接,dissolve后是一条完整线;如果中间有断档,说明原始观测点位缺失,这种场次建议在分析时剔除。

5.5 年份字段是字符串,怎么过滤都不对

现象:做时间过滤时写year > 2000报错,或者排序结果完全混乱,2000年排到了1950年前面。 原因:dbf字段类型定义为文本型,存的年份值是“2000”而不是整数2000。字符串排序按字典序,“1950”确实排在“2000”前面,但“200”这种值也会混进来捣乱。 解决:读取数据后先执行print(gdf.dtypes)定位字段类型,再用astype转换。转换前要处理空值,否则int()直接报错:

gdf["year"] = pd.to_numeric(gdf["year"], errors="coerce").fillna(0).astype(int)

参数说明:errors="coerce"把无法转换的内容变成NaN,fillna(0)兜底,这样至少保证过滤时不崩。转换后重新打印dtypes确认,再做过滤逻辑。

5.6 换了数据分片字段全变,脚本直接跑不通

现象:换一个年份分区或另一来源的台风SHP,原先脚本里写的LON字段不存在了,报KeyError。 原因:不同发布者字段命名不一致,LON、FX_LON、JD、LONGITUDE都是经度的意思,但没统一标准。 解决:写一个字段兼容别名函数,在脚本开头自动匹配:

def find_col(df, candidates): for col in candidates: if col in df.columns: return col raise KeyError(f"None of {candidates} found") lon_col = find_col(gdf, ["LON", "FX_LON", "JD", "LONGITUDE"]) lat_col = find_col(gdf, ["LAT", "FX_LAT", "WD", "LATITUDE"]) year_col = find_col(gdf, ["year", "YEAR", "Time", "DATE"])

这段代码的核心思路是把字段名当作参数而不是写死。兼容函数跑一遍能自动识别当前数据源用什么名字,后续所有逻辑都基于变量操作,换数据源时不用改主逻辑。

6. 让六十九年的路径数据开口说话:逐年趋势与路径北移验证

数据清洗完毕,最后这一步是验证这个方向值不值得投入。第一步做逐年登陆频次曲线,直接回答“这些年台风活动是多了还是少了”:

import matplotlib.pyplot as plt # 逐年近海台风数量 plot_data = yearly_count.tail(30) # 取1989-2018这30年 plt.figure(figsize=(12, 5)) plt.bar(plot_data["year"], plot_data["count"], color="#2c7fb8", width=0.8) plt.xlabel("Year") plt.ylabel("Typhoon Count") plt.title("Typhoons Entering the Coastal Area (1989-2018)") plt.grid(axis="y", alpha=0.3) plt.show()

第二步做路径带对比,这是我最想推荐你上手的进阶验证。把数据按年代分两个图层:1960-1979和2000-2018,画在同一张图上,观察路径带是否整体北移。更定量一点的做法是计算每条台风路径的平均纬度——把线几何的质心纬度取出来,按年份做线性回归:

# 计算每条路径的质心纬度 typhoon_lines_m["centroid_lat"] = typhoon_lines_m.geometry.centroid.y # 注意:此时坐标系是EPSG:3857,y值不是纬度,需要先转回经纬度 typhoon_lines_wgs = typhoon_lines_m.to_crs(epsg=4326) typhoon_lines_wgs["centroid_lat"] = typhoon_lines_wgs.geometry.centroid.y

这个指标虽然粗糙,但能直观反映路径分布的重心移动。如果回归斜率明显为正,就说明存在北移迹象,值得用更严谨的再分析数据做深入验证。我自己的习惯是拿到这类SHP先做三件事:查prj文件、确认dbf编码、打印几何类型,这三步做完,数据能不能用就心里有数了。做历史台风分析的这几个月里,这套流程帮我少踩了无数个坑,希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进