简介
最近公司让把shp文件修改数据后导入数据库中,研究了许久,网上的资料全是一个
to_sql
就搞定了,但是由于shp文件的多样性会出现很多问题。
在此总结一下遇到的问题
shp数据类型分类
shp数据共有6种类型
MultiPolygon, MultiPoint, MultiLineString, Point, Polygon, LineString
,分别是点、线、面、多点、多线、多面
点线面可以转换为多点、多线、多面,多点、多线、多面也可以转为、点、线、面。两者可以互相转换
将Polygon转为MultiPolygon
例:
from shapely import Polygon, MultiPolygon
polygon = Polygon([(0, 0), (1, 1), (1, 0)])
multi = MultiPolygon([polygon])
print(multi)
将 MultiPolygon转为Polygon
polygon = Polygon([(0, 0), (1, 1), (1, 0)])
multi = MultiPolygon([polygon])
for i in MultiPolygon([polygon]).geoms:
print(i)
总结
点、线、面与多点、多线、多面可以相互转换,多点可以理解为一个点的列表,同理其他的也是一样的
POLYGON
与MultiPolygon
不相符
在将shp中,一般的文件中只有一个类型,但是有特殊情况,例如:一个shp中有两个类型
POLYGON
与MultiPolygon
,shp文件本身是支持的,但是数据库不支持,如果矢量数据包含这两种类型,geopandas
本身是添加不进去的,如果强行添加会报错。
因此就需要手动转换类型,如果你用的是qgis或者arcgis也会发现,他会把POLYGON
转为MultiPolygon
再添加到数据中。
如果使用MultiPolygon
转为POLYGON
,它会产生多条数据,不符合需求,因此一般使用POLYGON
转为MultiPolygon
需要注意的是
if_exists
必须选择replace
,否则会出现各种各样的问题,比如没有指定srid
等错误,但是使用replace
会先删除原本的表,表的约束会清空,pandas在重新创建表时,没办法指定主键等约束,目前没有找到解决方法
完整代码:
import re
from urllib import parse
from geoalchemy2 import Geometry
from shapely import MultiPoint, MultiLineString, MultiPolygon
from sqlalchemy import create_engine
from geopandas import GeoDataFrame, GeoSeries
def translation(url):
# 对用户名密码进行转码,如果账号密码有特殊字符,sqlalchemy 会连接失败
return parse.quote_plus(url)
url = f"postgresql+psycopg2://{translation(user)}:{translation(pwd)}@{host}:{port}/{dbname}"
engine = create_engine(url)
file = r"JBHQ418.shp"
shp_data = GeoDataFrame.from_file(file) # type: gpd.GeoDataFrame
shp_data.columns = [i.lower() for i in shp_data.columns]
shp_len = len(shp_data)
# 获取srid, 坐标系
srid = int(re.search('\d+', str(shp_data.crs).split(',')[-1]).group(0))
# 获取每一行矢量数据的数据类型
geom_type = shp_data.geom_type # type: core.series.Series
# 给类型去重,并获取总类型
types = list(set(i for i in geom_type))
print(types)
# ['Polygon', 'MultiPolygon']
geometry_type = None
if 'Point' in types:
geometry_type = 'MultiPoint'
if 'LineString' in types:
geometry_type = 'MultiLineString'
if 'Polygon' in types:
geometry_type = 'MultiPolygon'
# 重新创建表,并给表赋值
geo_data = GeoDataFrame()
geo_data['bsm'] = shp_data['bsm']
geo_data['ysdm'] = shp_data['ysdm']
geo_data['bhqbh'] = shp_data['bhqbh']
geo_data['jbntmj'] = shp_data['jbntmj']
geo_data['jbntmjm'] = shp_data['jbntmjm']
geo_data['qxdm'] = ['15' for _ in range(shp_len)]
# `POLYGON`转为`MultiPolygon`
def one_to_multi1(x):
_t = type(x).__name__
if _t == 'Point':
w = MultiPoint([x])
return w
elif _t == 'LineString':
w = MultiLineString([x])
return w
elif _t == 'Polygon':
w = MultiPolygon([x])
return w
else:
return x
geo_data['shape'] = shp_data['geometry'].apply(lambda x: one_to_multi1(x))
# 重写矢量数据字段
geo_data.set_geometry('shape', inplace=True)
# 写入坐标系
geo_data.set_crs(f'EPSG:{srid}', allow_override=True)
geo_data.to_postgis(
'table_name',
engine,
if_exists='replace',
index=False,
chunksize=1000,
dtype={'geometry': Geometry(geometry_type=geometry_type, srid=srid)},
)
to_postgis
postgresql数据导出
from shapely import wkb
from geopandas import GeoDataFrame
def translation(url):
# 对用户名密码进行转码,如果账号密码有特殊字符,sqlalchemy 会连接失败
return parse.quote_plus(url)
url = f"postgresql+psycopg2://{translation(user)}:{translation(pwd)}@{host}:{port}/{dbname}"
engine = create_engine(url)
conn = engine.connect()
def select_to_geo(table_name):
sql = f'SELECT * FROM "{table_name}"'
return GeoDataFrame(read_sql(sql, conn))
geo['shape'] = geo['shape'].apply(lambda x: wkb.loads(x))
geo.set_geometry('shape', inplace=True)
geo.to_file(
r'F:\test\shp\dlxx_xzdy.shp',
index=False,
encoding='utf-8'
)