项目名就叫Python-Proj,光听名字就知道是个用Python折腾地图投影的小项目。可项目第一次跑起来,第一个拦路虎就来了:代码里明明写着CRS.from_epsg(4326),结果终端直接甩给我一句PROJ: proj_exception_create: unrecognized CRS: epsg:4326。这个报错我前前后后在五六台机器上踩过,每一回都能感觉到血压在飙升。今天把这个问题的来龙去脉和最快解决路径彻底讲一遍,该诊断的诊断,该重装的重装,保证你看完不用再瞎试。
先解释清楚这是套什么东西,免得新手一头雾水。Pyproj是PROJ地图投影库的Python绑定,凡是做坐标系转换、地图投影、地理数据分析的Python项目,底层十有八九靠它撑着,GeoPandas、Cartopy、Folium这些耳熟能详的库全都挂着它。EPSG:4326就是WGS84经纬度坐标系,是全世界地理数据最通用的基础坐标系。一个连EPSG:4326都认不出来的投影库,约等于翻译软件突然不认识hello了,手里所有坐标相关的活儿全得停摆。这篇文章适合所有用Python做地图、遥感、空间分析的人,哪怕你只是刚照着教程装完Python准备跑第一个数据可视化脚本,只要哪天撞上这个报错,照下面的路子排查,基本都能救回来。
1. 先搞清楚为什么Python项目会找不到EPSG:4326
1.1 报错到底长什么样
我先把最常见的三种现场摆出来,你对号入座。
第一种是用pyproj直接查坐标系。
from pyproj import CRS crs = CRS.from_epsg(4326)输出:
CRSError: Invalid projection: epsg:4326: (Internal Proj Error: proj_exception_create: unrecognized CRS / Request for file database failed)第二种是配合GeoPandas读数据,看起来像是在shp文件读取阶段突然崩掉。
import geopandas as gpd gdf = gpd.read_file("some_file.shp")报错尾段常常长这样:
RasterioIOError: PROJ: proj_create: unrecognized CRS第三种是用转换工具做坐标转换时翻车。
from pyproj import Transformer transformer = Transformer.from_crs("EPSG:4326", "EPSG:3857")报错核心还是那句unrecognized CRS,有时候会多一行database file not found之类的提示。
三种症状虽然表现不同,本质上是同一个问题:PROJ核心库的数据库文件proj.db缺失、损坏,或者路径没对上。记住这句话,后面所有排查思路都围着它转。看到这类报错,别急着怀疑代码逻辑,先往环境上想。
1.2 proj.db是什么,为什么它一掉链子全盘皆输
老版本的PROJ(6.0以前)把坐标系的定义直接写死在源码里,编译完成就自带全宇宙的坐标系知识,根本不存在"找不到"的可能。但从6.0开始,PROJ彻底换了设计思路,把几千个坐标参考系的定义、基准面参数、单位换算信息全部塞进一个叫proj.db的SQLite数据库文件。pyproj在运行时动态读取这个数据库,CRS.from_epsg(4326)这个操作,本质上就是拿着"4326"这个编号去数据库里查一行记录。
用生活类比来解释:旧版PROJ是随身带了一本打印好的电话簿,翻哪页都行;新版PROJ变成了一个需要读卡的设备,proj.db就是那张SIM卡。卡没插好,哪怕你背得出4326这个号码,设备也拨不出去。这解释了一个很多人困惑的现象——为什么连EPSG:4326这种最最基础的坐标系都找不到?因为问题压根不在"4326"这个数字本身,而在承载它的数据库文件没有正常工作。
实际项目里,proj.db出问题通常集中在几种情况:用pip直接安装pyproj时,数据文件没被正确放进包目录;conda和pip混用导致版本错配,conda环境里先装了pyproj,后面又用pip升级或降级,数据库版本对不上;某些精简版或镜像源安装包把数据文件剔除了;再就是迁移代码、同步环境时只拷贝了site-packages的一部分,把proj数据目录落在旧机器上。弄懂这几条,你就知道这不是代码逻辑的错误,而是环境不完整。方向要是搞错跑去逐行改代码,改到天亮也改不出来。
1.3 从报错到定位:一条快速诊断命令
在动手解决之前,先用一句话给环境做个诊断,省得瞎忙活。
python -c "import pyproj; print(pyproj.__version__)" python -c "from pyproj.datadir import get_data_dir; print(get_data_dir())"第一句看pyproj版本号,第二句看它实际去哪个目录找数据文件。如果第二个路径打印出来之后,你用ls检查目录发现里面没有proj.db,或者这个路径本身压根不存在,那问题基本实锤了。这一步花不到一分钟,却能帮你省下后面换环境、重装依赖的大量时间。
2. 三种解决方案怎么选:conda、环境变量还是版本重装
2.1 先看对比表再决定
对症下药之前,我先把能用得上的方案摆一张表,方便你按自己的环境条件对号入座。
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| conda重装pyproj、proj | 用的是conda环境,不排斥调整依赖 | 一条命令解决,数据文件自动配好 | 需要下载网络包,会改动环境 |
| 手动设置PROJ_LIB环境变量 | pip环境装好了,只是数据路径没对上 | 最快,不动依赖 | 换机器要重新配,只解决路径不解决版本 |
| pip按指定版本重装 | 项目锁死版本,或镜像源缺数据文件 | 精准控制版本 | 版本组合坑多,容易踩雷 |
我自己在实操中的选择标准很简单:能用conda就优先conda。这是因为conda把PROJ核心库、proj.db和pyproj当作一套整体来管理,能自动算好三者之间的匹配关系;pip则是各管各的,装出来有时候就缺胳膊少腿。但有时候项目限制了你没法用conda,那后面两条路就是救命稻草。
2.2 为什么conda重装是省心路线
pyproj、proj、geopandas这一族库,实际上涉及C扩展、数据文件和Python包三层依赖。conda-forge仓库里,这几样东西是打包成一套的,版本之间做过联动测试,安装时会把proj可执行程序、proj.db、pyproj的编译产物一起装好,目录结构也按约定配好。
而pip安装pyproj时,数据文件其实也会带,问题在于很多环境下pip安装过程不会帮你配好PROJ_LIB,或者因为缓存、镜像的原因导致文件不完整。conda则没有这个烦恼,它有一套约定好的目录结构,装完就能直接读。这也是为什么老手处理类似问题,第一反应都是把相关包全部统一重装一遍,而不是费劲去研究路径配置。
2.3 什么情况才选另外两条路
手动设置PROJ_LIB适合什么场景?比如你手上有个正在跑的生产服务,不想大动依赖,或者pyproj明明装着,只是当前用户的环境变量丢了路径。这时候直接指一条路过去最快,一行export的事,风险最小。
指定版本重装适合什么场景?项目锁定了pyproj版本,比如老代码只兼容pyproj 2.x,你不能顺手升到3.x,或者你怀疑镜像源给了一个残缺的安装包。这时候清缓存、精确指定版本重装,比整体换环境更安全。话虽如此,版本矩阵的坑依然存在,具体怎么躲,我在第4章里单独讲。
3. 手把手实操:从诊断到恢复的完整流程
3.1 动手前先做环境检查
不管选哪条方案,先把现场情况摸清楚。除了前面说的版本和目录检查,还要分清操作系统——Windows、Linux、macOS三者的路径习惯差得挺多,但排查思路完全一致。
Linux环境下,conda装出来的proj.db通常在/opt/conda/share/proj/这种位置,系统级pip装的通常在/usr/share/proj/或/usr/local/share/proj/。Windows环境下,常见位置是C:\Users\用户名\AppData\Local\Programs\Python\Python39\Lib\site-packages\pyproj\proj_dir\share\proj\proj.db。先确认文件在哪,后面设置PROJ_LIB才有依据。这一步省不得,因为很多"重装完还是报错"的案例,其实就是proj.db装在A路径,pyproj偏去B路径找,两边各说各话。
3.2 方案一实操:conda统一重装
进入目标conda环境,执行:
conda install -c conda-forge pyproj=3.4.2如果你用的是geopandas全家桶,干脆一起重装:
conda install -c conda-forge pyproj proj geopandas这里有个操作细节很多人不知道:如果conda提示依赖冲突,别急着加--force-reinstall强行覆盖。先检查当前环境里proj核心库是什么版本,再让conda自己解算。多数冲突都是因为proj和pyproj版本不匹配,conda会自动挑一套正确的组合覆盖掉。等它跑完,用:
conda list | grep -E "pyproj|proj"确认版本都在,就能进入验证环节。我当时就是这样把一台Ubuntu服务器上乱七八糟的环境理顺的,前后不超过五分钟。
3.3 方案二实操:手动指定PROJ_LIB
如果没法动conda,那就手动告诉PROJ数据文件在哪。先全域搜proj.db:
find / -name "proj.db" 2>/dev/nullWindows上可以用:
where /r C:\ proj.db找到目录后,临时设置环境变量:
export PROJ_LIB=/path/to/proj_data然后重新打开Python跑一次验证,成功后再把变量写进~/.bashrc永久生效。Windows的永久配置去"系统属性 -> 环境变量"里新建一个,变量名PROJ_LIB,变量值填proj.db所在目录即可。这个方案的好处是快,坏处是换一台机器就得重配一次,所以适合应急,不适合作为长期依赖。
3.4 方案三实操:pip清缓存重装
前面反复强调,pip装pyproj不等于数据文件就位。如果你是pip环境,又怀疑是缓存或残缺包的问题,清缓存重装:
pip cache purge pip install --force-reinstall --no-cache-dir pyproj==3.4.2重装完如果还是不行,再检查一次路径与proj.db是否存在。倘若文件存在但版本与pyproj要求的对不上,就需要回到版本矩阵里仔细配对。pyproj官方文档有详细的版本兼容表,别只看pyproj自己的版本号,还要确认它编译链接的PROJ核心版本。这一步最容易让人抓狂,因为报错长得一模一样,实际原因可能差着十万八千里。
3.5 验证是否恢复:一段自检脚本
环境改完,不要直接跑业务代码,先跑一段自检,把问题边界划清楚:
from pyproj import CRS, Transformer crs = CRS.from_epsg(4326) print("EPSG:4326 name:", crs.name) transformer = Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True) x, y = transformer.transform(116.4, 39.9) # 经度、纬度 print("Web墨卡托坐标:", x, y)输出正常,说明环境OK,问题不在PROJ。这步如果过不了,再回头排查环境。这段自检脚本我至今保存在常用代码片段里,每次新建环境都要跑一遍,算是排障的第一道防线。
4. 踩坑实录:常见错误与排查技巧
4.1 conda和pip混装的深水区
我遇到最多的坑,就是conda环境里混用pip装了pyproj,两边版本对不上。比如conda的proj核心库是7.2.0,然后pip装了个新版pyproj,它编译时链接的是PROJ 8.x,运行时就按8.x的数据库格式去找,跟conda自带的7.x数据文件不匹配,结果自然就是unrecognized CRS。这种情况在数据科学环境里特别常见,因为conda装数据分析全家桶,pip又单独装地图库,两边互相不知道对方的存在。
排查思路很直接:把两边版本都打出来对照。
projinfo --version python -c "import pyproj; print(pyproj.proj_version_str)"projinfo是PROJ核心库自带的命令行工具,pyproj.proj_version_str是pyproj编译时链接的PROJ版本。两个版本差太多,就该统一来源:要么全conda,要么全pip。最忌讳的就是"哪个缺了装哪个",这在PROJ的依赖体系里行不通。
4.2 一个容易忽略的坑:proj.db装上了但路径不对
有时候proj.db其实是装了的,但pyproj去另一个路径找。这种错位通常发生在多Python版本并存、虚拟环境套娃、或者用户级site-packages和系统级site-packages混用的情况下。这时候路径检查就是唯一的救命稻草。学诊断命令的价值就在这里——它直接告诉你pyproj的视线范围,你只管把文件放到它看得见的地方。
遇到这类错位,我还有一个小技巧:直接对比pyproj.datadir.get_data_dir()的输出和实际proj.db所在路径,两者不一致就说明有东西在中间改变了环境变量。可以用以下命令单独抓一下当前环境里是否已有PROJ_LIB设置:
python -c "import os; print(os.environ.get('PROJ_LIB'))"有输出的话,看看这个指向是否存在且正确,很多环境错位就是某个配置文件里残留了一个旧的路径。
4.3 常见问题速查表
| 症状 | 可能原因 | 快速处理 |
|---|---|---|
Could not find EPSG:4326 | proj.db缺失或路径不对 | 重装pyproj / 设置PROJ_LIB |
proj_exception_create: unrecognized CRS | PROJ数据库版本不匹配 | conda统一重装pyproj和proj |
| 读shp时后台报proj错误 | GeoPandas底层proj配置坏了 | conda install geopandas 整体重装 |
| 自定义坐标系找不到 | 自定义CRS未写入proj.db | 用WKT或Proj4字符串传入 |
| 换机器跑代码就挂 | 环境没同步完整 | 用conda env export导出yaml复现 |
在这个速查表之外,还有一个诊断技巧值得收藏:当报错信息里出现Request for file database failed时,基本可以停止怀疑代码,直接进入环境排障流程。这个提示是PROJ数据库请求失败的标志性信息,我看到它就知道该往哪查。
4.4 两个容易带偏的细节
第一,个别自定义坐标系找不到时,不必非得修改数据库。直接把WKT文本传给CRS.from_user_input(),或者用CRS.from_proj4()传Proj4字符串,都能绕开proj.db缺失的障碍。这个方法在数据文件缺得不多、又不想动环境的时候特别实用,尤其是项目里只有一两个自定义坐标系的情况。
提示:绝对不要手动往proj.db里插记录。我见过有人在DB Browser里手插了一行4326定义,结果SQLite表结构对不上,整个pyproj初始化直接崩掉,最后只能重装。数据库的结构和字段受版本控制,手改就是自找麻烦。自定义坐标系用代码传WKT就好,别去动数据库本身。
第二,同步环境时别只拷贝site-packages。很多团队喜欢把整个Python环境打包拷贝到另一台机器,但proj数据文件有时不在site-packages的标准结构里,拷贝一半就丢。稳妥做法是conda环境用conda env export > environment.yml导出完整依赖,pip环境用pip freeze > requirements.txt并额外带上proj数据目录,这样才会排除版本错位的隐患。
最后分享一点个人体会。踩过这么多次坑之后,我的原则现在很固定:凡是涉到proj这一族的库,要么全用conda管理,要么全用pip管理,绝不混搭。每次新建环境,装完pyproj的第一件事就是跑一遍自检脚本,确认proj.db能正常读到。另外,别小看PROJ_LIB这个环境变量,路径出问题时它能救命;但也别过度依赖它,它只能解决路径问题,解决不了版本错配。真正根治的办法永远是先把依赖关系理干净。这套思路帮我在几台服务器和笔记本上快速治好了同样的病,希望也能帮你少走一段弯路。