4

I have a LineString GeoDataframe that I am trying to convert into a Points GeoDataframe, but I want to retain the GroupBy and SortBy features inherent in a LineString (i.e., all the vertices that make up a line are grouped by some ID and sorted in a specific order).

A similar question was asked here, but I don't understand from the answers (1) how to get my groupby/sortby requirement; and (2) why they use a one line function, it seems like there should be a cleaner way.

Below I have an example where I build a LineString from a Points GeoDataFrame, and I am basically trying to decompose it back to Points. In reality, I don't have the original Points GeoDataFrame, I just made one up here so that someone can have an easy copy/paste example to work with (per the questions guidelines).

Build Example LineString GeoDataFrame

%matplotlib inline
import matplotlib.pyplot as plt
import pandas as pd
import geopandas as gpd
from shapely.geometry import Point
from shapely.geometry import LineString
import pyproj
from pyproj import CRS

myid = [1, 1, 1, 2, 2]
myorder = [1, 2, 3, 1, 2]
lat = [36.42, 36.4, 36.32, 36.28, 36.17]
long = [-118.11, -118.12, -118.07, -117.95, -117.95]
df = pd.DataFrame(list(zip(myid, myorder, lat, long)), columns =['myid', 'myorder', 'lat', 'long']) 
gdf_pt = gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(df['long'], df['lat']))
display(gdf_pt)
gdf_line = gdf_pt.sort_values(by=['myorder']).groupby(['myid'])['geometry'].apply(lambda x: LineString(x.tolist()))
gdf_line = gpd.GeoDataFrame(gdf_line, geometry='geometry')
gdf_line.crs = "EPSG:4326"
display(gdf_line)
ax = gdf_line.plot();
ax.set_aspect('equal')
ax.set_xticklabels(ax.get_xticklabels(), rotation=90);

enter image description here

Attempt Below follows one of the answers from the linked question. It returns a Pandas Series, and I'm just not sure how to unpack it into a dataframe with GroupBy (based on "myid") and then create a SortBy based on the order.

mypoints = gdf_line.apply(lambda x: [y for y in x['geometry'].coords], axis=1)
print(mypoints)
print(type(mypoints))

enter image description here

System details: Windows 10
conda 4.8.2
Python 3.8.3
shapely 1.7.0 py38hbf43935_3 conda-forge
pyproj 2.6.1.post1 py38h1dd9442_0 conda-forge

3 Answers 3

1

I am not sure if I understood your question clearly.
Anyway, I think this problem will be solved if you make a gdf that keeps the order and ID.
shepely.coords can return the coordinates (point values) of each linestring.
Based on this, you can create a new gdf.
By default, coords return values in the order of Linestring.

myid_list = gdf_line.index.to_list()
repeat_list = [len(line.coords) for line in gdf_line['geometry'].unary_union] #how many points in each Linestring
coords_list = [line.coords for line in gdf_line['geometry'].unary_union]

#make new gdf
gdf = gpd.GeoDataFrame(columns=['myid', 'order', 'geometry'])

for myid, repeat, coords in zip(myid_list, repeat_list, coords_list):
    index_num = gdf.shape[0]
    for i in range(repeat):
        gdf.loc[index_num+i, 'geometry'] = Point(coords[i])
        gdf.loc[index_num+i, 'myid'] = myid

gdf['order'] = range(1, 1+len(df))

#you can use groupby method
gdf.groupby('myid')['geometry'].apply(list)

I think there are other better ways.

UPDATE

By AlexS1 comment

for myid, repeat, coords in zip(myid_list, repeat_list, coords_list):
    index_num = gdf.shape[0]
    for i in range(repeat):
        gdf.loc[index_num+i, 'geometry'] = Point(coords[i])
        gdf.loc[index_num+i, 'myid'] = myid
        gdf.loc[index_num+i, 'order'] = i+1
3
  • This is great, it is really close, thank you. If you do display(gdf) or print(gdf) however the order field is off... it should be 1,2,3,1,2 but it is 1,2,3,4,5
    – a11
    Commented Jul 8, 2020 at 0:46
  • @AlexS1 The value of i in the loop means the order of each point. I updated my answer.
    – Urban87
    Commented Jul 8, 2020 at 4:20
  • Gosh that obvious now that I look at with fresh eyes, thank you for your help and explanations
    – a11
    Commented Jul 8, 2020 at 16:18
0

I'm trying to make quite same manipulation. In my case I want ton convert linestring to points in my study in order to avoid dateline problem.

I used your explanations, everything is fine. My question is, do you have any idea on how I can speed up my code ? I have many tracks to convert.

Here is my code and my example geodataframe looks like:

    DEBUT   MEMBER  FIN ID  VORT    PRES    CIRC    geometry
0   2020112712  0   2020112715  48  15.0    1010.14 0.0 LINESTRING (-89 8, -88 8.75)
1   2020112715  0   2020112718  48  14.0    1009.51 0.0 LINESTRING (-88 8.75, -87.75 8)
2   2020112718  0   2020112721  48  14.0    1007.63 0.0 LINESTRING (-87.75 8, -88 7.75)
3   2020112721  0   2020112800  48  15.0    1007.83 0.0 LINESTRING (-88 7.75, -89 7.75)
4   2020112800  0   2020112803  48  17.0    1009.37 0.0 LINESTRING (-89 7.75, -89.25 8.25)
5   2020112803  0   2020112806  48  17.0    1009.05 0.0 LINESTRING (-89.25 8.25, -88.75 8.25)
6   2020112806  0   2020112809  48  18.0    1007.61 0.0 LINESTRING (-88.75 8.25, -89 8.25)
tracks_line = geopandas.read_file('./ECCC/Output/CMC_cyclone-tracks_geps_PRMSL_MSL_0_2020112400_P111.gpkg')

#def linestring_to_points(feature,line):
#    return {feature:line.coords}
#tracks_line['geometry'] = tracks_line.apply(lambda l: linestring_to_points(l['ID'],l['geometry']),axis=1)
#tracks_line.plot()


debut_list = tracks_line['DEBUT'].to_list()
member_list = tracks_line['MEMBER'].to_list()
fin_list = tracks_line['FIN'].to_list()
id_list = tracks_line['ID'].to_list()
vort_list = tracks_line['VORT'].to_list()
pres_list = tracks_line['PRES'].to_list()
circ_list = tracks_line['CIRC'].to_list()
coords_list = [line.coords for line in tracks_line['geometry'].unary_union]
repeat_list = [len(line.coords) for line in tracks_line['geometry'].unary_union] #how many points in each Linestring

# #make new gdf
gdf = geopandas.GeoDataFrame(columns=['DEBUT', 'MEMBER', 'FIN', 'ID', 'VORT', 'PRES', 'CIRC', 'geometry'])

for deb, memb, fin, id_list, vort, pres, circ, coords, repeat in zip(debut_list, member_list, fin_list, id_list,  vort_list, pres_list, circ_list, coords_list, repeat_list):
    index_num = gdf.shape[0]
    for i in range(repeat):
        gdf.loc[index_num+i, 'DEBUT'] = deb
        gdf.loc[index_num+i, 'MEMBER'] = int(memb)
        gdf.loc[index_num+i, 'FIN'] = fin
        gdf.loc[index_num+i, 'ID'] = int(id_list)
        gdf.loc[index_num+i, 'VORT'] = float(vort)
        gdf.loc[index_num+i, 'PRES'] = float(pres)
        gdf.loc[index_num+i, 'CIRC'] = int(circ)
        gdf.loc[index_num+i, 'geometry'] = Point(coords[i])

1
  • I am not sure how to speed it up... you might have better luck if you do your post as a new question, instead of an answer, and maybe add a link to this post. good luck
    – a11
    Commented Dec 3, 2020 at 22:43
0

To speed this up it's rather suggested to use lists instead of the .loc method, as mentioned by cs95 here.

point_list, track_index_list, point_index_list = 
list(), list(), list()

for id, repeat, coords in zip(index_list, repeat_list, coords_list):

for i in range(repeat):
    track_index_list.append(id)
    point_index_list.append(i)
    point_list.append(Point(coords[i]))

multiindex = pandas.MultiIndex.from_tuples(zip(*[track_index_list, point_index_list]))
gdf = geopandas.GeoDataFrame(index=multiindex, geometry=point_list)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.