Gdal Geopandas python 程序打包过程

Python地理空间应用打包指南:解决GDAL、Shapely和GeoPandas依赖问题

前言

在开发地理空间Python应用时,使用GDAL、Shapely和GeoPandas等库是非常常见的。然而,当需要将应用打包成可执行文件分发时,往往会遇到各种依赖问题。本文将详细介绍如何使用PyInstaller完美打包包含这些地理空间库的Python应用。

操作系统环境win10  以上  

 

如果用win7 请安装vxkey 

软件使win7 下可以运行python3.9  把程序添加到vxkeycfg 列表中

一、环境准备

1.1 创建专用虚拟环境

强烈建议为打包创建一个干净的conda环境:

bash

conda deactivate  
新环境
conda create -n python39env python=3.9 -y
conda activate python39env

1.2 安装核心依赖

bash

conda install -c conda-forge gdal=3.4.1 shapely=2.0.2 geopandas=0.12.2 pyinstaller=5.6.2 -y

二、完整打包解决方案

2.1 基础打包命令

bash

0 脚本编写

# -*- coding: utf-8 -*-
import os
import sys
import warnings
from osgeo import gdal, osr
import pyproj
import os
import json
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import sys
import ctypes
from pathlib import Path

def init_shapely():
    """初始化Shapely的DLL加载路径"""
    base_path = getattr(sys, '_MEIPASS', Path(__file__).parent)
    
    # Windows专用处理
    if sys.platform == 'win32':
        dll_path = base_path / 'geos_c.dll'
        if not dll_path.exists():
            raise RuntimeError(f"缺失geos_c.dll,预期位置: {dll_path}")
        
        # 显式加载DLL
        try:
            ctypes.CDLL(str(dll_path))
        except Exception as e:
            raise RuntimeError(f"加载geos_c.dll失败: {str(e)}")
        
        # 添加DLL搜索路径
        os.add_dll_directory(str(base_path))

# 在所有shapely导入前调用
init_shapely()
from collections import defaultdict
from tkinter import scrolledtext
import pdb
import os
from dbf import dbf  # 使用 dbf 库操作 DBF 文件
import traceback
from datetime import datetime
import time
import pdb
from osgeo import ogr
from osgeo import gdal
import geopandas as gpd
os.environ['PROJ_DEBUG'] = '3' 
def resource_path(relative):
    """获取打包资源的正确路径"""
    if hasattr(sys, '_MEIPASS'):
        return os.path.join(sys._MEIPASS, relative)
    return os.path.join(os.path.abspath("."), relative)

def setup_environment():
    """配置GDAL/PROJ环境"""
    # 显式设置异常处理(解决FutureWarning)
    osr.UseExceptions()
    
    # 设置数据路径
    os.environ['PROJ_LIB'] = resource_path('proj_data')
    os.environ['GDAL_DATA'] = resource_path('gdal_data')
    pyproj.datadir.set_data_dir(resource_path('proj_data'))
    gdal.SetConfigOption('GDAL_DRIVER_PATH', resource_path('gdalplugins'))
    
    # 启用网络网格下载(可选)
    os.environ['PROJ_NETWORK'] = 'ON'

def validate_coordinates(lon, lat):
    """验证坐标有效性"""
    if not (-180 <= lon <= 180):
        raise ValueError(f"Invalid longitude: {lon} (must be -180 to 180)")
    if not (-85.06 <= lat <= 85.06):  # Web Mercator的有效纬度范围
        raise ValueError(f"Invalid latitude: {lat} (must be -85.06 to 85.06 for Web Mercator)")

def transform_coordinates(lon, lat, from_epsg=4326, to_epsg=3857):
    """安全的坐标转换"""
    validate_coordinates(lon, lat)
    
    src_srs = osr.SpatialReference()
    src_srs.ImportFromEPSG(from_epsg)
    
    tgt_srs = osr.SpatialReference()
    tgt_srs.ImportFromEPSG(to_epsg)
    
    # 设置坐标转换域(解决某些PROJ版本问题)
    osr.SetPROJSearchPaths([resource_path('proj_data')])
    transform = osr.CoordinateTransformation(src_srs, tgt_srs)
    return transform.TransformPoint(lon, lat)

def main():
    # 初始化环境
    setup_environment()
    
    print("="*40)
    print(f"GDAL版本: {gdal.__version__}")
    print(f"PROJ版本: {pyproj.__proj_version__}")
    print(f"PROJ数据路径: {pyproj.datadir.get_data_dir()}")
    
    # 测试坐标转换
    from pyproj import CRS, Transformer
    transformer = Transformer.from_crs(
        CRS("EPSG:4326"), 
        CRS("EPSG:3857"),
        always_xy=True)

    
    input("
按回车键退出...")

if __name__ == "__main__":
    # 抑制FutureWarning
    with warnings.catch_warnings():
        warnings.simplefilter("ignore", category=FutureWarning)
        main()

:: 1. 设置虚拟环境路径(注意已经去掉了重复的Libraryin层级)
set GDAL_DIR=G:ProgramDataAnaconda3envspython39envLibraryin
set PROJ_DATA=G:ProgramDataAnaconda3envspython39envLibraryshareproj

pyinstaller –onefile ^
    –name=GDALApp ^
    –hidden-import=osgeo._gdal ^
    –hidden-import=pyproj._datadir ^
    –hidden-import=shapely.geos ^
    –hidden-import=shapely._geos ^
    –hidden-import=geopandas._compat ^
    –hidden-import=fiona._shim ^
    –hidden-import=rtree.index ^
    –add-data=”%PROJ_DATA%proj.db;.” ^
    –add-data=”%PROJ_DATA%*;proj_data” ^
    –add-binary=”%GDAL_DIR%gdal.dll;.” ^
    –add-binary=”%GDAL_DIR%geos_c.dll;.” ^
    –add-binary=”%GDAL_DIR%geos.dll;.” ^
     –collect-all shapely ^
    –collect-all geopandas ^
    –runtime-tmpdir=. ^
    –clean ^
    test.py

:: 复制可能遗漏的文件
xcopy “%GDAL_DIR%geos_c.dll” “dist” /Y
xcopy “%GDAL_DIR%geos.dll” “dist” /Y
xcopy “%GDAL_DIR%libspatialindex_c.dll” “dist” /Y

:: 4. 复制文件(修正了路径引用)
xcopy “%PROJ_DATA%proj.db” “dist” /Y
xcopy “%GDAL_DIR%gdal.dll” “dist” /Y
robocopy “%GDAL_DIR%gdalplugins” “distgdalplugins” /

5文件列表

gdallocationinfo.exe
gdalmanage.exe
gdalmdiminfo.exe
gdalmdimtranslate.exe
gdalsrsinfo.exe
gdaltindex.exe
gdaltransform.exe
gdalwarp.exe
geos.dll
geos_c.dll
lib.cp39-win_amd64.pyd
proj.db
proj_9_3.dll
gdalplugins
_gdal.pyd
_gdal_array.cp39-win_amd64.pyd
_gdalconst.cp39-win_amd64.pyd
_geometry_helpers.cp39-win_amd64.pyd
_geos.cp39-win_amd64.pyd
_geos.pxd
_gnm.cp39-win_amd64.pyd
_ogr.cp39-win_amd64.pyd
_osr.cp39-win_amd64.pyd
_pygeos_api.pxd
gdal.dll
gdal.lib
gdal_contour.exe
gdal_create.exe
gdal_footprint.exe
gdal_grid.exe
gdal_rasterize.exe
gdal_translate.exe
gdal_viewshed.exe
gdaladdo.exe
gdalbuildvrt.exe
gdaldem.exe
gdalenhance.exe
gdalinfo.exe
GDALApp.exe
shp_processing.log

三、关键问题解决方案

3.1 解决DLL加载问题

在代码中添加运行时初始化:

python

import os
import sys
from pathlib import Path

def init_geo_env():
    """初始化地理空间环境"""
    base_path = Path(getattr(sys, '_MEIPASS', Path(__file__).parent))
    
    # 设置GDAL环境
    os.environ['GDAL_DATA'] = str(base_path / 'gdal_data')
    os.environ['GDAL_DRIVER_PATH'] = str(base_path / 'gdalplugins')
    
    # 设置PROJ环境
    proj_path = base_path / 'proj_data'
    os.environ['PROJ_LIB'] = str(proj_path)
    
    # 加载关键DLL
    dlls = ['geos_c.dll', 'gdal304.dll', 'libspatialindex_c.dll']
    for dll in dlls:
        try:
            ctypes.CDLL(str(base_path / dll))
        except Exception as e:
            print(f"加载{dll}失败: {str(e)}")

if __name__ == '__main__':
    init_geo_env()
    # 主程序代码...

3.2 验证打包结果

创建验证脚本verify.py

python

import os
import sys

def verify_package():
    required = {
        '文件': ['gdal304.dll', 'geos_c.dll', 'proj.db'],
        '目录': ['gdalplugins', 'proj_data']
    }
    
    base_path = getattr(sys, '_MEIPASS', os.path.dirname(__file__))
    
    errors = []
    for file in required['文件']:
        if not os.path.exists(os.path.join(base_path, file)):
            errors.append(f"缺失文件: {file}")
    
    for folder in required['目录']:
        if not os.path.isdir(os.path.join(base_path, folder)):
            errors.append(f"缺失目录: {folder}")
    
    if errors:
        print("验证失败:")
        print("
".join(errors))
        print("当前目录内容:", os.listdir(base_path))
        sys.exit(1)
    else:
        print("所有依赖验证通过")

verify_package()

四、常见错误处理

4.1 “GEOSException: Could not find module…”

解决方案:

确保geos_c.dll存在

添加运行时DLL加载:

python

ctypes.CDLL(os.path.join(base_path, 'geos_c.dll'))

4.3 PROJ数据库问题

解决方案:

确保包含完整proj_data目录

设置环境变量:

python

os.environ['PROJ_LIB'] = proj_data_path

也可用spec

GDALApp.spec

python

# -*- mode: python -*-
from PyInstaller.utils.hooks import collect_data_files, collect_dynamic_libs
import os

# 1. Get environment variables
gdal_dir = os.environ.get('GDAL_DIR', '')
proj_data = os.environ.get('PROJ_DATA', '')

# 2. Setup paths
binaries = []
datas = []

# GDAL binaries
if gdal_dir:
    binaries += [
        (os.path.join(gdal_dir, 'gdal.dll'), '.'),
        (os.path.join(gdal_dir, 'geos_c.dll'), '.'),
        (os.path.join(gdal_dir, 'geos.dll'), '.')
    ]
    
    # GDAL plugins (recursive copy)
    gdalplugins_dir = os.path.join(gdal_dir, 'gdalplugins')
    if os.path.exists(gdalplugins_dir):
        binaries += [(os.path.join(gdalplugins_dir, '*'), 'gdalplugins')]

# PROJ data
if proj_data:
    datas += [
        (os.path.join(proj_data, 'proj.db'), '.'),
        (os.path.join(proj_data, '*'), 'proj_data')
    ]

# 3. Main Analysis
a = Analysis(
    ['test.py'],
    pathex=[],
    binaries=binaries,
    datas=datas,
    hiddenimports=[
        'osgeo',
        'osgeo._gdal',
        'pyproj',
        'pyproj._datadir',
        'shapely.geos',
        'shapely._geos',
        'geopandas._compat',
        'fiona._shim',
        'rtree.index'
    ],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False
)

# 4. Collect additional packages
for pkg in ['shapely', 'geopandas', 'fiona', 'rtree']:
    a.datas += collect_data_files(pkg)
    try:
        a.binaries += collect_dynamic_libs(pkg)
    except:
        pass

pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    pyz,
    a.scripts,
    [],
    exclude_binaries=True,
    name='GDALApp',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=True,
    disable_windowed_tracker=False,
    runtime_tmpdir='.',
    argv_emulation=False
)

coll = COLLECT(
    exe,
    a.binaries,
    a.zipfiles,
    a.datas,
    strip=False,
    upx=True,
    upx_exclude=[],
    name='GDALApp'
)

批处理文件

build.bat

batch

@echo off
setlocal enabledelayedexpansion

:: 1. Set environment paths
set GDAL_DIR=G:ProgramDataAnaconda3envspython39envLibraryin
set PROJ_DATA=G:ProgramDataAnaconda3envspython39envLibraryshareproj

:: 2. Verify paths
echo [INFO] Building GDAL application...
echo [DEBUG] GDAL path: %GDAL_DIR%
echo [DEBUG] PROJ data path: %PROJ_DATA%

if not exist "%GDAL_DIR%gdal.dll" (
    echo [ERROR] gdal.dll not found in %GDAL_DIR%
    pause
    exit /b 1
)

if not exist "%PROJ_DATA%proj.db" (
    echo [ERROR] proj.db not found in %PROJ_DATA%
    pause
    exit /b 1
)

:: 3. Build using spec file
echo [INFO] Running PyInstaller...
G:ProgramDataAnaconda3envspython39envpython.exe -m PyInstaller GDALApp.spec --clean

if errorlevel 1 (
    echo [ERROR] Build failed
    pause
    exit /b 1
)

:: 4. Verify output
if exist "distGDALAppGDALApp.exe" (
    echo [SUCCESS] Build completed!
    echo Executable location: %cd%distGDALAppGDALApp.exe
) else (
    echo [ERROR] Executable not found in dist folder
)

pause
© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容