Search code examples
pythongisgeopandasshapely

Python GeoPandas Convert LineString 2D to 3D - Only adding z-coordinates


I have spend a lot of time searching for a solution to this but haven't found a decent one yet.

I have a geodataframe with 2D LineStrings between two points and also 2 different Z-values/coordinates per 2D LineString. I want a direct way to add those z-coordinates to the respected LineString-parts. Is there a good way to accomplish this by use ex shapely.ops.transform?? or any other technique?.

I know I can access the LineString x,y coordinates in several ways, build two 3D points and then create a 3D LineString between these two points using x,y,z coordinates, so I'm not searching for all methods. But I want a more direct way to do this than to first create 3D points and then 3D LineString.

I'm also not looking for how to add the a dedicated z-value to a 2D LineString to create a 3D LineString. I need different Z-coordinates on the different part of the LineStrings.

My code:

    import pandas as pd
    import geopandas as gpd
    from shapely import wkt
    
    df = pd.DataFrame(
        {
        "ID": [1, 2, 3],
        "z1": [133.56, 184.84, 230.53],
        "z2": [158.66, 212.68, 241.20],
        "geometry": [
            "LINESTRING (565035.0499992011 6622605.24012313, 565064.889999201 6622483.90012313)",
            "LINESTRING (565084.009999201 6622367.28012313, 565101.339999201 6622257.27012313)",
            "LINESTRING (565119.8599992 6622140.37012313, 565144.4599992 6621985.04012313)"]
    }
    )
    
    print(df)
    df["geometry"] = gpd.GeoSeries.from_wkt(df["geometry"])
    
    gdf = gpd.GeoDataFrame(df, geometry="geometry")
    gdf = gdf.set_crs('epsg:25832')
    print(gdf.crs)
    print(gdf.dtypes)
    
    print(gdf['geometry'][0])
    print(gdf['geometry'][1])
    print(gdf['geometry'][2])
    gdf

Result: epsg:25832 ID int64 z1 float64 z2 float64 geometry geometry dtype: object

LINESTRING (565035.0499992011 6622605.24012313, 565064.889999201 6622483.90012313)

LINESTRING (565084.009999201 6622367.28012313, 565101.339999201 6622257.27012313)

LINESTRING (565119.8599992 6622140.37012313, 565144.4599992 6621985.04012313)

I want to add the respected z-coordinates so the output geodataframe geometry becomes:

"geometry": [
"LINESTRING Z(565035.0499992011 6622605.24012313 133.56, 565064.889999201         6622483.90012313 158.66)",
"LINESTRING Z(565084.009999201 6622367.28012313 184.84, 565101.339999201 6622257.27012313 212.68)",
"LINESTRING Z(565119.8599992 6622140.37012313 230.53, 565144.4599992 6621985.04012313 241.20)"]    

I appreciate any help with this

Regards Lars

I have tried severel shapely.ops.transform codes but none is solving this issue


Solution

  • Following your transform's idea, you can do it this way :

    from shapely import LineString
    from shapely.ops import transform
    
    l3d = [
        LineString(
            (
                transform(lambda x, y, z=z1: (x, y, z), p1),
                transform(lambda x, y, z=z2: (x, y, z), p2),
            )
        )
        for p1, p2, z1, z2 in gdf.boundary.explode(index_parts=True)
        .unstack()
        .join(gdf[["z1", "z2"]])
        .to_numpy()
    ]
    
    gdf.geometry = l3d # is a list of LineString Z objects
    

    Output :

    >>> l3d
    
    [
        <LINESTRING Z (565035.05 6622605.24 133.56, 565064.89 6622483.9 158.66)>,
        <LINESTRING Z (565084.01 6622367.28 184.84, 565101.34 6622257.27 212.68)>,
        <LINESTRING Z (565119.86 6622140.37 230.53, 565144.46 6621985.04 241.2)>
    ]
    
    >>> gdf
    
       ID      z1      z2                                           geometry
    0   1  133.56  158.66  LINESTRING Z (565035.050 6622605.240 133.560, ...
    1   2  184.84  212.68  LINESTRING Z (565084.010 6622367.280 184.840, ...
    2   3  230.53  241.20  LINESTRING Z (565119.860 6622140.370 230.530, ...