<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def create_footprints_gdf(polygon=None, north=None, south=None, east=None, west=None, footprint_type='building', retain_invalid=False):
 """ Get footprint data from OSM then assemble it into a GeoDataFrame. Parameters polygon : shapely Polygon or MultiPolygon geographic shape to fetch the footprints within north : float northern latitude of bounding box south : float southern latitude of bounding box east : float eastern longitude of bounding box west : float western longitude of bounding box footprint_type : string type of footprint to be downloaded. OSM tag key e.g. 'building', 'landuse', 'place', etc. retain_invalid : bool if False discard any footprints with an invalid geometry Returns ------- GeoDataFrame """


    responses = osm_footprints_download(polygon, north, south, east, west, footprint_type)

    # list of polygons to removed at the end of the process
    pop_list = []

    vertices = {}
    for response in responses:
        for result in response['elements']:
            if 'type' in result and result['type']=='node':
                vertices[result['id']] = {'lat' : result['lat'],
                                          'lon' : result['lon']}

    footprints = {}
    for response in responses:
        for result in response['elements']:
            if 'type' in result and result['type']=='way':
                nodes = result['nodes']
                try:
                    polygon = Polygon([(vertices[node]['lon'], vertices[node]['lat']) for node in nodes])
                except Exception:
                    log('Polygon has invalid geometry: {}'.format(nodes))
                footprint = {'nodes' : nodes,
                            'geometry' : polygon}

                if 'tags' in result:
                    for tag in result['tags']:
                        footprint[tag] = result['tags'][tag]

                # if polygons are untagged or not tagged with the footprint_type
                # add them to pop_list to be removed from the final dictionary
                if 'tags' not in result:
                    pop_list.append(result['id'])
                elif footprint_type not in result['tags']:
                    pop_list.append(result['id'])

                footprints[result['id']] = footprint

    # Create multipolygon footprints and pop untagged supporting polygons from footprints
    for response in responses:
        for result in response['elements']:
            if 'type' in result and result['type']=='relation':

                outer_polys = []
                inner_polys = []
                multipoly = []

                for member in result['members']:
                    if 'role' in member and member['role']=='outer':
                        outer_polys.append(member['ref'])
                    if 'role' in member and member['role']=='inner':
                        inner_polys.append(member['ref'])

                # osm allows multiple outer polygons in a relation
                for outer_poly in outer_polys:
                    temp_poly=footprints[outer_poly]['geometry']

                    for inner_poly in inner_polys:
                        temp_poly=temp_poly.difference(footprints[inner_poly]['geometry'])

                    multipoly.append(temp_poly)

                footprint = {'geometry' : MultiPolygon(multipoly)}

                if 'tags' in result:
                    for tag in result['tags']:
                        footprint[tag] = result['tags'][tag]

                footprints[result['id']] = footprint

    # remove supporting geometry from footprints dictionary
    for item in pop_list:
        footprints.pop(item)

    gdf = gpd.GeoDataFrame(footprints).T
    gdf.crs = settings.default_crs

    if not retain_invalid:
        # drop all invalid geometries
        gdf = gdf[gdf['geometry'].is_valid]

    return gdf