news 2026/10/22 10:05:02

近一年地震数据可视化实战:从下载清洗到出图全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
近一年地震数据可视化实战:从下载清洗到出图全流程

市面上讲地震数据做可视化的文章不少,但大多是拿一份“多年前的全量历史数据”跑完就结束了。这次我拿到的是 CnOpenData 上面「中国历史地震表-当前时间1年内」这个数据集的下载入口,也就是说,它只包含最近一年内记录到的地震目录。如果你正在做区域风险分析、App 里的灾害速报模块、或者只是想给数据可视化作品添一个“近一年震情”图层,这份数据比全量历史表好用得多——但又没那么简单,因为“当前时间1年”本身是个滚动窗口,表结构、更新节奏、字段口径都藏着一些坑。这篇文章我会按我的实操顺序,把这套数据从下载、清洗到落库、出图说清楚。

先说下我的项目场景:某公司的内部决策支持系统里需要一张“近12个月国内地震活动”热力底图,用于应急资源调度的参考展示。数据来源锁定了 CnOpenData 的这份近一年表,而不是历史全量表。原因很直接:全量表动辄几十万条,推送、存储、存到前端渲染都不划算;近一年表轻量,且正好覆盖决策关心的时效区间。如果你是做 GIS 的、做数据采集的、或者只是偶尔需要近一年地震目录做统计的学生,这篇都可以当成一份完整的上手指南。

1. 为什么“近一年”这份数据比全量历史表更值得用

1.1 数据集定位:滚动更新的轻量子集

CnOpenData 的数据集一般分两种:一种是“全量历史”,另一种是“时间段切片”。“中国历史地震表-当前时间1年内”这个数据集属于后者。它的定位不是让你研究多年尺度的地震频度变化,而是给你一份“此时此刻往前数365天发生过什么”的快照。

我最初也犹豫过要不要直接用全量表自己筛近一年,后来发现没必要。全量表字段更复杂,包含很多历史测定修正版本,你筛的时候还要处理“同一次地震在不同时间轴上的修订记录”,非常啰嗦。而这份近一年表是平台整理好的轻量目录,基本保留了原始台网目录的字段精度,同时砍掉了大量历史修正数据,读进来干净很多。

1.2 典型使用场景:从“看热闹”到“做决策”

近一年地震表拿来能做什么?我实际操作中验证过三类场景:

  • 地图可视化:用经纬度画散点,点的大小映射震级,颜色映射深度。这类表按月更新时,可以做成“月度/季度地震活动回顾”。
  • 区域差异分析:按省级或网格聚合出震级均值、频次,对比近一年和上年同期,识别“地震活动偏多的区域”。
  • 应急值班参考:配合行政区划图层,筛选指定半径内的历史事件,辅助资源投放。

这里面有一个和全量表最大的差别:近一年表的时效性天然适合“滚动报告”。比如我做值班日报,只需要今天增量拉取,跟昨天的表 diff 一下,就能发现新增了哪些事件,而不需要每次都去全量表里做耗时过滤。

1.3 装卸成本低,适合快速验证想法

近一年表即使包含余震密集序列,也就几千行到一两万行。这个量级用 pandas 处理速度极快,不需要上任何分布式框架。正因如此,你在拿到数据后可以先做小规模探索性分析:字段是否齐全、时间是否连续、震级分布是否合理。等确认口径没问题,再把它接进正式的数仓管线,这是我觉得最稳妥的推进方式。

2. 下载前必须先搞懂的字段结构与口径问题

2.1 核心字段逐个拆解

这份表虽然叫“历史地震表”,但字段并没有复杂到劝退。常见列大致如下:

字段名示例值说明
发震时刻2024-05-12 01:20:33事件发生时间,精确到秒
纬度33.21震中纬度,单位度,北为正
经度103.48震中经度,单位度,东为正
深度11震源深度,单位公里
震级3.8事件震级
参考位置某市某县台网定位后给出的中文位置描述
来源国家台网数据来源标识,有的数据集会写“ISC”“USGS”等

不同批次的导出字段名可能略有差异,有的叫“EventTime”,有的叫“发震时刻”。我的习惯是下载之后先df.columns.tolist()打印列名,确认后统一重命名,不要直接按记忆里的列名索引。

2.2 时间字段的第一大坑:时区归属

地震目录的时间几乎都使用北京时间,也就是东八区标准时间。这本身没问题,问题在于如果你的下游系统默认使用协调世界时或本地服务器时间,直接存进数据库就会整体偏移8小时。

我一般在清洗时把发震时刻全部转成带时区的datetime64[ns, Asia/Shanghai]类型,再由下游程序自行决定是否转换,不在源数据里做隐式处理。这样既保留了原始语义,也方便和国际数据集做时间对齐。

2.3 震级标度:别把不同标度混在一起算

这是我在拿到数据后最先发现的坑。近一年表里震级基本是国内台网发报的震级,常见的有面波震级、近震震级、矩震级。不同标度数值上天然就有差异,例如一个中等地震,近震震级可能标成 4.5,矩震级可能标成 4.2。如果你直接把这些数混合统计,均值和中位数都会失真。

实操上我做了两件事:

  • 先看字段说明里是否有“震级标度”列。如果有,按标度分类建三个子集分别统计。
  • 如果表里只有震级数值没有标度,那就默认按“目录统一震级”处理,只用于相对比较,不跨表跟其他标度体系做精确数值对比。

另外,数据中可能存在个别负值或 0 值。例如微震有时记成 ML0.5,甚至出现“-0.3”。遇到这种值不要当异常删,先查原始速报里是不是真的有记录,再决定是否保留。

2.4 参考位置字段:能做粗粒度匹配,别做精准定位

“参考位置”看着是中文地名,但通常是台网根据地理信息粗略判定的一句话描述,比如“某县某乡镇”。直接做文本匹配行政区划会出现两个问题:一是景物名称不对应,二是靠近县域边界的震中位置描述和实际行政区可能不符。

我的做法是用经纬度做空间连接,将震中落到省级和县级行政区多边形里。如果项目里没有行政区划数据,退而求其次才用参考位置的文本前缀做粗筛。

3. 从下载数据到一张“能直接用”的清洗表:完整流程

3.1 第一步:读入、统一列名、处理空值

拿到 Excel 或 CSV 后,先做最小化清洗。以下代码,我以 pandas 为例:

import pandas as pd # 读取原始文件,文件名为示例 df = pd.read_csv("China_Earthquake_Last1Y.csv", encoding="utf-8-sig") # 统一列名 col_map = { "发震时刻": "event_time", "纬度": "lat", "经度": "lon", "深度": "depth_km", "震级": "magnitude", "参考位置": "location_desc", "来源": "source" } df.rename(columns=col_map, inplace=True) # 删除完全空行 df.dropna(how="all", inplace=True) # 打印数据形状和列名 print(df.shape) print(df.columns.tolist())

这里有个细节:编码统一用utf-8-sig,因为很多从平台下载的 CSV 带 BOM 头,直接utf-8读会把第一列列名读出\ufeff前缀,后续引用列名总报错。看起来是小事,实际很烦人。

3.2 第二步:时间切片,精确用“今天往前 365 天”

这一步是整篇文章我最想强调的,也是看起来简单、出错率最高的一步。所谓“当前时间1年内”,指的是数据平台生成这份数据集时设定的起止区间。你在下载后的任意一天使用它,都要自己再切一次,因为“当前时间”在你手里才是真正动态的。

import datetime today = datetime.date.today() start_date = today - datetime.timedelta(days=365) df["event_time"] = pd.to_datetime(df["event_time"]) mask = (df["event_time"].dt.date >= start_date) & (df["event_time"].dt.date <= today) df = df[mask].copy()

注意,这里我用的是dt.date而不是直接比较字符串。直接用字符串比较时,如果原始时间格式不统一,很容易漏数据。转成datetime64再做范围过滤是最稳妥的。

3.3 第三步:去重与同震序列识别

下载来的表偶尔会有重复记录,产生原因主要是震相修订后重新发报,导致同一事件出现两行。我的去重逻辑是这样的:

df = df.sort_values("event_time").reset_index(drop=True) df["time_round_10s"] = df["event_time"].dt.round("10s") df["dup_key"] = ( df["time_round_10s"].astype(str) + "_" + df["lat"].round(2).astype(str) + "_" + df["lon"].round(2).astype(str) ) df = df.drop_duplicates(subset=["dup_key"], keep="last").reset_index(drop=True)

同一地震的多个台网事件,发震时刻和经纬度会在一定精度内相同。用 10 秒 + 0.01 度做组合键,去重效果非常理想。keep="last"是保留最新修订版本,如果原始表里对同一事件存在多行,说明最后一行多半是修订后的结果。

这里要特别说明:余震序列和震群事件在时间上高度聚集,经纬度也可能很接近,但发震时刻通常相隔几十秒到几分钟,因此 10 秒级的组合键不会误把它们合并。如果你的下游需要保留“震群内部独立事件”,这个参数就别再往大了调。

3.4 第四步:经纬度粗检与陆地海洋标记

拿到坐标后,第一件事是检查有没有明显异常值。经纬度合法范围是纬度负 90 到正 90、经度负 180 到正 180。超出范围的直接剔除,比如因为字段错位导致经度跑到 120 的情况。

另外,我还会加一个“大陆/近海”标记。这不是分析必需,但可视化时很管用——同时画陆地和海洋地震时,图例里可以按大类分开展示。

# 示例:用简单规则粗标,不依赖外部 geojson def region_tag(row): if row["depth_km"] > 70: return "deep" elif row["magnitude"] >= 4.5: return "major" else: return "common" df["event_tag"] = df.apply(region_tag, axis=1)

别小看这个标签,它可以帮你快速排除“深度缺失被填成 0”而画到地表的事件。

3.5 第五步:导出清洗结果

清洗完的 DataFrame 建议导出两种格式:

  • 一份 CSV,保留全字段,用于临时查询和分析。
  • 一份 GeoJSON,供地图前端直接加载。如果你装了geopandas,转换非常方便。
df.to_csv("earthquake_clean_last365d.csv", index=False, encoding="utf-8-sig") import json features = [] for _, r in df.iterrows(): features.append({ "type": "Feature", "geometry": { "type": "Point", "coordinates": [r["lon"], r["lat"]], }, "properties": { "time": r["event_time"].strftime("%Y-%m-%d %H:%M:%S"), "mag": r["magnitude"], "depth": r["depth_km"], "place": r["location_desc"], }, }) geojson = {"type": "FeatureCollection", "features": features} with open("earthquake_clean_last365d.geojson", "w", encoding="utf-8") as f: json.dump(geojson, f, ensure_ascii=False)

导出 GeoJSON 时一定要加ensure_ascii=False,否则中文参考位置会变成\uXXXX转义,你在前端地图上看到的就是一堆编码串,排查起来很头疼。

4. 拿到数据后,我做的三件事:出图、对比、生成速报表

4.1 近一年地震分布图:散点图上的第一印象

清洗完数据后,第一件事永远是看分布。最基础的做法是画一张简易散点图,横轴经度、纵轴纬度、点大小映射震级。

import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(10, 8)) scatter = ax.scatter( df["lon"], df["lat"], s=df["magnitude"] ** 3, c=df["depth_km"], cmap="YlOrRd", alpha=0.6, edgecolors="none", ) plt.colorbar(scatter, label="Depth (km)") ax.set_xlabel("Longitude") ax.set_ylabel("Latitude") ax.set_title("China Earthquake Events - Last 365 Days") plt.tight_layout() plt.savefig("eq_density_last365d.png", dpi=150) plt.show()

震级映射到点大小时用三次方,是为了让 3 级和 5 级在图上拉开肉眼差距。因为散点面积跟半径平方相关,直接映射半径会造成小震级点完全看不清,映射到体积即三次方关系,视觉差异更接近真实能量差异。

如果你有行政区划边界数据,可以用geopandas叠加到图上。没有的话,用经纬度散点先看个大概也足够。

4.2 与去年同期对比:判断“是不是多了”

单看绝对数量没有意义。某区域一年500次,可能是常态;另一区域一年20次,也可能异常偏多。我习惯把近一年的数据按月度汇总,再跟更早的同期数据做对比。

假设你的下载表只有近一年数据,那同比需要的上一年数据必须从平台的全量表取。我的做法是:全量表筛选“今天往前 730 天到往前 365 天”作为去年同期,和近一年表做同字段处理,然后逐月对比。

对比时可以看三个指标:

  • 月度事件总数。
  • 月均最大震级,也就是每月范围内的最高震级。
  • 震级大于等于 3 的事件数占比。

这三个指标能快速暴露“某个月是不是突然多了几次中小地震”。例如某区域今年3月有 8 次 3 级以上地震,去年3月只有 2 次,这种异常在纯总数对比里是不容易看出来的,但分震级段后一目了然。

4.3 生成一张值班用的“近一年较大地震速报表”

做应急相关的东西,老板和同事通常不关心几十行小震,他们只想知道“最近有哪些值得关注的”。我写了一段脚本,每天定时跑,筛选出满足任意条件的事件:

  • 当日新增事件且震级大于等于 3.0;
  • 近 7 天最大震级超过 4.0;
  • 近 30 天最大震级超过 5.0。

然后把结果渲染成一个简单的 HTML 表格,标题写上“统计周期:近一年”,点开就能看到每条事件的发震时刻、参考位置、震级、深度。

字段用表格列出来更清楚:

发震时刻参考位置震级深度(km)标签
2024-05-12 01:20:33某县某区域4.111近7日较大事件
2024-05-18 09:40:11某县某区域3.68当日新增事件

速报表不要直接发全量列表。大家的时间有限,只发“需要关注”的即可。这个小脚本上线以后,值班同学反馈比翻原始台网页高效很多。

5. 实际使用中踩过的几个坑,以及对应的避坑思路

5.1 “当前时间”是滚动窗口,不是下载完成就固定

这是我踩得最疼的一次。第一次用这份表时,我下载完后直接把它当成“截至下载日的固定历史”存了库,计划每周手动更新一次。结果第二周再查,发现某些上周还在表里的事件,这周消失了。

原因就是这份表本身就是滚动窗口。平台生成它时,内部以生成日为基准;等它更新时,早于更新日 365 天的事件自然被移出。所以如果你的下游系统需要留存历史快照,就必须定期全量拉取,并且把每次快照里的数据都合并到自己的历史库里。不要只用“最新一张表”作为长期数据源。

我的建议是:写一个定时任务,每天凌晨拉一次这张近一年表,按事件索引去重后并进独立的历史表。这样既能保证后续分析有完整序列,又能让速报表反应灵敏。

5.2 数据滞后:别拿“表里没有”当“没有发生”

平台数据的更新存在一定延迟。尤其是小震级事件,从台网测定到数据发布可能需要几个小时甚至更长。有一次我白天看到一个速报,晚上查这份表居然还没有,后来第二天才出现。

这意味着两个问题:

  • 做当日速报时,不能完全依赖这份表,要与实时接口配合。
  • 判定“近24小时无地震”要慎重,先确认数据更新时间和表内最新事件时间,再做结论。

我通常在速报表里加一列“数据截止时间”,让看表的人知道这份数据的更新时间,避免误判。

5.3 自然年和滚动年的差别

“当前时间1年内”不等于“今年1月1日以来”。好多拿到数据的人第一反应是“这是不是就是当年的地震目录”,不对。当前时间1年内是指“从今天倒推一年”,滚动区间。

如果你需要自然年的年度统计,有两种办法:

  • 如果近一年表的覆盖窗口跨了两个年份,则从中筛选出“1月1日到12月31日”的记录。
  • 更推荐直接用全量历史表,把时间范围设定为自然年开始和结束。

从数据治理的角度,年度报告不建议用滚动窗口表拼。滚动窗口表适合日常监测,不适合做年度口径。这点最好在设计数据管道时就区分开,否则年底做报告时,你会发现自己手头的数据区间是歪的。

5.4 多个事件扎堆不等于地震变多

震后余震序列会在表里形成一片“时间-震级”聚集。如果你把这份表拿去做频次趋势分析,很容易被这种序列带偏,得出“某段时间地震活动性飙升”的错误结论。

处理办法很简单:在做趋势分析前,先做“聚类事件识别”。用发震时刻差小于某阈值、空间距离小于某阈值的事件作为一个震群。聚类后,比较震群数和“独立事件数”,比直接比较事件总数科学得多。

我在实际项目里做了个简化版:按天分组,如果某天事件数显著高于整体日均值的 3 倍,就标记为“疑似震群日”,查看时不作为独立事件统计。

5.5 行政区划匹配要留意边界精度

前面提到过参考位置字段不适宜直接做精准行政区匹配。我用经纬度做空间连接时也遇到过问题:有些事件发生在行政区划边界附近,空间连接返回的县级代码和参考位置里的地名对不上。

碰到这种情况,我的经验是:以空间连接结果为准,把参考位置列保留在原始字段里供人工复核。不要试图用参考位置的文本来“纠正”空间连接,因为文本描述本身就不精确。

如果你用的是省级层面的展示,空间连接基本没有问题;但如果精确到县,请务必人工抽检边界附近的点位。

6. 一些值得留意的数据质量细节

6.1 深度为 0 的记录怎么处理

地震目录里深度为 0 有两种可能:一是某些近地表事件确实定深到 0 公里,二是人工置零表示“未能测深”。如果你拿深度做三维展示,建议把所有深度为 0 的记录单独标记,不参与深度分层配色,否则图中会出现一条“假地表线”。

一种可操作的处理:把深度 0 替换为一个默认值,例如 5 公里,并加一个“深度是否实测”的布尔列。这样既不丢失原始信息,也不干扰后续统计。

6.2 震级保留精度的问题

不同台网报告的震级精度不同,有的给一位小数,有的给两位小数。你在聚合计算时,比如算某个区域的平均震级,不必过度追求高精度,因为原始数据本身误差就在正负0.2级左右。我通常在输出报告时统一保留一位小数,避免给读者造成“精确到 0.01 级”的错觉。

6.3 数据来源和多源合并的取舍

如果你只拿 CnOpenData 这份表做单一数据源,问题不大。但如果你想把它跟国外目录合并,就必须面对事件去重问题——同一个地震在不同目录里发震时刻会差几秒,震级也可能不一样。

我的做法是:以国内台网目录为主表,国外目录作为补充,用于补充国内目录遗漏的边境附近事件。合并时使用“时间差小于 15 秒且空间距离小于 50 公里”作为匹配条件,匹配上的事件按主表优先原则保留一个。这个阈值参数需要根据你研究区域的地震活动度调整,活动性强的区域建议缩小阈值,避免把邻近的不同事件误并。

7. 关于长期维护和数据管线的一些想法

这份数据如果只是偶尔用一次,那无所谓;但如果要做月度更新、接入BI报表或值班系统,建议把整个流程自动化。我搭的管线大致分四层:

  1. 采集层:每天定时下载最新近一年表,全量覆盖到本地临时目录。
  2. 清洗层:执行时间切片、去重、异常值过滤、字段标准化,输出标准 CSV。
  3. 存储层:CSV 导入数据库分区表,按月份分区,查询提速。
  4. 展示层:前端地图和速报表从数据库读取,每日刷新。

这样做的好处是,任何一层的依赖字段变化,都不会直接影响下游展示。尤其是平台偶尔调整字段名或更新格式时,只需要改清洗层,出图和速报表脚本完全不用动。

我个人的经验,用这份“当前时间1年内”数据,最适合的定位是“态势感知”,而不是“科研级地震目录”。它帮你快速建立近一年的活动全貌,但如果要发论文或者做严谨的概率性地震危险性分析,还是应该使用官方台网经过完整修订的全量目录。明白这个边界,就不会在项目里用错数据。

最后分享一个习惯:每次拿到新一批数据,我都会先随机抽 3 个已知的较大事件,人工核对发震时刻、震级和位置。这比跑什么数据质量检查工具都管用,因为你会很快感知到这份表的更新节奏和字段稳定性。等确认个两三次之后,再放心把自动化流程挂上定时任务。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 14:55:29

PHP双写一致性实战:延迟双删、消息表与Binlog订阅方案解析

双写一致性这个问题&#xff0c;凡是做PHP后端超过两三年的&#xff0c;几乎都会撞上。尤其是项目里同时上了Redis缓存和Elasticsearch搜索之后&#xff0c;"明明写了数据库&#xff0c;缓存里却还是旧数据"这种破事&#xff0c;绝对让人头大。我最早在某电商项目里负…

作者头像 李华
网站建设 2026/10/22 11:51:34

artcraft创意工具开发指南:从技术选型到社区生态的完整路线

1. 从“artcraft”这个名字说起&#xff1a;它到底想解决什么问题第一次看到“artcraft”这个词&#xff0c;我脑子里蹦出来的第一反应是“艺术&#xff08;art&#xff09;”和“手艺/工艺&#xff08;craft&#xff09;”的拼接。这不是一个随便起的名字&#xff0c;它精准地…

作者头像 李华
网站建设 2026/10/22 16:01:36

Django入门实操:从URL路由到数据库的完整Web开发流程

想用 Python 做 Web 页面开发&#xff0c;第一个绕不开的名字就是 Django。我几年前第一次接触 Django 时&#xff0c;其实心里很没底——但认真把它的 URL 路由、视图函数和模板系统串起来之后&#xff0c;会发现页面开发的底层逻辑一下子通了。这篇文章就适合那些已经会一点 …

作者头像 李华
网站建设 2026/10/22 12:29:28

低照度图像增强代码汇总:SSR/LIME/KinD Lite生产级实现

简介&#xff1a;本资源是面向计算机视觉研究者与深度学习开发者的一站式低照度图像增强代码合集&#xff0c;聚焦解决夜间监控、弱光检测等场景中因RGB特征匮乏导致的目标识别精度低、定位困难等核心问题。资源包含Retinex、EnlightenGAN、SCI、Zero-DCE、IceNet、RRDNet、URe…

作者头像 李华
网站建设 2026/10/22 15:49:57

铝构件门头 + 铝代木构件 + 铝瓦 筑就院墅的京派门第风骨

在成都的高端墅区,京派四合院别墅以庄重的门第规制与东方气韵,成为塔尖居所的标杆。一座规制严整的垂花门头,黛瓦覆顶、朱漆饰金,既是宅院的身份封面,也是京派建筑文化的浓缩表达。然而四川盆地常年潮湿多雨、雾气氤氲,传统木制门头与屋面极易霉变虫蛀、漆面剥落,往往数年便需大…

作者头像 李华
网站建设 2026/10/18 20:12:09

8B模型LoRA微调实战:打造高性价比营销内容生成模型

简介&#xff1a;这份资源面向具备一定机器学习基础的技术人员与市场营销从业者&#xff0c;聚焦如何用大模型生成高质量训练数据&#xff0c;再微调小模型完成特定营销内容创作&#xff0c;解决预算与算力有限时难以产出优质文案的问题。包内共1个docx文档&#xff0c;约1.08M…

作者头像 李华