在地理信息系统(GIS)和数据分析领域,我们经常需要处理包含空间坐标的表格数据。这些数据通常以Excel或CSV格式存储,包含X(经度)、Y(纬度)坐标列,但缺乏空间属性,无法直接用于GIS分析。这就是"XY表转点"操作要解决的核心问题——将普通表格中的坐标数据转换为具有空间参考系统的点要素。
我最近接手了一个城市设施管理的项目,需要将分散在200多个CSV文件中的公共设施坐标(总计约15万条记录)批量转换为可地图化的点数据。手动操作不仅效率低下,还容易出错。通过Python自动化处理,最终将原本需要3天的工作压缩到15分钟完成。下面分享我的完整实现方案和踩坑经验。
实现XY表转点的常见方案有:
我选择纯Python方案,主要基于:
python复制# 基础必备库
import pandas as pd # 表格数据处理
import geopandas as gpd # 空间数据处理
from shapely.geometry import Point # 点对象构造
# 可选辅助库
import os # 文件遍历
import time # 耗时统计
from tqdm import tqdm # 进度条显示
注意:geopandas安装可能需先安装GDAL,建议使用conda安装:
conda install -c conda-forge geopandas
python复制def csv_to_shapefile(input_csv, output_shp,
x_col='x', y_col='y',
crs='EPSG:4326', encoding='utf-8'):
"""
将单个CSV转换为Shapefile点文件
参数:
input_csv: 输入CSV路径
output_shp: 输出SHP路径
x_col: X坐标列名
y_col: Y坐标列名
crs: 坐标参考系统
encoding: 文件编码
"""
# 读取CSV
df = pd.read_csv(input_csv, encoding=encoding)
# 构造点几何
geometry = [Point(xy) for xy in zip(df[x_col], df[y_col])]
# 转换为GeoDataFrame
gdf = gpd.GeoDataFrame(df, geometry=geometry, crs=crs)
# 保存为Shapefile
gdf.to_file(output_shp, encoding=encoding)
python复制def batch_xy_to_point(input_dir, output_dir,
file_suffix='.csv', **kwargs):
"""
批量处理目录下的所有CSV文件
"""
# 创建输出目录
os.makedirs(output_dir, exist_ok=True)
# 获取文件列表
files = [f for f in os.listdir(input_dir)
if f.endswith(file_suffix)]
# 处理每个文件
for file in tqdm(files):
input_path = os.path.join(input_dir, file)
output_path = os.path.join(
output_dir,
f"{os.path.splitext(file)[0]}.shp")
try:
csv_to_shapefile(input_path, output_path, **kwargs)
except Exception as e:
print(f"处理失败 {file}: {str(e)}")
实际项目中常遇到的坐标问题:
解决方案:
python复制# 在构造Point前添加数据清洗
def clean_coordinates(df, x_col, y_col):
# 移除空值
df = df.dropna(subset=[x_col, y_col])
# 过滤异常坐标
df = df[(df[x_col] != 0) & (df[y_col] != 0)]
# 限制经纬度范围(如果是WGS84)
if crs == 'EPSG:4326':
df = df[(df[x_col].between(-180, 180)) &
(df[y_col].between(-90, 90))]
return df
处理大型CSV文件时(>100MB),建议:
pd.read_csv(chunksize=50000)python复制del df
gc.collect()
利用多核加速:
python复制from multiprocessing import Pool
def parallel_convert(args):
"""包装函数用于多进程"""
return csv_to_shapefile(*args)
# 创建任务列表
tasks = [(f_in, f_out) for f_in, f_out in file_pairs]
# 启动进程池
with Pool(processes=4) as pool:
pool.map(parallel_convert, tasks)
错误现象:
UnicodeEncodeError: 'ascii' codec can't encode characters...
解决方案:
python复制# 在所有文件操作中使用原始字符串
path = r"C:\中文路径\data.csv"
典型错误:
KeyError: 'x'
处理策略:
python复制print(df.columns.tolist())
python复制x_candidates = ['x', 'X', '经度', 'longitude', 'lng']
x_col = next((col for col in x_candidates if col in df.columns), None)
Shapefile输出时可能遇到:
PermissionError: [Errno 13] Permission denied
这是因为Shapefile实际由多个文件组成(.shp, .shx, .dbf等),解决方案:
python复制import time
time.sleep(0.1)
除了Shapefile,还可输出为:
gdf.to_file('output.geojson', driver='GeoJSON')python复制from sqlalchemy import create_engine
engine = create_engine('postgresql://user:pass@localhost:5432/gisdb')
gdf.to_postgis('table_name', engine)
转换时可对字段进行优化:
python复制# 字段类型转换
gdf['date'] = pd.to_datetime(gdf['date_str'])
# 添加计算字段
gdf['area_km2'] = gdf.geometry.area / 1e6
# 字段重命名
gdf = gdf.rename(columns={'old_name':'new_name'})
典型应用场景:
watchdog库监控文件夹变化python复制from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class CSVHandler(FileSystemEventHandler):
def on_created(self, event):
if event.src_path.endswith('.csv'):
convert_to_shp(event.src_path)
python复制#!/usr/bin/env python3
"""
批量XY表转点工具 - 完整版
支持功能:
1. 批量处理目录下所有CSV
2. 自动识别坐标列
3. 内存优化处理
4. 异常捕获与日志
"""
import os
import logging
import pandas as pd
import geopandas as gpd
from shapely.geometry import Point
from multiprocessing import Pool
from tqdm import tqdm
# 日志配置
logging.basicConfig(
filename='xy_converter.log',
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
def validate_columns(df, x_candidates, y_candidates):
"""自动检测坐标列"""
x_col = next((c for c in x_candidates if c in df.columns), None)
y_col = next((c for c in y_candidates if c in df.columns), None)
if not x_col or not y_col:
raise ValueError(f"未找到坐标列,候选X列: {x_candidates},候选Y列: {y_candidates}")
return x_col, y_col
def convert_single(args):
"""单文件转换函数(供多进程使用)"""
input_path, output_dir, crs, x_cands, y_cands = args
try:
df = pd.read_csv(input_path)
# 自动检测坐标列
x_col, y_col = validate_columns(
df, x_cands, y_cands)
# 构造点几何
geometry = [Point(xy) for xy in zip(df[x_col], df[y_col])]
gdf = gpd.GeoDataFrame(df, geometry=geometry, crs=crs)
# 输出文件路径
out_name = f"{os.path.splitext(os.path.basename(input_path))[0]}.gpkg"
out_path = os.path.join(output_dir, out_name)
# 保存为GeoPackage(比Shapefile更稳定)
gdf.to_file(out_path, layer='points', driver='GPKG')
return (input_path, True, None)
except Exception as e:
logging.error(f"处理失败 {input_path}: {str(e)}")
return (input_path, False, str(e))
def batch_convert(input_dir, output_dir, crs='EPSG:4326',
x_candidates=None, y_candidates=None,
workers=4):
"""批量转换主函数"""
if x_candidates is None:
x_candidates = ['x', 'X', '经度', 'longitude', 'lng']
if y_candidates is None:
y_candidates = ['y', 'Y', '纬度', 'latitude', 'lat']
# 获取输入文件
files = [
os.path.join(input_dir, f)
for f in os.listdir(input_dir)
if f.lower().endswith('.csv')
]
# 准备任务参数
tasks = [(f, output_dir, crs, x_candidates, y_candidates)
for f in files]
# 多进程处理
with Pool(processes=workers) as pool:
results = list(tqdm(
pool.imap(convert_single, tasks),
total=len(tasks)
))
# 统计结果
success = sum(1 for r in results if r[1])
logging.info(f"处理完成: 成功 {success}/{len(results)}")
# 输出失败清单
failures = [r[0] for r in results if not r[1]]
if failures:
logging.warning("失败文件清单:\n" + "\n".join(failures))
return results
if __name__ == '__main__':
# 示例用法
batch_convert(
input_dir=r'./input_csvs',
output_dir=r'./output_shps',
crs='EPSG:4490', # 中国2000坐标系
workers=6
)
文件格式选择:新项目建议使用GeoPackage(.gpkg)替代Shapefile,它更稳定且支持中文路径
性能监控:处理10万+记录时,添加内存监控:
python复制import psutil
print(f"内存使用: {psutil.virtual_memory().percent}%")
python复制print(gdf.dtypes) # 检查字段类型
python复制gdf.to_file('output.shp', encoding='utf-8')