def _filter_features(
    gdf: gpd.GeoDataFrame,
    polygon: Polygon | MultiPolygon,
    tags: dict[str, bool | str | list[str]],
) -> gpd.GeoDataFrame:
    """
    Filter features GeoDataFrame by spatial boundaries and query tags.

    If the `polygon` and `tags` arguments are empty objects, the final
    GeoDataFrame will not be filtered accordingly.

    Parameters
    ----------
    gdf
        Original GeoDataFrame of features.
    polygon
        If not empty, the spatial boundaries to filter the GeoDataFrame.
    tags
        If not empty, the query tags to filter the GeoDataFrame.

    Returns
    -------
    gdf
        Filtered GeoDataFrame of features.
    """
    # remove any null or empty geometries then fix any invalid geometries
    gdf = gdf[~(gdf["geometry"].isna() | gdf["geometry"].is_empty)]
    gdf.loc[:, "geometry"] = gdf["geometry"].make_valid()

    # retain rows with geometries that intersect the polygon
    if polygon.is_empty:
        geom_filter = pd.Series(data=True, index=gdf.index)
    else:
        idx = utils_geo._intersect_index_quadrats(gdf["geometry"], polygon)
        geom_filter = gdf.index.isin(idx)

    # retain rows that have any of their tag filters satisfied
    if len(tags) == 0:
        tags_filter = pd.Series(data=True, index=gdf.index)
    else:
        tags_filter = pd.Series(data=False, index=gdf.index)
        for col in set(gdf.columns) & tags.keys():
            value = tags[col]
            if value is True:
                tags_filter |= gdf[col].notna()
            elif isinstance(value, str):
                tags_filter |= gdf[col] == value
            elif isinstance(value, list):
                tags_filter |= gdf[col].isin(set(value))

    # filter gdf then drop any columns with only nulls left after filtering
    gdf = gdf[geom_filter & tags_filter].dropna(axis="columns", how="all")
    if len(gdf) == 0:  # pragma: no cover
        msg = "No matching features. Check query location, tags, and log."
        raise InsufficientResponseError(msg)

    msg = f"{len(gdf):,} features in the final GeoDataFrame"
    utils.log(msg, level=lg.INFO)
    return gdf

    Filter features GeoDataFrame by spatial boundaries and query tags.

    If the `polygon` and `tags` arguments are empty objects, the final
    GeoDataFrame will not be filtered accordingly.

    Parameters
    ----------
    gdf
        Original GeoDataFrame of features.
    polygon
        If not empty, the spatial boundaries to filter the GeoDataFrame.
    tags
        If not empty, the query tags to filter the GeoDataFrame.

    Returns
    -------
    gdf
        Filtered GeoDataFrame of features.
    
osmnx/features.py