def download_file(url, dest=None, chunk_size=1024, replace="ask",
                  label="Downloading {dest_basename} ({size:.2f}MB)",
                  expected_extension=None):
    """Download a file from a given URL and display progress.

    :param dest: If the destination exists and is a directory, the filename
        will be guessed from the Content-Disposition header. If the destination
        is an existing file, the user will either be prompted to overwrite, or
        the file will be replaced (depending on the value of **replace**). If
        the destination does not exist, it will be used as the filename.
    :param int chunk_size: bytes read in at a time.
    :param replace: If `False`, an existing destination file will not be
        overwritten.
    :param label: a string which is formatted and displayed as the progress bar
        label. Variables provided include *dest_basename*, *dest*, and *size*.
    :param expected_extension: if set, the filename will be sanitized to ensure
        it has the given extension. The extension should not start with a dot
        (`.`).
    """
    dest = Path(dest or url.split("/")[-1])
    response = get(url, stream=True)
    if (dest.exists()
            and dest.is_dir()
            and "Content-Disposition" in response.headers):
        content_disposition = rfc6266.parse_requests_response(response)
        if expected_extension is not None:
            filename = content_disposition.filename_sanitized(
                expected_extension)
        filename = secure_filename(filename)
        dest = dest / filename
    if dest.exists() and not dest.is_dir():
        if (replace is False
                or replace == "ask"
                and not click.confirm("Replace {}?".format(dest))):
            return str(dest)
    size = int(response.headers.get("content-length", 0))
    label = label.format(dest=dest, dest_basename=dest.name,
                         size=size/1024.0/1024)
    with click.open_file(str(dest), "wb") as f:
        content_iter = response.iter_content(chunk_size=chunk_size)
        with click.progressbar(content_iter, length=size/1024,
                               label=label) as bar:
            for chunk in bar:
                if chunk:
                    f.write(chunk)
                    f.flush()
    return str(dest)
def _subset_sounding_data(
        example_dict, netcdf_dataset_object, example_indices_to_keep,
        field_names_to_keep, heights_to_keep_m_agl):
    """Subsets sounding data by field and height.

    :param example_dict: See doc for `_subset_radar_data`.
    :param netcdf_dataset_object: Same.
    :param example_indices_to_keep: Same.
    :param field_names_to_keep: 1-D list of field names to keep.  If None, will
        keep all fields.
    :param heights_to_keep_m_agl: 1-D numpy array of heights to keep.  If None,
        will keep all heights.
    :return: example_dict: Same as input but with the following exceptions.

    [1] Keys "sounding_field_names" and "sounding_heights_m_agl" may have
        different values.
    [2] Key "sounding_matrix" has been added.
    """

    if field_names_to_keep is None:
        field_names_to_keep = copy.deepcopy(example_dict[SOUNDING_FIELDS_KEY])
    if heights_to_keep_m_agl is None:
        heights_to_keep_m_agl = example_dict[SOUNDING_HEIGHTS_KEY] + 0

    error_checking.assert_is_numpy_array(
        numpy.array(field_names_to_keep), num_dimensions=1
    )

    heights_to_keep_m_agl = numpy.round(heights_to_keep_m_agl).astype(int)
    error_checking.assert_is_numpy_array(
        heights_to_keep_m_agl, num_dimensions=1)

    sounding_matrix = numpy.array(
        netcdf_dataset_object.variables[SOUNDING_MATRIX_KEY][
            example_indices_to_keep, ...
        ],
        dtype=float
    )

    # TODO(thunderhoser): This is a HACK.
    spfh_index = example_dict[SOUNDING_FIELDS_KEY].index(
        soundings.SPECIFIC_HUMIDITY_NAME)
    temp_index = example_dict[SOUNDING_FIELDS_KEY].index(
        soundings.TEMPERATURE_NAME)
    pressure_index = example_dict[SOUNDING_FIELDS_KEY].index(
        soundings.PRESSURE_NAME)
    theta_v_index = example_dict[SOUNDING_FIELDS_KEY].index(
        soundings.VIRTUAL_POTENTIAL_TEMPERATURE_NAME)

    sounding_matrix[..., spfh_index][
        numpy.isnan(sounding_matrix[..., spfh_index])
    ] = 0.

    nan_example_indices, nan_height_indices = numpy.where(numpy.isnan(
        sounding_matrix[..., theta_v_index]
    ))

    if len(nan_example_indices) > 0:
        this_temp_matrix_kelvins = sounding_matrix[..., temp_index][
            nan_example_indices, nan_height_indices]

        this_pressure_matrix_pa = sounding_matrix[..., pressure_index][
            nan_example_indices, nan_height_indices]

        this_thetav_matrix_kelvins = (
            temp_conversion.temperatures_to_potential_temperatures(
                temperatures_kelvins=this_temp_matrix_kelvins,
                total_pressures_pascals=this_pressure_matrix_pa)
        )

        sounding_matrix[..., theta_v_index][
            nan_example_indices, nan_height_indices
        ] = this_thetav_matrix_kelvins

    these_indices = numpy.array([
        example_dict[SOUNDING_FIELDS_KEY].index(f)
        for f in field_names_to_keep
    ], dtype=int)

    sounding_matrix = sounding_matrix[..., these_indices]

    these_indices = numpy.array([
        numpy.where(example_dict[SOUNDING_HEIGHTS_KEY] == h)[0][0]
        for h in heights_to_keep_m_agl
    ], dtype=int)

    sounding_matrix = sounding_matrix[..., these_indices, :]

    example_dict[SOUNDING_FIELDS_KEY] = field_names_to_keep
    example_dict[SOUNDING_HEIGHTS_KEY] = heights_to_keep_m_agl
    example_dict[SOUNDING_MATRIX_KEY] = sounding_matrix

    return example_dict