docs: def create_shapes(feed: "Feed", *, all_trips: bool = False) -> "Feed":
      """
      Given a feed, create a shape for every trip that is missing a
      shape ID.
      Do this by connecting the stops on the trip with straight lines.
      Return the resulting feed which has updated shapes and trips
      tables.
  
      If ``all_trips``, then create new shapes for all trips by
      connecting stops, and remove the old shapes.
  
      Assume the following feed attributes are not ``None``:
  
      - ``feed.stop_times``
      - ``feed.trips``
      - ``feed.stops``
  
      """
      feed = feed.copy()
  
      if all_trips:
          trip_ids = feed.trips["trip_id"]
      else:
          trip_ids = feed.trips[feed.trips["shape_id"].isnull()]["trip_id"]
  
      # Get stop times for given trips
      f = feed.stop_times[feed.stop_times["trip_id"].isin(trip_ids)][
          ["trip_id", "stop_sequence", "stop_id"]
      ]
      f = f.sort_values(["trip_id", "stop_sequence"])
  
      if f.empty:
          # Nothing to do
          return feed
  
      # Create new shape IDs for given trips.
      # To do this, collect unique stop sequences,
      # sort them to impose a canonical order, and
      # assign shape IDs to them
      stop_seqs = sorted(
          set(
              tuple(group["stop_id"].values)
              for trip, group in f.groupby("trip_id")
          )
      )
      k = int(math.log10(len(stop_seqs))) + 1  # Digits for padding shape IDs
      shape_by_stop_seq = {
          seq: f"shape_{i:0{k}d}" for i, seq in enumerate(stop_seqs)
      }
  
      # Assign these new shape IDs to given trips
      shape_by_trip = {
          trip: shape_by_stop_seq[tuple(group["stop_id"].values)]
          for trip, group in f.groupby("trip_id")
      }
      trip_cond = feed.trips["trip_id"].isin(trip_ids)
      feed.trips.loc[trip_cond, "shape_id"] = feed.trips.loc[
          trip_cond, "trip_id"
      ].map(lambda x: shape_by_trip[x])
  
      # Build new shapes for given trips
      G = [
          [shape, i, stop]
          for stop_seq, shape in shape_by_stop_seq.items()
          for i, stop in enumerate(stop_seq)
      ]
      g = pd.DataFrame(G, columns=["shape_id", "shape_pt_sequence", "stop_id"])
      g = g.merge(feed.stops[["stop_id", "stop_lon", "stop_lat"]]).sort_values(
          ["shape_id", "shape_pt_sequence"]
      )
      g = g.drop(["stop_id"], axis=1)
      g = g.rename(
          columns={"stop_lon": "shape_pt_lon", "stop_lat": "shape_pt_lat"}
      )
  
      if feed.shapes is not None and not all_trips:
          # Update feed shapes with new shapes
          feed.shapes = pd.concat([feed.shapes, g], sort=False)
      else:
          # Create all new shapes
          feed.shapes = g
  
      return feed