在GIS数据处理操作时,经常会用到点转线操作。一般ArcGIS、QGIS等等都会提供相应的工具。
这里提供一种利用Python实现点转线的方法。
Pandas及GeoPandas的强大之处,谁用谁知道
先来看以下点文件,格式是带经纬度坐标的csv文件。我们先用pandas将数据呈现出来。内容如下:
import pandas as pd
import geopandas as gpd
from shapely.geometry import LineString,Point
fp = r'E:\Dev\data\BigRoads31.csv'
df = pd.read_csv(fp)
df.iloc[:30,:]
FID | Name | State | Speed | Num | Lng | Lat | |
---|---|---|---|---|---|---|---|
0 | 0 | 科技六路 | 3 | 10.0 | 0 | 108.888107 | 34.211662 |
1 | 1 | 科技六路 | 3 | 10.0 | 0 | 108.889725 | 34.211670 |
2 | 2 | 瞪羚路 | 2 | 15.0 | 1 | 108.856926 | 34.201595 |
3 | 3 | 瞪羚路 | 2 | 15.0 | 1 | 108.857033 | 34.201599 |
4 | 4 | 瞪羚路 | 2 | 15.0 | 1 | 108.859413 | 34.201637 |
5 | 5 | 瞪羚路 | 2 | 15.0 | 1 | 108.861893 | 34.201626 |
6 | 6 | 瞪羚路 | 2 | 15.0 | 1 | 108.863655 | 34.201611 |
7 | 7 | 瞪羚路 | 2 | 15.0 | 1 | 108.868385 | 34.201630 |
8 | 8 | 瞪羚路 | 2 | 15.0 | 1 | 108.869408 | 34.201630 |
9 | 9 | 瞪羚路 | 2 | 25.0 | 2 | 108.869408 | 34.201710 |
10 | 10 | 瞪羚路 | 2 | 25.0 | 2 | 108.869308 | 34.201710 |
11 | 11 | 瞪羚路 | 2 | 25.0 | 2 | 108.868408 | 34.201698 |
12 | 12 | 瞪羚路 | 2 | 25.0 | 2 | 108.863556 | 34.201675 |
13 | 13 | 瞪羚路 | 2 | 25.0 | 2 | 108.859413 | 34.201695 |
14 | 14 | 瞪羚路 | 2 | 25.0 | 2 | 108.857033 | 34.201706 |
15 | 15 | 瞪羚路 | 2 | 25.0 | 2 | 108.856926 | 34.201702 |
16 | 16 | 高新六路 | 2 | 25.0 | 3 | 108.877281 | 34.211655 |
17 | 17 | 高新六路 | 2 | 25.0 | 3 | 108.877281 | 34.211735 |
18 | 18 | 高新六路 | 2 | 25.0 | 3 | 108.877266 | 34.212143 |
19 | 19 | 高新六路 | 2 | 25.0 | 3 | 108.877274 | 34.214108 |
20 | 20 | 高新六路 | 2 | 25.0 | 3 | 108.877243 | 34.216526 |
21 | 21 | 高新六路 | 2 | 25.0 | 3 | 108.877243 | 34.220024 |
22 | 22 | 高新六路 | 2 | 25.0 | 3 | 108.877281 | 34.220097 |
23 | 23 | 高新四路 | 2 | 25.0 | 4 | 108.895088 | 34.235291 |
24 | 24 | 高新四路 | 2 | 25.0 | 4 | 108.895073 | 34.235027 |
25 | 25 | 高新四路 | 2 | 25.0 | 4 | 108.895088 | 34.232681 |
26 | 26 | 高新四路 | 2 | 25.0 | 4 | 108.895096 | 34.232456 |
27 | 27 | 高新四路 | 2 | 25.0 | 4 | 108.895058 | 34.231377 |
28 | 28 | 高新四路 | 2 | 25.0 | 4 | 108.895042 | 34.228569 |
29 | 29 | 高新四路 | 2 | 25.0 | 4 | 108.895042 | 34.228458 |
可以看出,每一行代表一个点,每一行的最后两列为坐标。另外有两列是我们需要关注的,FID列是数据的自增ID,Num是一个编号。后面的点转线,将依据这两列。
用Geopandas将点打印出来,可以看到点的地理分布情况,如下图:
xy = [Point(xy) for xy in zip(df.Lng,df.Lat)]
pointDataFrame = gpd.GeoDataFrame(df,geometry=xy)
pointDataFrame.plot(figsize = (24, 24))
熟悉GIS矢量数据结构的人都知道,线是由点构成的,其在物理结构上表现的就是一连串有序排列的点。根据这一特征,我们来进行点转线操作。
有规则才成方圆,不,有规则才能连成线,通过查看上面的表,我们按照如下规则,将点转换为线:
1、num值相同的点,合并成一根线段;
2、线段上的点的排列顺序,按照其在表中的排列顺序FID
转换过程如下:
#分组
dataGroup = df.groupby('Num')
#构造数据
tb = []
geomList = []
for name,group in dataGroup:
# 分离出属性信息,取每组的第1行前5列作为数据属性
tb.append(group.iloc[0,:5])
# 把同一组的点打包到一个list中
xyList = [xy for xy in zip(group.Lng, group.Lat)]
line = LineString(xyList)
geomList.append(line)
# 点转线
geoDataFrame = gpd.GeoDataFrame(tb, geometry = geomList)
我们打印下结果看看
geoDataFrame.iloc[:20,:]
FID | Name | State | Speed | Num | geometry | |
---|---|---|---|---|---|---|
0 | 0 | 科技六路 | 3 | 10.0 | 0 | LINESTRING (108.888107 34.21166229999999, 108.... |
2 | 2 | 瞪羚路 | 2 | 15.0 | 1 | LINESTRING (108.856926 34.2015953, 108.857033 ... |
9 | 9 | 瞪羚路 | 2 | 25.0 | 2 | LINESTRING (108.869408 34.2017097, 108.869308 ... |
16 | 16 | 高新六路 | 2 | 25.0 | 3 | LINESTRING (108.877281 34.2116547, 108.877281 ... |
23 | 23 | 高新四路 | 2 | 25.0 | 4 | LINESTRING (108.895088 34.2352905, 108.895073 ... |
31 | 31 | 科技一路 | 2 | 20.0 | 5 | LINESTRING (108.900749 34.2257996, 108.900589 ... |
38 | 38 | 210国道 | 1 | 40.0 | 6 | LINESTRING (108.781281 33.8055725, 108.781242 ... |
2914 | 2914 | 210国道 | 1 | 45.0 | 7 | LINESTRING (108.8955 34.19851679999999, 108.89... |
3131 | 3131 | 太白南路 | 1 | 35.0 | 8 | LINESTRING (108.895638 34.1985016, 108.89666 3... |
3174 | 3174 | 丈八三路 | 1 | 30.0 | 9 | LINESTRING (108.875626 34.1899567, 108.87558 3... |
3180 | 3180 | 丈八三路 | 1 | 30.0 | 10 | LINESTRING (108.875481 34.19846339999999, 108.... |
3187 | 3187 | 唐延南路 | 1 | 35.0 | 11 | LINESTRING (108.881477 34.1797485, 108.881508 ... |
3229 | 3229 | 唐延南路 | 1 | 35.0 | 12 | LINESTRING (108.890282 34.19849779999999, 108.... |
3267 | 3267 | 丈八东路 | 1 | 35.0 | 13 | LINESTRING (108.874435 34.2050018, 108.874565 ... |
3339 | 3339 | 丈八东路 | 1 | 35.0 | 14 | LINESTRING (108.945831 34.19813920000001, 108.... |
3408 | 3408 | 丈八六路 | 1 | 35.0 | 15 | LINESTRING (108.855911 34.1825256, 108.855934 ... |
3430 | 3430 | 丈八六路 | 1 | 40.0 | 16 | LINESTRING (108.857033 34.2045937, 108.856934 ... |
3451 | 3451 | 锦业路 | 1 | 35.0 | 17 | LINESTRING (108.841652 34.1938324, 108.841728 ... |
3482 | 3482 | 丈八五路 | 1 | 40.0 | 18 | LINESTRING (108.864204 34.1888275, 108.86409 3... |
3497 | 3497 | 丈八五路 | 1 | 35.0 | 19 | LINESTRING (108.863541 34.20478060000001, 108.... |
可以看到,数据已经按Num分组,并合并成了一个LineString对象。
##通过地图显示查看结果。
geoDataFrame.plot(figsize = (24, 24))
1)保存为geojson,两种方法
# 方法一
fp = r"E:\Dev\data\lineRoads.geojson"
geoDataFrame.to_file(fp, driver='GeoJSON', encoding="utf-8")
#方法二
json = geoDataFrame.to_json()
with open(fp,'w') as f:
f.write(json)
2)保存为shp
shp = r"E:\Dev\data\lineRoads.shp"
geoDataFrame.to_file(shp,driver="ESRI Shapefile",encoding="utf-8")
刨除上面的一些测试验证代码,仅仅几行即可实现点转线功能。Good!!!
import pandas as pd
import geopandas as gpd
from shapely.geometry import LineString,Point
def main():
fp = r'E:\Dev\data\BigRoads31.csv'
df = pd.read_csv(fp)
#分组
dataGroup = df.groupby('Num')
#构造数据
tb = []
geomList = []
for name,group in dataGroup:
# 分离出属性信息,取每组的第1行前5列作为数据属性
tb.append(group.iloc[0,:5])
# 把同一组的点打包到一个list中
xyList = [xy for xy in zip(group.Lng, group.Lat)]
line = LineString(xyList)
geomList.append(line)
# 点转线
geoDataFrame = gpd.GeoDataFrame(tb, geometry = geomList)
fp = r"E:\Dev\data\lineRoads.geojson"
geoDataFrame.to_file(fp, driver='GeoJSON', encoding="utf-8")
if __name__ == '__main__':
main()