Search code examples
geopandaspydeck

Plot LINESTRING Z from GeoDataFrame using pydeck's PathLayer (or TripLayer)


I have a geodataframe with LINESTRING Z geometries:

TimeUTC Latitude Longitude AGL geometry
0 2021-06-16 00:34:04+00:00 42.8354 -70.9196 82.2 LINESTRING Z (42.83541343273769 -70.91961015378617 82.2, 42.83541343273769 -70.91961015378617 82.2)
1 2021-06-14 13:32:18+00:00 42.8467 -70.8192 66.3 LINESTRING Z (42.84674080836037 -70.81919357049679 66.3, 42.84674080836037 -70.81919357049679 66.3)
2 2021-06-18 23:56:05+00:00 43.0788 -70.7541 0.9 LINESTRING Z (43.07882882269921 -70.75414567194126 0.9, 43.07884601143309 -70.75416286067514 0, 43.07885174101104 -70.75416286067514 0, 43.07884028185512 -70.75415713109717 0, 43.07884601143309 -70.75414567194126 0, 43.07884601143309 -70.75414567194126 0)

I can plot the component points using pydeck's ScatterplotLayer using the raw (not geo) dataframe but I need to also plot the full, smooth, track.

I've tried this:

        layers = [ 
pdk.Layer(
    type = "PathLayer",
    data=tracks,
    get_path="geometry",
    width_scale=20,
    width_min_pixels=5,
    get_width=5,
    get_color=[180, 0, 200, 140],
    pickable=True,
    ),
]
view_state = pdk.ViewState(
    latitude=gdf_polygon.centroid.x,
    longitude=gdf_polygon.centroid.y,
    zoom=6,
    min_zoom=5,
    max_zoom=15,
    pitch=40.5,
    bearing=-27.36)

r = pdk.Deck(layers=[layers], initial_view_state=view_state)
return(r)

Which silently fails. Try as I might, I cannot find a way to convert the LINESTRING Z's (and I can do without the Z component if need be) to an object that pydeck will accept.


Solution

  • I found a way to extract the info needed from GeoPandas and make it work in pydeck. You just need to apply a function that extracts the coordinates from the shapely geometries as a list. Here is a fully reproducible example:

    import shapely
    import numpy as np
    import pandas as pd
    import pydeck as pdk
    import geopandas as gpd
    
    
    linestring_a = shapely.geometry.LineString([[0,1,2],
                                                [3,4,5],
                                                [6,7,8]])
    
    linestring_b = shapely.geometry.LineString([[7,15,1],
                                                [8,14,2],
                                                [9,13,3]])
    
    multilinestring = shapely.geometry.MultiLineString([[[10,11,2],
                                                         [13,14,5],
                                                         [16,17,8]],
                                                        [[19,10,11],
                                                         [12,15,4],
                                                         [10,13,0]]])
    
    
    
    gdf = gpd.GeoDataFrame({'id':[1,2,3],
                            'geometry':[linestring_a,
                                        linestring_b,
                                        multilinestring],
                            'color_hex':['#ed1c24',
                                         '#faa61a',
                                         '#ffe800']})
    
    # Function that transforms a hex string into an RGB tuple.
    def hex_to_rgb(h):
        h = h.lstrip("#")
        return tuple(int(h[i : i + 2], 16) for i in (0, 2, 4))
    
    # Applying the HEX-to-RGB function above
    gdf['color_rgb'] = gdf['color_hex'].apply(hex_to_rgb)
    
    # Function that extracts the 2d list of coordinates from an input geometry
    def my_geom_coord_extractor(input_geom):
        if (input_geom is None) or (input_geom is np.nan):
            return []
        else:
            if input_geom.type[:len('multi')].lower() == 'multi':
                full_coord_list = []
                for geom_part in input_geom.geoms:
                    geom_part_2d_coords = [[coord[0],coord[1]] for coord in list(geom_part.coords)]
                    full_coord_list.append(geom_part_2d_coords)
            else:
                full_coord_list = [[coord[0],coord[1]] for coord in list(input_geom.coords)]
            return full_coord_list
    
    # Applying the coordinate list extractor to the dataframe
    gdf['coord_list'] = gdf['geometry'].apply(my_geom_coord_extractor)
    
    gdf_polygon = gdf.unary_union.convex_hull 
    
    # Establishing the default view for the pydeck output
    view_state = pdk.ViewState(latitude=gdf_polygon.centroid.coords[0][1], 
                               longitude=gdf_polygon.centroid.coords[0][0], 
                               zoom=4)
    
    # Creating the pydeck layer
    layer = pdk.Layer(
        type="PathLayer",
        data=gdf,
        pickable=True,
        get_color='color_rgb',
        width_scale=20,
        width_min_pixels=2,
        get_path="coord_list",
        get_width=5,
    )
    
    # Finalizing the pydeck output
    r = pdk.Deck(layers=[layer], initial_view_state=view_state, tooltip={"text": "{id}"})
    r.to_html("path_layer.html")
    

    Here's the output it yields: screenshot from "path_layer.html"

    Big caveat

    It seems like pydeck isn't able to deal with MultiLineString geometries. Notice how, in the example above, my original dataframe had 3 geometries, but only 2 lines were drawn in the screenshot.