简介2025全国医院医疗机构兴趣点POI矢量数据是一份面向GIS分析、城市规划与公共卫生研究的空间数据集基于WGS84坐标系收录全国医院名称和精确坐标可支撑医疗资源分布评估、服务半径分析、急救路径规划、流行病学空间研究与医疗设施选址等场景。压缩包共6个文件包括.shp空间几何、.shx索引、.dbf属性表、.prj投影参数、.cpg字符编码和.xml元数据整体仅12.97MB文件结构规范便于在ArcGIS、QGIS等主流GIS软件中直接打开、检索与转换使用。该数据已有333人学习下载适合GIS从业者、规划人员和医疗科研人员作为基础底图快速开展空间分析与专题制图。借助该数据读者能直接构建全国医院分布图层进行密度制图、缓冲区分析、可达性计算等空间操作也可结合人口、交通数据做医疗资源公平性评估。同时.dbf属性表中包含医院等级、服务范围等字段可支撑商业选址、应急资源调度和政策制定等应用。1. 全国医院POI矢量数据到底是什么一份文件背后的数据工程“2025最新全国医院医疗机构POI点位矢量数据”这个标题说的不是一张简单的坐标表而是一套带有完整属性字段的医疗机构点要素集合。它把医院名称、等级、地址、科室、电话和经纬度挂在同一个矢量文件里能直接支撑就诊可达性分析、医疗资源分布评估、急救站点选址这类GIS工作。但是拿到这份数据的第一天大多数人做的第一件事是拖进QGIS直接画点画完就发现点位漂移几百米或者属性表里全是乱码——这不是数据本身不行而是坐标系、编码和去重这三道工序还没做对。这篇文章按照我从头清理一份全国医疗机构POI数据集的完整流程来写覆盖字段拆解、坐标系判断、清洗脚本、空间分析和验证手段目标只有一个让你拿到数据后少走三天的弯路。我默认你是要拿这份POI数据做真实分析的人不管你是做公共卫生课题、选址评估还是可视化大屏下面的步骤都按“可复现”的标准来。不会出现任何编造的下载链接或来源遇到拿不准的地方我会直接告诉你“这个字段常见做法是什么”以及“这里为什么最容易翻车”。2. 医院POI矢量数据拆解字段结构、坐标系与精度源头2.1 医院POI的三个来源名录整理、地图抓取、第三方聚合一份全国医院POI数据常见来源有三种来源决定了你后续清洗的工作量也决定了这份数据有多少隐藏的坑。第一种是从卫健部门公示的医疗机构名录整理而来。这种数据字段规范等级、性质、地址都写在表里但时效性通常滞后新增的诊所和分院往往要隔半年才补进来。第二种是从在线地图的POI接口抓取。这种数据点位多、更新快但坐标是加密过的GCJ-02甚至BD-09直接拿到WGS84底图上画一定偏而且字段命名随意同一个医院可能有多个别名条目。第三种是第三方聚合后转成SHP或GeoJSON的成品数据集看起来整理得很干净但实际上是把前两种来源混合洗过一遍字段层级和覆盖口径不透明最容易被“看起来专业”的外表骗过去。来源字段规范度坐标类型时效性主要风险卫健名录整理高等级/性质齐全通常WGS84滞后新增机构缺失地图POI抓取低别名多GCJ-02或BD-09较新坐标偏移、重复第三方聚合中口径不一混合不透明来源混杂、难溯源所以拿到文件后先别急着打开属性表做分析先看两点。第一文件里有没有“来源”或“update_time”字段第二随机抽三个你熟悉的医院坐标放到在线地图的卫星底图上比对。这两步能让你在开始清洗之前就判断出这份数据属于哪一类后面所有的操作策略都基于这个判断。2.2 核心字段逐个拆解名称、等级、地址、经纬度各是什么一份能直接进入分析的医院POI数据字段表大致长这样字段名含义常见脏数据name机构名称带空格、括号不统一、有别名level医院等级三甲/三乙/二甲等空缺、命名混乱“三级甲等”和“三甲”并存type机构类型综合/专科/社区/卫生院分类口径不同有“诊所”与“门诊部”混用address详细地址不完整、含冗余楼层信息lng / lat经纬度坐标GCJ-02与WGS84混用tel联系电话空缺率高province / city / district行政区划边界与最新区划不一致status运营状态已注销、停业未标记level字段是这里面最值钱也是最容易出问题的字段。很多聚合数据集里“三级甲等”和“三甲”同时存在直接分组统计时分出两个组图表做出来就是错的。我的习惯是先做一层值映射把“三级甲等”“三甲”“三甲综合”统一成“三甲”一个值再做后续统计。经纬度字段则要区分是WGS84、GCJ-02还是BD-09。判断方法很简单取一个你熟悉的地标坐标与本地地图上的实际位置比对偏移量在300米以内大概率是GCJ-02偏移量到几百米甚至一公里以上可能是BD-09基本不偏的才是WGS84。这个判断不需要工具肉眼加一个坐标换算网站就能确定。2.3 坐标系定生死WGS84、GCJ-02和BD-09怎么一眼分辨坐标系是医院POI数据里最大的分水岭。WGS84是GPS设备原始输出坐标也是绝大多数开源GIS底图和SHP文件的默认坐标系GCJ-02是火星坐标国内大部分在线地图服务采用的坐标BD-09是百度坐标在GCJ-02基础上又做了一次二次偏移。对全国数据来说判断方法有三个。第一看经纬度数值范围中国境内经度通常介于73度到135度之间纬度介于18度到53度之间超出这个范围要么是数据错误要么坐标系完全不对。第二抽一个你熟悉的地点坐标做距离比对比如你所在的医院门口和地图上你确认的位置比一下偏移量。第三看数据文件里是否附带.prj文件或坐标系说明SHP的.prj里会写明GCS_WGS_1984还是GCS_GCJ_02。最坑的一点是很多聚合数据在转换坐标系时只做了数值平移没有做投影转换导致字段说明写着WGS84实际数值却是GCJ-02。这种数据做全国分布图看不出来问题一旦放大到城市级别和路网、行政边界叠加就全部错位。所以后面的清洗流程里坐标反算是不可跳过的一步。3. 用Python把医院POI清洗到可分析状态去重、坐标转换与导出格式3.1 第一步清洗字段归一化与去重怎么写拿到原始数据后的第一件事是把字段里肉眼可见的不统一处理掉。我一般用pandas完成这一步先把level字段做值映射再按“名称 地址”做去重。这里有一个关键点直接按name去重会误杀很多真实存在的同名分院比如“XX市人民医院东院区”和“XX市人民医院西院区”名称都含“人民医院”但它们是两个不同的点位。import pandas as pd df pd.read_csv(hospital_poi_raw.csv, encodingutf-8) # 等级字段归一化 level_map { 三级甲等: 三甲, 三甲: 三甲, 三甲综合: 三甲, 三级乙等: 三乙, 三乙: 三乙, 二级甲等: 二甲, } df[level] df[level].astype(str).str.strip().map(level_map).fillna(未分级) # 名称归一化去空格、统一括号 df[name] ( df[name] .astype(str) .str.replace(r\s, , regexTrue) .str.replace(, () .str.replace(, )) ) # 去重键名称 地址前六个字 df[addr_key] df[address].astype(str).str.replace(r\s, , regexTrue).str[:6] df df.drop_duplicates(subset[name, addr_key], keepfirst) print(f去重后剩余点位: {len(df)}) df.to_csv(hospital_poi_cleaned.csv, indexFalse, encodingutf-8-sig)这段代码的逻辑是先用字典映射等级写法把“三级甲等”和“三甲”统一成同一个值然后清洗名称里的空格和中文括号最后用“名称 地址前六个字”作为去重键这样同一家医院的两个不同条目一个写“XX市人民医院”带全称一个写“市人民医院”带简称能被识别成重复而真正的东西两个院区会因为地址前六字不同被保留下来。addr_key截取地址前六个字是我试过多个方案后觉得最稳定的做法。截太短比如四个字同一个街道的不同医院容易误合并截太长比如八个字一个医院名称稍有不完整就躲过去重。六个字在城市门牌号体系下恰好能定位到街道或片区级别。如果你的数据里有区划字段也可以把province/city/district拼进addr_key准确率更高。3.2 坐标反算GCJ-02转WGS84的Python函数在笔者做过的几份全国POI数据里十份有八份的坐标是GCJ-02。这一节把坐标系转换的函数给出针对GCJ-02转WGS84的反算问题最常见的做法是先用公式计算偏移再用原始坐标减去偏移得到近似WGS84坐标。由于GCJ-02的偏移方程是已知的公开算法可以直接用代码实现。import math def gcj02_to_wgs84(lng, lat): 将 GCJ-02 坐标反算为 WGS84 坐标 精度约 1~2 米满足常规可视化与统计需求 a 6378245.0 ee 0.00669342162296594323 def _transform_lat(x, y): ret -100.0 2.0 * x 3.0 * y 0.2 * y * y 0.1 * x * y ret 0.2 * math.sqrt(abs(x)) ret (20.0 * math.sin(6.0 * x * math.pi) 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0 ret (20.0 * math.sin(y * math.pi) 40.0 * math.sin(y / 3.0 * math.pi)) * 2.0 / 3.0 ret (160.0 * math.sin(y / 12.0 * math.pi) 320.0 * math.sin(y * math.pi / 30.0)) * 2.0 / 3.0 return ret def _transform_lng(x, y): ret 300.0 x 2.0 * y 0.1 * x * x 0.1 * x * y ret (20.0 * math.sin(6.0 * x * math.pi) 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0 ret (20.0 * math.sin(x * math.pi) 40.0 * math.sin(x / 3.0 * math.pi)) * 2.0 / 3.0 ret (150.0 * math.sin(x / 12.0 * math.pi) 300.0 * math.sin(x / 30.0 * math.pi)) * 2.0 / 3.0 return ret dlat _transform_lat(lng - 105.0, lat - 35.0) dlng _transform_lng(lng - 105.0, lat - 35.0) radlat lat / 180.0 * math.pi magic math.sin(radlat) magic 1 - ee * magic * magic sqrtmagic math.sqrt(magic) dlat (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * math.pi) dlng (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * math.pi) wgs_lng lng * 2 - (lng dlng) wgs_lat lat * 2 - (lat dlat) return wgs_lng, wgs_lat这段代码的作用是完成GCJ-02到WGS84的反算核心思路是用GCJ-02的正算公式先算出偏移量dlng和dlat然后用“原始坐标乘以2减去偏移后坐标”得到近似WGS84坐标。这样做的精度一般在1到2米对POI点数据做统计和可视化完全够用但如果你的场景是精细的短距离测量比如医院与某个地标之间的精确距离建议在反算后再叠加一次人工抽样校准。参数说明a是克拉索夫斯基椭球长半径ee是偏心率平方这两个值来自GCJ-02加密算法使用的坐标系参数。_transform_lat和_transform_lng内部是一组三角级数拟合用来模拟加密偏移的规律。代码里用的是float64精度对全国几十万条POI批量处理耗时大约几分钟性能瓶颈不在计算而在IO。需要注意这个函数适用于中国大陆区域的GCJ-02坐标对港澳台区域不要把函数套用上去因为加密网格可能不覆盖那些区域。转换完成后建议顺手保存一份带坐标系字段的中间结果避免后续处理时又搞混来源。3.3 导出SHP、GeoJSON、WKT和CSV给不同下游用不同格式清洗完成后的下一站是把DataFrame转成真正的矢量文件。不同下游用不同格式SHP给QGIS和传统GIS流程用GeoJSON给Web前端和大屏用WKT给空间数据库和文本分析用CSV给Excel党和Python/pandas用户用。import geopandas as gpd from shapely.geometry import Point # 从清洗后的DataFrame构建GeoDataFrame gdf gpd.GeoDataFrame( df, geometrygpd.points_from_xy(df[lng], df[lat]), crsEPSG:4326, # WGS84 ) # 导出SHP gdf.to_file(hospital_poi.shp, encodingutf-8) # 导出GeoJSON gdf.to_file(hospital_poi.geojson, driverGeoJSON) # 导出WKT文本 wkt_series gdf.geometry.apply(lambda geom: geom.wkt) df_wkt gdf.drop(columns[geometry]).copy() df_wkt[wkt] wkt_series df_wkt.to_csv(hospital_poi_wkt.csv, indexFalse, encodingutf-8-sig)这里有两个容易翻车的点。第一SHP文件本身不支持UTF-8元数据直接用utf-8编码写出在QGIS里大概率显示正常但用ArcMap打开可能乱码。要在输出目录下额外生成一个.cpg文件写入“UTF-8”或者干脆用GeoJSON替代SHP做归档。第二导出WKT时坐标顺序是“经度 纬度”还是“纬度 经度”取决于GeoDataFrame的轴顺序shapely的.wkt默认输出“经度 纬度”但有些空间数据库的WKT规范是“纬度 经度”在前导入时一定要先确认目标库的格式约定。参数说明crsEPSG:4326明确写出坐标系统比默认的None要安全得多。to_file的encoding参数控制属性表编码utf-8-sig是带BOM的UTF-8Excel直接打开CSV时不乱码。GeoJSON的driver参数要显式声明否则geopandas会按扩展名推断。WKT导出的用途比较特殊当你要把POI数据放进PostGIS或做文本匹配分析时WKT是通用性最好的中间格式前提是保留lng/lat字段一起导出方便出错时反向核对。4. 把POI用起来缓冲区、分级符号化与可达性分析的三个落地做法4.1 按医院等级建缓冲区15分钟医疗服务圈怎么算缓冲区是医院POI分析里最常见的操作比如“15分钟医疗服务圈”就是给每个医院点位画一个半径三到五公里的圆。但这里有一个坐标系陷阱如果你直接在WGS84的经纬度坐标上调用buffer()半径单位是“度”3公里在赤道大约是0.027度但在北纬40度大约只有0.035度经度。所以正确做法是先投影到以米为单位的坐标系再计算缓冲区最后转回经纬度做可视化。# 统一转换到Web墨卡托投影以米为单位做缓冲区 gdf_proj gdf.to_crs(epsg3857) # 三甲医院缓冲区半径8公里其他医院半径3公里 gdf_proj[buffer_radius] gdf_proj[level].apply(lambda x: 8000 if x 三甲 else 3000) gdf_proj[buffer_geom] gdf_proj.geometry.buffer(gdf_proj[buffer_radius]) # 转回WGS84 buffer_gdf gpd.GeoDataFrame( gdf_proj.drop(columns[geometry]), geometrygdf_proj[buffer_geom], crsEPSG:3857, ).to_crs(epsg4326) buffer_gdf.to_file(hospital_buffer_15min.shp, encodingutf-8)这段代码先整体投影到EPSG:3857这个投影以米为单位buffer(8000)就代表8公里半径不会再出现“度”的换算问题。buffer_radius按等级设定不同半径体现三甲医院服务半径大于社区卫生院的业务逻辑。这里要说明一点EPSG:3857在高纬度地区有面积变形对全国尺度的缓冲区分析来说结果可以作为粗略参考。如果你要做严谨的覆盖人口测算更稳妥的方案是按省份分别投影到对应的UTM分带每个省用各自的投影坐标系计算完再合并回WGS84。我在实际项目中用过一个折中方案先把数据按省分组每组to_crs到所在省的CGCS2000投影带计算完再合并全国跑一遍耗时约十分钟而精度提升是肉眼可见的。4.2 分级设色可视化三甲、二级、社区卫生院一张图分开显示画点很容易画一组能讲故事的点和画一张色块图是两回事。我的习惯是按level字段做分级设色三甲用深红色大圆点二级用橙色中等圆点社区卫生院用蓝色的半透明小圆点。这样在全国视角下一眼就能看出医疗资源集中在哪些城市群。import matplotlib.pyplot as plt fig, ax plt.subplots(figsize(12, 12)) # 按等级分组绘制不同等级不同颜色和大小 level_style { 三甲: {color: #D32F2F, size: 8, alpha: 0.8}, 三乙: {color: #F57C00, size: 5, alpha: 0.7}, 二甲: {color: #FBC02D, size: 4, alpha: 0.6}, 未分级: {color: #90A4AE, size: 2, alpha: 0.4}, } for level, style in level_style.items(): subset gdf[gdf[level] level] ax.scatter( subset.geometry.x, subset.geometry.y, sstyle[size], cstyle[color], alphastyle[alpha], labellevel, ) ax.legend(title医院等级) ax.set_xlabel(经度) ax.set_ylabel(纬度) plt.tight_layout() plt.savefig(hospital_poi_level_map.png, dpi150)这个可视化脚本的作用不是做最终成图而是快速检查数据分布是否合理。如果看到大量“未分级”的灰色点散布在东部沿海说明清洗阶段的level映射没有覆盖全需要回头补值。如果三甲医院的红色点密密麻麻挤在同一个城市说明去重环节有遗漏需要回到3.1节重新处理。分级符号化的参数调整技巧s参数控制点大小全国图用2到8是合适的范围太大互相压盖太小看不见alpha透明度在0.4到0.8之间透明度太低看不出密度对比太高又盖住底图。第一遍跑完先用默认参数看分布再根据实际效果微调。4.3 叠加人口与路网从“画点”走向“算覆盖”点图层加缓冲区只是第一步真正有价值的是把医院POI数据与人口栅格、路网数据叠加计算真实的覆盖率。这一个节给出思路和关键参数不做全量代码展开因为路网分析涉及的数据源和网络分析库各人偏好不同。常见做法是先把医院POI点捕捉到路网最近节点然后用等时圈分析工具计算每个医院在车行15分钟、步行30分钟内实际能到达的范围再把等时圈与人口栅格做分区统计得到“医疗服务覆盖人口”。这里的坑是医院点的路网捕捉距离阈值——我一般设为200米超过200米的点大概率是坐标漂移而不是真的远离路网。叠加人口数据时要注意人口栅格的分辨率一般用100米或1公里的格网数据。1公里格网粗看趋势够用但要算“某个街道缺少医疗资源”这种精度至少得100米格网。医院POI和人口栅格做叠加用空间连接即可但等时圈与POI缓冲区是两个概念缓冲区是圆的等时圈是路网形态决定的两者在真实城市里差异极大。如果只是快速浏览可以用离线全球矢量数据里的道路层作为底图叠加医院点做目视检查。这一层检查能发现明显的坐标错位和点位漂移但精细的可达性结论仍需要跑路网分析光靠肉眼和缓冲区是不够的。5. 医院POI数据避坑我踩过的坐标系、编码与时效性五类坑5.1 点位漂移几百米坐标系被悄悄换掉了现象医院点位在QGIS里和卫星底图对比整体偏移300到600米方向一致。原因数据文件里的.prj写着WGS84但实际数值来自GCJ-02是数据供应商做了坐标平移但没改描述字段。解决不要急着用3.2节的函数批量转换先抽10个坐标用手工比对确认偏移模式和数值量级。确认后对全表跑一次gcj02_to_wgs84反算再抽3个点复核。注意如果数据本身是BD-09直接套GCJ-02反算公式点位会往另一个方向偏需要先调用BD-09转GCJ-02的中间函数。5.2 同一医院重复出现去重不是简单按名字DROP现象统计“某市三甲医院数量”时发现结果比官方公示多了一倍。原因同一个医院有多个条目有的来自名录整理有的来自地图POI名称略有差别比如“市第一人民医院”和“第一人民医院总院区”。按name直接去重去不掉。解决按照3.1节的思路用“名称归一化 地址前六个字”组合去重。如果还有漏网之鱼再按经纬度做一次空间去重两条记录距离小于50米且名称相似度高于80%合并为一条。5.3 DBF字段中文乱码SHP的老毛病导出前就要处理现象SHP在QGIS里打开中文正常放到ArcGIS或导入到某些平台后医院名称变成乱码。原因SHP的属性表使用DBF格式存储DBF的编码声明依赖.cpg文件。geopandas的to_file导出时如果直接写encodingutf-8部分老版本GIS软件会按系统默认的GBK解码。解决导出后在输出目录里检查有没有.cpg文件内容应为“UTF-8”。如果没有或内容不对手动创建一个同名.cpg文件写入“UTF-8”。更省事的办法是优先用GeoJSON格式做归档GeoJSON原生支持UTF-8没有这个编码问题。5.4 等级字段大面积空缺来源名录和POI抓取没对齐现象level字段空缺率超过40%而且空缺集中在社区卫生院和诊所。原因地图POI抓取的数据里很多小诊所没有等级信息名录整理的数据里则可能有等级但机构范围不同。两批数据直接拼接等级字段就出现大量空值。解决先按机构类型做推断名称包含“卫生院”的默认填“基层医疗”包含“门诊部”的填“门诊”包含“医院”但等级空白的用周边路网密度和规模做辅助判断或者直接标记“未分级”不要硬填。硬填的错误信息比空缺更有破坏性因为它会进入后续的统计。5.5 “2025最新”不等于实时update_time字段怎么看怎么用现象数据文件名叫“2025最新”但统计结果和官方公示的医院数量差了好几百。原因所谓“最新”只是数据集的发布版本时间不代表里面每个条目的采集时间。很多POI条目的原始采集时间可能是两年前的。解决拿到数据先看有没有update_time或source_time字段。有的话按月份分组统计各月更新的条目量如果发现集中在某个年月说明那是批量导入时间不代表真实时效。没有时间字段的最稳妥的做法是抽样50个机构打官网电话或查卫健委公示名录核对状态核验比例不低于5%。这一步虽然费时间但比信文件名靠谱得多。医院开诊状态变化极快新院区启用、老院区合并这类变更用POI数据根本反映不出来。6. 验证一份“2025最新”医院POI是否可信三个自查手段与一段脚本拿到数据先跑一段快速脚本验证可信度再谈做分析和可视化。这段脚本做的是空间合法性检查和字段完整率统计五分钟以内能跑完。import pandas as pd from shapely.geometry import Point from shapely.validation import explain_validity def validate_hospital_poi(gdf): # 1. 空间范围粗筛 china_bounds gdf.cx[73:135, 18:53] out_of_china len(gdf) - len(china_bounds) print(f超出中国范围的坐标点: {out_of_china}) # 2. 几何有效性检查 invalid_geoms gdf[~gdf.geometry.is_valid] print(f无效几何对象: {len(invalid_geoms)}) # 3. 字段完整率 for col in [name, level, lng, lat]: non_null_rate gdf[col].notna().sum() / len(gdf) * 100 print(f{col} 完整率: {non_null_rate:.1f}%) # 4. 重复坐标近似检查 coords list(zip(gdf.geometry.x.round(3), gdf.geometry.y.round(3))) dup_count len(coords) - len(set(coords)) print(f经纬度近似重复的点位数: {dup_count})这段脚本的four个检查项里空间范围粗筛能抓出坐标单位错误比如把度写成了米和坐标系错误几何有效性检查主要针对从外部转换来的GeoJSON个别几何对象可能变成空的或者自相交的无效多边形字段完整率看name、level、lng、lat四个关键字段level完整率低于60%就要警惕经纬度近似重复检查把坐标保留三位小数后统计重复值三位小数对应约百米精度能找出同址不同名的漏网重复条目。第二个自查手段是抽样比对。我之前踩过一次教训拿到一份全国医院数据抽查了二十个坐标点都正常就直接用于项目后来到某县做现场调研才发现当地新建的两家医院不在表里而表里列的三家老医院里有一家已经搬走了。从那之后我的习惯是任何“最新”POI数据都要抽5%的样本对照本地地图底图人工核对光看坐标范围检查不够因为坐标合法但状态过时是这类数据的通病。第三个手段是把WKT导出后做一次全量查重按“医院名称 行政区 WKT文本”三列拼接后统计重复。这一招能发现坐标上差几十米、名称完全一样的重复条目普通属性去重做不到这一点。整轮验证下来数据能不能用、能用到什么精度心里基本有底。这个过程耗不了多长时间却能在你后续所有分析结论里加一分保险。数据工程的本质就是给结论加置信度医院POI这份数据尤其如此希望这份流程对你有用。本文还有配套的精品资源点击获取