"""
pyrad.graph.plots_vol
=====================

Functions to plot radar volume data

.. autosummary::
    :toctree: generated/

    plot_ray
    plot_ppi
    plot_ppi_map
    plot_rhi
    plot_bscope
    plot_time_range
    plot_fixed_rng
    plot_fixed_rng_span
    plot_fixed_rng_sun
    plot_cappi
    plot_xsection
    plot_traj
    plot_rhi_contour
    plot_ppi_contour
    plot_roi_contour
    plot_rhi_profile
    plot_along_coord
    plot_field_coverage

"""

from ..io.write_data import write_histogram
from ..util.radar_utils import compute_histogram_sweep
from ..util.radar_utils import compute_quantiles_sweep, find_ang_index
from .plots import _plot_sunscan
from .plots import plot_quantiles, plot_histogram, _plot_time_range
from .plots_aux import generate_complex_range_Doppler_title
from .plots_aux import generate_fixed_rng_span_title
from .plots_aux import get_colobar_label, get_norm, generate_fixed_rng_title
import pyart
import matplotlib.pyplot as plt
from warnings import warn
from copy import deepcopy

import numpy as np


from netCDF4 import num2date

try:
    import cartopy
    from cartopy.io.img_tiles import Stamen

    _CARTOPY_AVAILABLE = True
except ImportError:
    _CARTOPY_AVAILABLE = False

try:
    import shapely

    _SHAPELY_AVAILABLE = True
except ImportError:
    warn("shapely not available")
    _SHAPELY_AVAILABLE = False

# Test whether ARM or MCH fork of pyart
if hasattr(pyart, "__ismchfork__"):
    _PYARTMCH_AVAILABLE = True
else:
    warn(
        "MCH version of pyart not installed, some features"
        + " of pyrad are not supported by ARM pyart!"
    )
    _PYARTMCH_AVAILABLE = False


import matplotlib as mpl

mpl.use("Agg")

# Increase a bit font size
mpl.rcParams.update({"font.size": 16})
mpl.rcParams.update({"font.family": "sans-serif"})


def plot_ray(
    radar,
    field_name,
    ind_ray,
    prdcfg,
    fname_list,
    titl=None,
    vmin=None,
    vmax=None,
    save_fig=True,
):
    """
    plots a ray

    Parameters
    ----------
    radar : Radar object
        object containing the radar data to plot
    field_name : str
        name of the radar field to plot
    ind_ray : int
        ray index to plot
    prdcfg : dict
        dictionary containing the product configuration
    fname_list : list of str
        list of names of the files where to store the plot
    plot_type : str
        type of plot (PPI, QUANTILES or HISTOGRAM)
    titl : str
        Plot title
    vmin, vmax : float
        min and max values of the y axis
    save_fig : bool
        if true save the figure. If false it does not close the plot and
        returns the handle to the figure

    Returns
    -------
    fname_list : list of str or
    fig, ax : tupple
        list of names of the saved plots or handle of the figure an axes

    """
    rng_km = radar.range["data"] / 1000.0
    dpi = prdcfg["ppiImageConfig"].get("dpi", 72)

    xsize = prdcfg["ppiImageConfig"]["xsize"]
    ysize = prdcfg["ppiImageConfig"]["ysize"]
    fig = plt.figure(figsize=[xsize, ysize], dpi=dpi)

    if titl is None:
        titl = generate_complex_range_Doppler_title(radar, field_name, ind_ray)
    labely = get_colobar_label(radar.fields[field_name], field_name)

    ax = fig.add_subplot(111)

    ax.plot(rng_km, radar.fields[field_name]["data"][ind_ray, :], marker="x")

    ax.set_title(titl)
    ax.set_xlabel("Range (km)")
    ax.set_ylabel(labely)
    ax.set_ylim(bottom=vmin, top=vmax)
    ax.set_xlim([rng_km[0], rng_km[-1]])

    # Turn on the grid
    ax.grid()

    # Make a tight layout
    fig.tight_layout()

    if save_fig:
        for fname in fname_list:
            fig.savefig(fname, dpi=dpi)
        plt.close(fig)

        return fname_list

    return (fig, ax)


def plot_ppi(
    radar,
    field_name,
    ind_el,
    prdcfg,
    fname_list,
    plot_type="PPI",
    titl=None,
    vmin=None,
    vmax=None,
    step=None,
    quantiles=None,
    save_fig=True,
    fname_hist=None,
):
    """
    plots a PPI

    Parameters
    ----------
    radar : Radar object
        object containing the radar data to plot
    field_name : str
        name of the radar field to plot
    ind_el : int
        sweep index to plot
    prdcfg : dict
        dictionary containing the product configuration
    fname_list : list of str
        list of names of the files where to store the plot
    plot_type : str
        type of plot (PPI, QUANTILES or HISTOGRAM)
    titl : str
        Plot title
    vmin, vmax : float
        The minimum and maximum value. If None the scale is going to be
        obtained from the Py-ART config file.
    step : float
        step for histogram plotting
    quantiles : float array
        quantiles to plot
    save_fig : bool
        if true save the figure. If false it does not close the plot and
        returns the handle to the figure
    fname_hist : str or None
        If not None, the name of a text file where the histogram will be stored

    Returns
    -------
    fname_list : list of str or
    fig, ax : tupple
        list of names of the saved plots or handle of the figure an axes

    """
    if plot_type == "PPI":
        dpi = prdcfg["ppiImageConfig"].get("dpi", 72)

        norm = None
        ticks = None
        ticklabs = None
        if vmin is None or vmax is None:
            norm, ticks, ticklabs = get_norm(
                field_name, field_dict=radar.fields[field_name]
            )
            vmin = None
            vmax = None

        xsize = prdcfg["ppiImageConfig"]["xsize"]
        ysize = prdcfg["ppiImageConfig"]["ysize"]
        fig = plt.figure(figsize=[xsize, ysize], dpi=dpi)

        ax = fig.add_subplot(111, aspect="equal")

        display = pyart.graph.RadarDisplay(radar)
        display.plot_ppi(
            field_name,
            title=titl,
            sweep=ind_el,
            norm=norm,
            ticks=ticks,
            vmin=vmin,
            vmax=vmax,
            ticklabs=ticklabs,
            fig=fig,
            ax=ax,
        )

        display.set_limits(
            ylim=[prdcfg["ppiImageConfig"]["ymin"], prdcfg["ppiImageConfig"]["ymax"]],
            xlim=[prdcfg["ppiImageConfig"]["xmin"], prdcfg["ppiImageConfig"]["xmax"]],
            ax=ax,
        )
        if "rngRing" in prdcfg["ppiImageConfig"]:
            if prdcfg["ppiImageConfig"]["rngRing"] > 0:
                display.plot_range_rings(
                    np.arange(
                        0.0,
                        radar.range["data"][-1] / 1000.0,
                        prdcfg["ppiImageConfig"]["rngRing"],
                    ),
                    ax=ax,
                )
        display.plot_cross_hair(5.0, ax=ax)

        # Turn on the grid
        ax.grid()

        # Make a tight layout
        fig.tight_layout()

        if save_fig:
            for fname in fname_list:
                fig.savefig(fname, dpi=dpi)
            plt.close(fig)

            return fname_list

        return (fig, ax)

    if plot_type == "QUANTILES":
        quantiles, values = compute_quantiles_sweep(
            radar.fields[field_name]["data"],
            radar.sweep_start_ray_index["data"][ind_el],
            radar.sweep_end_ray_index["data"][ind_el],
            quantiles=quantiles,
        )

        titl = pyart.graph.common.generate_title(radar, field_name, ind_el)
        labely = get_colobar_label(radar.fields[field_name], field_name)

        plot_quantiles(
            quantiles, values, fname_list, labelx="quantile", labely=labely, titl=titl
        )

    elif plot_type == "HISTOGRAM":
        bins, values = compute_histogram_sweep(
            radar.fields[field_name]["data"],
            radar.sweep_start_ray_index["data"][ind_el],
            radar.sweep_end_ray_index["data"][ind_el],
            field_name,
            step=step,
        )

        titl = pyart.graph.common.generate_title(radar, field_name, ind_el)
        labelx = get_colobar_label(radar.fields[field_name], field_name)

        plot_histogram(
            bins,
            values,
            fname_list,
            labelx=labelx,
            labely="Number of Samples",
            titl=titl,
        )
        if fname_hist is not None:
            hist, _ = np.histogram(values, bins=bins)
            write_histogram(bins, hist, fname_hist, step=step)
    else:
        warn("Unknown plot type " + plot_type)

    return fname_list


def plot_ppi_map(
    radar, field_name, ind_el, prdcfg, fname_list, vmin=None, vmax=None, save_fig=True
):
    """
    plots a PPI on a geographic map

    Parameters
    ----------
    radar : Radar object
        object containing the radar data to plot
    field_name : str
        name of the radar field to plot
    ind_el : int
        sweep index to plot
    prdcfg : dict
        dictionary containing the product configuration
    fname_list : list of str
        list of names of the files where to store the plot
    save_fig : bool
        if true save the figure. If false it does not close the plot and
        returns the handle to the figure

    Returns
    -------
    fname_list : list of str or
    fig, ax, display : tupple
        list of names of the saved plots or handle of the figure an axes

    """
    if not _CARTOPY_AVAILABLE:
        warn("Unable to plot PPI map. Missing shapely cartopy module")
        return None

    dpi = prdcfg["ppiMapImageConfig"].get("dpi", 72)

    norm, ticks, ticklabs = get_norm(field_name, field_dict=radar.fields[field_name])

    xsize = prdcfg["ppiMapImageConfig"]["xsize"]
    ysize = prdcfg["ppiMapImageConfig"]["ysize"]
    lonstep = prdcfg["ppiMapImageConfig"].get("lonstep", 0.5)
    latstep = prdcfg["ppiMapImageConfig"].get("latstep", 0.5)
    min_lon = prdcfg["ppiMapImageConfig"].get("lonmin", 2.5)
    max_lon = prdcfg["ppiMapImageConfig"].get("lonmax", 12.5)
    min_lat = prdcfg["ppiMapImageConfig"].get("latmin", 43.5)
    max_lat = prdcfg["ppiMapImageConfig"].get("latmax", 49.5)
    exact_limits = prdcfg["ppiMapImageConfig"].get("exact_limits", True)
    resolution = prdcfg["ppiMapImageConfig"].get("mapres", "110m")
    if resolution not in ("110m", "50m", "10m"):
        warn("Unknown map resolution: " + resolution)
        resolution = "110m"
    background_zoom = prdcfg["ppiMapImageConfig"].get("background_zoom", 8)

    if exact_limits:
        lon_lines = np.arange(min_lon, max_lon + lonstep, lonstep)
        lat_lines = np.arange(min_lat, max_lat + latstep, latstep)
    else:
        lon_lines = np.arange(np.floor(min_lon), np.ceil(max_lon) + 1, lonstep)
        lat_lines = np.arange(np.floor(min_lat), np.ceil(max_lat) + 1, latstep)

    fig = plt.figure(figsize=[xsize, ysize], dpi=dpi)

    projection = cartopy.crs.PlateCarree()
    display_map = pyart.graph.RadarMapDisplay(radar)
    if _PYARTMCH_AVAILABLE:
        display_map.plot_ppi_map(
            field_name,
            vmin=vmin,
            vmax=vmax,
            sweep=ind_el,
            norm=norm,
            ticks=ticks,
            ticklabs=ticklabs,
            min_lon=min_lon,
            max_lon=max_lon,
            min_lat=min_lat,
            max_lat=max_lat,
            lat_lines=lat_lines,
            lon_lines=lon_lines,
            single_grid_lines_labels=True,
            projection=projection,
            fig=fig,
            embellish=False,
            colorbar_flag=True,
            alpha=1,
        )
    else:
        display_map.plot_ppi_map(
            field_name,
            vmin=vmin,
            vmax=vmax,
            sweep=ind_el,
            norm=norm,
            ticks=ticks,
            ticklabs=ticklabs,
            min_lon=min_lon,
            max_lon=max_lon,
            min_lat=min_lat,
            max_lat=max_lat,
            lat_lines=lat_lines,
            lon_lines=lon_lines,
            projection=projection,
            fig=fig,
            embellish=False,
            colorbar_flag=True,
            alpha=1,
        )
    ax = display_map.ax
    if "maps" in prdcfg["ppiMapImageConfig"]:
        if "relief" in prdcfg["ppiMapImageConfig"]["maps"]:
            tiler = Stamen("terrain-background")
            projection = tiler.crs
            fig.delaxes(ax)
            ax = fig.add_subplot(111, projection=projection)
            warn(
                "The projection of the image is set to that of the "
                + "background map, i.e. "
                + str(projection),
                UserWarning,
            )

        for cartomap in prdcfg["ppiMapImageConfig"]["maps"]:
            if cartomap == "relief":
                ax.add_image(tiler, background_zoom)
            if cartomap == "countries":
                # add countries
                countries = cartopy.feature.NaturalEarthFeature(
                    category="cultural",
                    name="admin_0_countries",
                    scale=resolution,
                    facecolor="none",
                )
                ax.add_feature(countries, edgecolor="black")
            elif cartomap == "provinces":
                # Create a feature for States/Admin 1 regions at
                # 1:resolution from Natural Earth
                states_provinces = cartopy.feature.NaturalEarthFeature(
                    category="cultural",
                    name="admin_1_states_provinces_lines",
                    scale=resolution,
                    facecolor="none",
                )
                ax.add_feature(states_provinces, edgecolor="gray")
            elif cartomap == "urban_areas" and resolution in ("10m", "50m"):
                urban_areas = cartopy.feature.NaturalEarthFeature(
                    category="cultural", name="urban_areas", scale=resolution
                )
                ax.add_feature(
                    urban_areas, edgecolor="brown", facecolor="brown", alpha=0.25
                )
            elif cartomap == "roads" and resolution == "10m":
                roads = cartopy.feature.NaturalEarthFeature(
                    category="cultural", name="roads", scale=resolution
                )
                ax.add_feature(roads, edgecolor="red", facecolor="none")
            elif cartomap == "railroads" and resolution == "10m":
                railroads = cartopy.feature.NaturalEarthFeature(
                    category="cultural", name="railroads", scale=resolution
                )
                ax.add_feature(
                    railroads, edgecolor="green", facecolor="none", linestyle=":"
                )
            elif cartomap == "coastlines":
                ax.coastlines(resolution=resolution)
            elif cartomap == "lakes":
                # add lakes
                lakes = cartopy.feature.NaturalEarthFeature(
                    category="physical", name="lakes", scale=resolution
                )
                ax.add_feature(lakes, edgecolor="blue", facecolor="blue", alpha=0.25)
            elif resolution == "10m" and cartomap == "lakes_europe":
                lakes_europe = cartopy.feature.NaturalEarthFeature(
                    category="physical", name="lakes_europe", scale=resolution
                )
                ax.add_feature(
                    lakes_europe, edgecolor="blue", facecolor="blue", alpha=0.25
                )
            elif cartomap == "rivers":
                # add rivers
                rivers = cartopy.feature.NaturalEarthFeature(
                    category="physical",
                    name="rivers_lake_centerlines",
                    scale=resolution,
                )
                ax.add_feature(rivers, edgecolor="blue", facecolor="none")
            elif resolution == "10m" and cartomap == "rivers_europe":
                rivers_europe = cartopy.feature.NaturalEarthFeature(
                    category="physical", name="rivers_europe", scale=resolution
                )
                ax.add_feature(rivers_europe, edgecolor="blue", facecolor="none")
            else:
                warn(
                    "cartomap "
                    + cartomap
                    + " for resolution "
                    + resolution
                    + " not available"
                )

    if "rngRing" in prdcfg["ppiMapImageConfig"]:
        if prdcfg["ppiMapImageConfig"]["rngRing"] > 0:
            rng_rings = np.arange(
                0.0,
                radar.range["data"][-1] / 1000.0,
                prdcfg["ppiMapImageConfig"]["rngRing"],
            )
            for rng_ring in rng_rings:
                display_map.plot_range_ring(rng_ring, ax=ax)

    if save_fig:
        for fname in fname_list:
            fig.savefig(fname, dpi=dpi)
        plt.close(fig)

        return fname_list

    return (fig, ax, display_map)


def plot_rhi(
    radar,
    field_name,
    ind_az,
    prdcfg,
    fname_list,
    plot_type="RHI",
    titl=None,
    vmin=None,
    vmax=None,
    step=None,
    quantiles=None,
    save_fig=True,
):
    """
    plots an RHI

    Parameters
    ----------
    radar : Radar object
        object containing the radar data to plot
    field_name : str
        name of the radar field to plot
    ind_az : int
        sweep index to plot
    prdcfg : dict
        dictionary containing the product configuration
    fname_list : list of str
        list of names of the files where to store the plot
    plot_type : str
        type of plot (PPI, QUANTILES or HISTOGRAM)
    titl : str
        Plot title
    vmin, vmax : float
        The minimum and maximum value. If None the scale is going to be
        obtained from the Py-ART config file.
    step : float
        step for histogram plotting
    quantiles : float array
        quantiles to plot
    save_fig : bool
        if true save the figure. If false it does not close the plot and
        returns the handle to the figure

    Returns
    -------
    fname_list : list of str or
    fig, ax : tupple
        list of names of the saved plots or handle of the figure an axes

    """
    if plot_type == "RHI":
        dpi = prdcfg["ppiImageConfig"].get("dpi", 72)

        norm = None
        ticks = None
        ticklabs = None
        if vmin is None or vmax is None:
            norm, ticks, ticklabs = get_norm(
                field_name, field_dict=radar.fields[field_name]
            )
            vmin = None
            vmax = None

        xsize = prdcfg["rhiImageConfig"]["xsize"]
        ysize = prdcfg["rhiImageConfig"]["ysize"]
        if "aspect" in prdcfg["rhiImageConfig"]:
            aspect = prdcfg["rhiImageConfig"]["aspect"]
        else:
            aspect = "equal"
        fig = plt.figure(figsize=[xsize, ysize], dpi=dpi)
        ax = fig.add_subplot(111, aspect=aspect)
        display = pyart.graph.RadarDisplay(radar)
        display.plot_rhi(
            field_name,
            title=titl,
            sweep=ind_az,
            norm=norm,
            ticks=ticks,
            ticklabs=ticklabs,
            vmin=vmin,
            vmax=vmax,
            colorbar_orient="horizontal",
            fig=fig,
            ax=ax,
        )
        display.set_limits(
            ylim=[prdcfg["rhiImageConfig"]["ymin"], prdcfg["rhiImageConfig"]["ymax"]],
            xlim=[prdcfg["rhiImageConfig"]["xmin"], prdcfg["rhiImageConfig"]["xmax"]],
            ax=ax,
        )
        display.plot_cross_hair(5.0, ax=ax)

        # Turn on the grid
        ax.grid()

        # Make a tight layout
        fig.tight_layout()

        if save_fig:
            for fname in fname_list:
                fig.savefig(fname, dpi=dpi)
            plt.close(fig)

            return fname_list

        return (fig, ax)

    if plot_type == "QUANTILES":
        quantiles, values = compute_quantiles_sweep(
            radar.fields[field_name]["data"],
            radar.sweep_start_ray_index["data"][ind_az],
            radar.sweep_end_ray_index["data"][ind_az],
            quantiles=quantiles,
        )

        if titl is None:
            titl = pyart.graph.common.generate_title(radar, field_name, ind_az)
        labely = get_colobar_label(radar.fields[field_name], field_name)

        plot_quantiles(
            quantiles, values, fname_list, labelx="quantile", labely=labely, titl=titl
        )

    elif plot_type == "HISTOGRAM":
        bins, values = compute_histogram_sweep(
            radar.fields[field_name]["data"],
            radar.sweep_start_ray_index["data"][ind_az],
            radar.sweep_end_ray_index["data"][ind_az],
            field_name,
            step=step,
        )

        if titl is None:
            titl = pyart.graph.common.generate_title(radar, field_name, ind_az)
        labelx = get_colobar_label(radar.fields[field_name], field_name)

        plot_histogram(
            bins,
            values,
            fname_list,
            labelx=labelx,
            labely="Number of Samples",
            titl=titl,
        )
    else:
        warn("Unknown plot type " + plot_type)

    return fname_list


def plot_bscope(
    radar,
    field_name,
    ind_sweep,
    prdcfg,
    fname_list,
    vmin=None,
    vmax=None,
    ray_dim="ang",
    xaxis_rng=True,
):
    """
    plots a B-Scope (angle-range representation)

    Parameters
    ----------
    radar : Radar object
        object containing the radar data to plot
    field_name : str
        name of the radar field to plot
    ind_sweep : int
        sweep index to plot
    prdcfg : dict
        dictionary containing the product configuration
    fname_list : list of str
        list of names of the files where to store the plot
    vmin, vmax : float
        Min and max values of the colorbar
    ray_dim : str
        the ray dimension. Can be 'ang' or 'time'
    xaxis : bool
        if true the range will be in the x-axis. Otherwise it will be in the
        y-axis.

    Returns
    -------
    fname_list : list of str
        list of names of the created plots

    """
    norm = None
    ticks = None
    ticklabs = None
    if vmin is None or vmax is None:
        norm, ticks, ticklabs = get_norm(
            field_name, field_dict=radar.fields[field_name]
        )

        if norm is None:  # if norm is set do not override with vmin/vmax
            vmin, vmax = pyart.config.get_field_limits(field_name)

    radar_aux = radar.extract_sweeps([ind_sweep])
    if ray_dim == "ang":
        if radar_aux.scan_type == "ppi":
            ray = np.sort(radar_aux.azimuth["data"])
            ind_ray = np.argsort(radar_aux.azimuth["data"])
            field = radar_aux.fields[field_name]["data"][ind_ray, :]
            ray_label = "azimuth angle (degrees)"
        elif radar_aux.scan_type == "rhi":
            ray = np.sort(radar_aux.elevation["data"])
            ind_ray = np.argsort(radar_aux.elevation["data"])
            field = radar_aux.fields[field_name]["data"][ind_ray, :]
            ray_label = "elevation angle (degrees)"
        else:
            field = radar_aux.fields[field_name]["data"]
            ray = np.array(range(radar_aux.nrays))
            ray_label = "ray number"
    else:
        ray = np.sort(radar_aux.time["data"])
        start_time = ray[0]
        ray -= start_time
        ind_ray = np.argsort(radar_aux.time["data"])
        field = radar_aux.fields[field_name]["data"][ind_ray, :]
        sweep_start_time = num2date(
            start_time, radar_aux.time["units"], radar_aux.time["calendar"]
        )
        ray_label = (
            "time [s from " + sweep_start_time.strftime("%Y-%m-%d %H:%M:%S") + " UTC]"
        )

    # display data
    titl = pyart.graph.common.generate_title(radar_aux, field_name, 0)
    label = get_colobar_label(radar_aux.fields[field_name], field_name)

    dpi = prdcfg["ppiImageConfig"].get("dpi", 72)

    fig = plt.figure(
        figsize=[prdcfg["ppiImageConfig"]["xsize"], prdcfg["ppiImageConfig"]["ysize"]],
        dpi=dpi,
    )
    ax = fig.add_subplot(111)
    if radar_aux.ngates == 1:
        ax.plot(ray, field, "bx", figure=fig)
        ax.set_xlabel(ray_label)
        ax.set_ylabel(label)
        ax.set_title(titl)
    else:
        cmap = pyart.config.get_field_colormap(field_name)

        rng_aux = radar_aux.range["data"] / 1000.0
        rng_res = rng_aux[1] - rng_aux[0]
        rng_aux = np.append(rng_aux - rng_res / 2.0, rng_aux[-1] + rng_res / 2.0)
        rng_label = "Range (km)"

        ray_res = np.ma.median(ray[1:] - ray[:-1])
        ray_aux = np.append(ray - ray_res / 2, ray[-1] + ray_res / 2)

        if xaxis_rng:
            cax = ax.pcolormesh(
                rng_aux, ray_aux, field, cmap=cmap, vmin=vmin, vmax=vmax, norm=norm
            )
            ax.set_xlabel(rng_label)
            ax.set_ylabel(ray_label)
        else:
            cax = ax.pcolormesh(
                ray_aux,
                rng_aux,
                np.ma.transpose(field),
                cmap=cmap,
                vmin=vmin,
                vmax=vmax,
                norm=norm,
            )
            ax.set_xlabel(ray_label)
            ax.set_ylabel(rng_label)
        ax.set_title(titl)

        cb = fig.colorbar(cax)
        if ticks is not None:
            cb.set_ticks(ticks)
        if ticklabs:
            cb.set_ticklabels(ticklabs)
        cb.set_label(label)

    # Make a tight layout
    fig.tight_layout()

    for fname in fname_list:
        fig.savefig(fname, dpi=dpi)
    plt.close(fig)

    return fname_list


def plot_time_range(
    radar,
    field_name,
    ind_sweep,
    prdcfg,
    fname_list,
    vmin=None,
    vmax=None,
    ylabel="range (Km)",
):
    """
    plots a time-range plot

    Parameters
    ----------
    radar : Radar object
        object containing the radar data to plot
    field_name : str
        name of the radar field to plot
    ind_sweep : int
        sweep index to plot
    prdcfg : dict
        dictionary containing the product configuration
    fname_list : list of str
        list of names of the files where to store the plot
    vmin, vmax : float
        Min and max values of the colorbar
    ylabel : str
        The y-axis label

    Returns
    -------
    fname_list : list of str
        list of names of the created plots

    """
    radar_aux = radar.extract_sweeps([ind_sweep])
    field = radar_aux.fields[field_name]["data"]

    # display data
    titl = pyart.graph.common.generate_title(radar_aux, field_name, ind_sweep)
    dpi = prdcfg["ppiImageConfig"].get("dpi", 72)
    xsize = prdcfg["ppiImageConfig"].get("xsize", 10)
    ysize = prdcfg["ppiImageConfig"].get("ysize", 8)

    rng_aux = deepcopy(radar_aux.range["data"])
    if ylabel == "range (km)":
        rng_aux /= 1000.0
    rng_res = rng_aux[1] - rng_aux[0]
    rng_aux = np.append(rng_aux - rng_res / 2.0, rng_aux[-1] + rng_res / 2.0)

    time_res = np.mean(radar_aux.time["data"][1:] - radar_aux.time["data"][0:-1])
    time_aux = np.append(radar_aux.time["data"], radar_aux.time["data"][-1] + time_res)
    return _plot_time_range(
        time_aux,
        rng_aux,
        field,
        field_name,
        fname_list,
        titl=titl,
        ylabel=ylabel,
        vmin=vmin,
        vmax=vmax,
        figsize=[xsize, ysize],
        dpi=dpi,
    )


def plot_fixed_rng(
    radar,
    field_name,
    prdcfg,
    fname_list,
    azi_res=None,
    ele_res=None,
    ang_tol=1.0,
    vmin=None,
    vmax=None,
):
    """
    plots a fixed range plot

    Parameters
    ----------
    radar : radar object
        The radar object containing the fixed range data
    field_name : str
        The name of the field to plot
    prdcfg : dict
        dictionary containing the product configuration
    fname_list : list of str
        list of names of the files where to store the plot
    azi_res, ele_res : float
        The nominal azimuth and elevation angle resolution [deg]
    ang_tol : float
        The tolerance between the nominal and the actual radar angle
    vmin, vmax : float
        Min and Max values of the color scale. If None it is going to be taken
        from the Py-ART config files

    Returns
    -------
    fname_list : list of str
        list of names of the created plots

    """
    # Get radar azimuth angles within limits taking as reference
    # the first elevation angle
    fixed_rng = radar.range["data"][0]

    if radar.scan_type == "ppi":
        ele_vec = np.sort(radar.fixed_angle["data"])
        azi_vec = np.sort(
            radar.azimuth["data"][
                radar.sweep_start_ray_index["data"][0] : radar.sweep_end_ray_index[
                    "data"
                ][0]
                + 1
            ]
        )
    else:
        ele_vec = np.sort(
            radar.elevation["data"][
                radar.sweep_start_ray_index["data"][0] : radar.sweep_end_ray_index[
                    "data"
                ][0]
                + 1
            ]
        )
        azi_vec = np.sort(radar.fixed_angle["data"])

    # put data in a regular 2D grid
    field_2D = np.ma.masked_all((azi_vec.size, ele_vec.size))
    sweep_start_inds = radar.sweep_start_ray_index["data"]
    sweep_end_inds = radar.sweep_end_ray_index["data"]

    if radar.scan_type == "ppi":
        for j, ele in enumerate(ele_vec):
            field_1D = radar.fields[field_name]["data"][
                sweep_start_inds[j] : sweep_end_inds[j] + 1
            ]
            azi_1D = radar.azimuth["data"][sweep_start_inds[j] : sweep_end_inds[j] + 1]

            for i, azi in enumerate(azi_vec):
                ind = find_ang_index(azi_1D, azi, ang_tol=ang_tol)

                if ind is None:
                    continue
                try:
                    field_2D[i, j] = field_1D[ind]
                except ValueError:
                    field_2D[i, j] = field_1D[ind][0]
    else:
        for i, azi in enumerate(azi_vec):
            field_1D = radar.fields[field_name]["data"][
                sweep_start_inds[i] : sweep_end_inds[i] + 1
            ]
            ele_1D = radar.elevation["data"][
                sweep_start_inds[i] : sweep_end_inds[i] + 1
            ]

            for j, ele in enumerate(ele_vec):
                ind = find_ang_index(ele_1D, ele, ang_tol=ang_tol)
                if ind is None:
                    continue
                field_2D[i, j] = field_1D[ind]

    # get limits of angle bins
    if radar.scan_type == "ppi":
        if azi_res is None:
            azi_res = np.median(azi_vec[1:] - azi_vec[0:-1])
            if radar.ray_angle_res is not None:
                azi_res = np.min([radar.ray_angle_res["data"][0], azi_res])

        azi_vec = np.append(azi_vec - azi_res / 2.0, azi_vec[-1] + azi_res / 2.0)

        if ele_res is None:
            ele_res = np.median(ele_vec[1:] - ele_vec[0:-1])
            if radar.instrument_parameters is not None:
                if "radar_beam_width_h" in radar.instrument_parameters:
                    bwidth = radar.instrument_parameters["radar_beam_width_h"]["data"][
                        0
                    ]
                    ele_res = np.min([bwidth, ele_res])
                elif "radar_beam_width_v" in radar.instrument_parameters:
                    bwidth = radar.instrument_parameters["radar_beam_width_v"]["data"][
                        0
                    ]
                    ele_res = np.min([bwidth, ele_res])

        ele_vec = np.append(ele_vec - ele_res / 2.0, ele_vec[-1] + ele_res / 2.0)
    else:
        if ele_res is None:
            ele_res = np.median(ele_vec[1:] - ele_vec[0:-1])
            if radar.ray_angle_res is not None:
                ele_res = np.min([radar.ray_angle_res["data"][0], ele_res])

        ele_vec = np.append(ele_vec - ele_res / 2.0, ele_vec[-1] + ele_res / 2.0)

        if azi_res is None:
            azi_res = np.median(azi_vec[1:] - azi_vec[0:-1])
            if radar.instrument_parameters is not None:
                if "radar_beam_width_h" in radar.instrument_parameters:
                    bwidth = radar.instrument_parameters["radar_beam_width_h"]["data"][
                        0
                    ]
                    azi_res = np.min([bwidth, azi_res])
                elif "radar_beam_width_v" in radar.instrument_parameters:
                    bwidth = radar.instrument_parameters["radar_beam_width_v"]["data"][
                        0
                    ]
                    azi_res = np.min([bwidth, azi_res])

        azi_vec = np.append(azi_vec - azi_res / 2.0, azi_vec[-1] + azi_res / 2.0)

    titl = generate_fixed_rng_title(radar, field_name, fixed_rng)

    dpi = prdcfg["ppiImageConfig"].get("dpi", 72)
    xsize = prdcfg["ppiImageConfig"].get("xsize", 10)
    ysize = prdcfg["ppiImageConfig"].get("ysize", 8)

    return _plot_time_range(
        azi_vec,
        ele_vec,
        field_2D,
        field_name,
        fname_list,
        titl=titl,
        xlabel="azimuth (deg)",
        ylabel="elevation (deg)",
        figsize=[xsize, ysize],
        vmin=vmin,
        vmax=vmax,
        dpi=dpi,
    )


def plot_fixed_rng_span(
    radar,
    field_name,
    prdcfg,
    fname_list,
    azi_res=None,
    ele_res=None,
    ang_tol=1.0,
    stat="max",
):
    """
    plots a fixed range plot

    Parameters
    ----------
    radar : radar object
        The radar object containing the fixed range data
    field_name : str
        The name of the field to plot
    prdcfg : dict
        dictionary containing the product configuration
    fname_list : list of str
        list of names of the files where to store the plot
    azi_res, ele_res : float
        The nominal azimuth and elevation angle resolution [deg]
    ang_tol : float
        The tolerance between the nominal and the actual radar angle

    Returns
    -------
    fname_list : list of str
        list of names of the created plots

    """
    # Get radar azimuth angles within limits taking as reference
    # the first elevation angle
    if radar.scan_type == "ppi":
        ele_vec = np.sort(radar.fixed_angle["data"])
        azi_vec = np.sort(
            radar.azimuth["data"][
                radar.sweep_start_ray_index["data"][0] : radar.sweep_end_ray_index[
                    "data"
                ][0]
                + 1
            ]
        )
    else:
        ele_vec = np.sort(
            radar.elevation["data"][
                radar.sweep_start_ray_index["data"][0] : radar.sweep_end_ray_index[
                    "data"
                ][0]
                + 1
            ]
        )
        azi_vec = np.sort(radar.fixed_angle["data"])

    # put data in a regular 2D grid
    field_2D = np.ma.masked_all((azi_vec.size, ele_vec.size))
    rng_2D = np.ma.masked_all((azi_vec.size, ele_vec.size))
    sweep_start_inds = radar.sweep_start_ray_index["data"]
    sweep_end_inds = radar.sweep_end_ray_index["data"]

    if radar.scan_type == "ppi":
        for j, ele in enumerate(ele_vec):
            field = radar.fields[field_name]["data"][
                sweep_start_inds[j] : sweep_end_inds[j] + 1, :
            ]

            if stat == "max":
                field_1D = np.ma.max(field, axis=-1)
                ind = np.ma.argmax(field, axis=-1)
                rng_1D = radar.range["data"][ind]
            elif stat == "min":
                field_1D = np.ma.min(field, axis=-1)
                ind = np.ma.argmin(field, axis=-1)
                rng_1D = radar.range["data"][ind]
            elif stat == "mean":
                field_1D = np.ma.mean(field, axis=-1)
                mid_rng = radar.range["data"][int(radar.ngates / 2)]
                rng_1D = np.ma.zeros(np.shape(field_1D)) + mid_rng
            elif stat == "median":
                field_1D = np.ma.median(field, axis=-1)
                mid_rng = radar.range["data"][int(radar.ngates / 2)]
                rng_1D = np.ma.zeros(np.shape(field_1D)) + mid_rng

            azi_1D = radar.azimuth["data"][sweep_start_inds[j] : sweep_end_inds[j] + 1]

            for i, azi in enumerate(azi_vec):
                ind = find_ang_index(azi_1D, azi, ang_tol=ang_tol)
                if ind is None:
                    continue
                field_2D[i, j] = field_1D[ind]
                rng_2D[i, j] = rng_1D[ind]
    else:
        for i, azi in enumerate(azi_vec):
            field = radar.fields[field_name]["data"][
                sweep_start_inds[i] : sweep_end_inds[i] + 1, :
            ]

            if stat == "max":
                field_1D = np.ma.max(field, axis=-1)
                ind = np.ma.argmax(field, axis=-1)
                rng_1D = radar.range["data"][ind]
            elif stat == "min":
                field_1D = np.ma.min(field, axis=-1)
                ind = np.ma.argmin(field, axis=-1)
                rng_1D = radar.range["data"][ind]
            elif stat == "mean":
                field_1D = np.ma.mean(field, axis=-1)
                mid_rng = radar.range["data"][int(radar.ngates / 2)]
                rng_1D = np.ma.zeros(np.shape(field_1D)) + mid_rng
            elif stat == "median":
                field_1D = np.ma.median(field, axis=-1)
                mid_rng = radar.range["data"][int(radar.ngates / 2)]
                rng_1D = np.ma.zeros(np.shape(field_1D)) + mid_rng

            ele_1D = radar.elevation["data"][
                sweep_start_inds[i] : sweep_end_inds[i] + 1
            ]

            for j, ele in enumerate(ele_vec):
                ind = find_ang_index(ele_1D, ele, ang_tol=ang_tol)
                if ind is None:
                    continue
                field_2D[i, j] = field_1D[ind]
                rng_2D[i, j] = rng_1D[ind]

    # get limits of angle bins
    if radar.scan_type == "ppi":
        if azi_res is None:
            azi_res = np.median(azi_vec[1:] - azi_vec[0:-1])
            if radar.ray_angle_res is not None:
                azi_res = np.min([radar.ray_angle_res["data"][0], azi_res])

        azi_vec = np.append(azi_vec - azi_res / 2.0, azi_vec[-1] + azi_res / 2.0)

        if ele_res is None:
            ele_res = np.median(ele_vec[1:] - ele_vec[0:-1])
            if radar.instrument_parameters is not None:
                if "radar_beam_width_h" in radar.instrument_parameters:
                    bwidth = radar.instrument_parameters["radar_beam_width_h"]["data"][
                        0
                    ]
                    ele_res = np.min([bwidth, ele_res])
                elif "radar_beam_width_v" in radar.instrument_parameters:
                    bwidth = radar.instrument_parameters["radar_beam_width_v"]["data"][
                        0
                    ]
                    ele_res = np.min([bwidth, ele_res])

        ele_vec = np.append(ele_vec - ele_res / 2.0, ele_vec[-1] + ele_res / 2.0)
    else:
        if ele_res is None:
            ele_res = np.median(ele_vec[1:] - ele_vec[0:-1])
            if radar.ray_angle_res is not None:
                ele_res = np.min([radar.ray_angle_res["data"][0], ele_res])

        ele_vec = np.append(ele_vec - ele_res / 2.0, ele_vec[-1] + ele_res / 2.0)

        if azi_res is None:
            azi_res = np.median(azi_vec[1:] - azi_vec[0:-1])
            if radar.instrument_parameters is not None:
                if "radar_beam_width_h" in radar.instrument_parameters:
                    bwidth = radar.instrument_parameters["radar_beam_width_h"]["data"][
                        0
                    ]
                    azi_res = np.min([bwidth, azi_res])
                elif "radar_beam_width_v" in radar.instrument_parameters:
                    bwidth = radar.instrument_parameters["radar_beam_width_v"]["data"][
                        0
                    ]
                    azi_res = np.min([bwidth, azi_res])

        azi_vec = np.append(azi_vec - azi_res / 2.0, azi_vec[-1] + azi_res / 2.0)

    titl = generate_fixed_rng_span_title(radar, field_name, stat)

    dpi = prdcfg["ppiImageConfig"].get("dpi", 72)
    xsize = prdcfg["ppiImageConfig"].get("xsize", 10)
    ysize = prdcfg["ppiImageConfig"].get("ysize", 8)

    fname_rng_list = []
    for fname in fname_list:
        fname_rng_list.append(
            fname.rsplit(".", 1)[0] + "_RNG." + fname.rsplit(".", 1)[1]
        )

    _plot_time_range(
        azi_vec,
        ele_vec,
        rng_2D,
        "radar_range",
        fname_rng_list,
        titl=titl,
        xlabel="azimuth (deg)",
        ylabel="elevation (deg)",
        figsize=[xsize, ysize],
        dpi=dpi,
    )

    return _plot_time_range(
        azi_vec,
        ele_vec,
        field_2D,
        field_name,
        fname_list,
        titl=titl,
        xlabel="azimuth (deg)",
        ylabel="elevation (deg)",
        figsize=[xsize, ysize],
        dpi=dpi,
    )


def plot_fixed_rng_sun(
    radar,
    field_name,
    sun_hits,
    prdcfg,
    fname_list,
    azi_res=None,
    ele_res=None,
    ang_tol=1.0,
    vmin=None,
    vmax=None,
):
    """
    plots a fixed range plot

    Parameters
    ----------
    radar : radar object
        The radar object containing the fixed range data
    field_name : str
        The name of the field to plot
    sun_hits: dict
        dictionary containing the sun hits data
    prdcfg : dict
        dictionary containing the product configuration
    fname_list : list of str
        list of names of the files where to store the plot
    azi_res, ele_res : float
        The nominal azimuth and elevation angle resolution [deg]
    ang_tol : float
        The tolerance between the nominal and the actual radar angle
    vmin, vmax : float
        Min and Max values of the color scale. If None it is going to be taken
        from the Py-ART config files

    Returns
    -------
    fname_list : list of str
        list of names of the created plots

    """
    # Get radar azimuth angles within limits taking as reference
    # the first elevation angle
    fixed_rng = radar.range["data"][0]

    if radar.scan_type == "ppi":
        ele_vec = np.sort(radar.fixed_angle["data"])
        azi_vec = np.sort(
            radar.azimuth["data"][
                radar.sweep_start_ray_index["data"][0] : radar.sweep_end_ray_index[
                    "data"
                ][0]
                + 1
            ]
        )
    else:
        ele_vec = np.sort(
            radar.elevation["data"][
                radar.sweep_start_ray_index["data"][0] : radar.sweep_end_ray_index[
                    "data"
                ][0]
                + 1
            ]
        )
        azi_vec = np.sort(radar.fixed_angle["data"])

    # put data in a regular 2D grid
    field_2D = np.ma.masked_all((azi_vec.size, ele_vec.size))
    sweep_start_inds = radar.sweep_start_ray_index["data"]
    sweep_end_inds = radar.sweep_end_ray_index["data"]

    if radar.scan_type == "ppi":
        for j, ele in enumerate(ele_vec):
            field_1D = radar.fields[field_name]["data"][
                sweep_start_inds[j] : sweep_end_inds[j] + 1
            ]
            azi_1D = radar.azimuth["data"][sweep_start_inds[j] : sweep_end_inds[j] + 1]

            for i, azi in enumerate(azi_vec):
                ind = find_ang_index(azi_1D, azi, ang_tol=ang_tol)
                # print('IND: ',ind)
                if ind is None:
                    continue
                # print('FIELD_1D: ',field_1D[ind])
                field_2D[i, j] = field_1D[ind][0]
    else:
        for i, azi in enumerate(azi_vec):
            field_1D = radar.fields[field_name]["data"][
                sweep_start_inds[i] : sweep_end_inds[i] + 1
            ]
            ele_1D = radar.elevation["data"][
                sweep_start_inds[i] : sweep_end_inds[i] + 1
            ]

            for j, ele in enumerate(ele_vec):
                ind = find_ang_index(ele_1D, ele, ang_tol=ang_tol)
                if ind is None:
                    continue
                field_2D[i, j] = field_1D[ind]

    # get limits of angle bins
    if radar.scan_type == "ppi":
        if azi_res is None:
            azi_res = np.median(azi_vec[1:] - azi_vec[0:-1])
            if radar.ray_angle_res is not None:
                azi_res = np.min([radar.ray_angle_res["data"][0], azi_res])

        azi_vec = np.append(azi_vec - azi_res / 2.0, azi_vec[-1] + azi_res / 2.0)

        if ele_res is None:
            ele_res = np.median(ele_vec[1:] - ele_vec[0:-1])
            if radar.instrument_parameters is not None:
                if "radar_beam_width_h" in radar.instrument_parameters:
                    bwidth = radar.instrument_parameters["radar_beam_width_h"]["data"][
                        0
                    ]
                    ele_res = np.min([bwidth, ele_res])
                elif "radar_beam_width_v" in radar.instrument_parameters:
                    bwidth = radar.instrument_parameters["radar_beam_width_v"]["data"][
                        0
                    ]
                    ele_res = np.min([bwidth, ele_res])

        ele_vec = np.append(ele_vec - ele_res / 2.0, ele_vec[-1] + ele_res / 2.0)
    else:
        if ele_res is None:
            ele_res = np.median(ele_vec[1:] - ele_vec[0:-1])
            if radar.ray_angle_res is not None:
                ele_res = np.min([radar.ray_angle_res["data"][0], ele_res])

        ele_vec = np.append(ele_vec - ele_res / 2.0, ele_vec[-1] + ele_res / 2.0)

        if azi_res is None:
            azi_res = np.median(azi_vec[1:] - azi_vec[0:-1])
            if radar.instrument_parameters is not None:
                if "radar_beam_width_h" in radar.instrument_parameters:
                    bwidth = radar.instrument_parameters["radar_beam_width_h"]["data"][
                        0
                    ]
                    azi_res = np.min([bwidth, azi_res])
                elif "radar_beam_width_v" in radar.instrument_parameters:
                    bwidth = radar.instrument_parameters["radar_beam_width_v"]["data"][
                        0
                    ]
                    azi_res = np.min([bwidth, azi_res])

        azi_vec = np.append(azi_vec - azi_res / 2.0, azi_vec[-1] + azi_res / 2.0)

    titl = generate_fixed_rng_title(radar, field_name, fixed_rng)

    dpi = prdcfg["ppiImageConfig"].get("dpi", 72)
    xsize = prdcfg["ppiImageConfig"].get("xsize", 10)
    ysize = prdcfg["ppiImageConfig"].get("ysize", 8)

    return _plot_sunscan(
        azi_vec,
        ele_vec,
        field_2D,
        sun_hits,
        field_name,
        fname_list,
        titl=titl,
        xlabel="azimuth (deg)",
        ylabel="elevation (deg)",
        figsize=[xsize, ysize],
        vmin=vmin,
        vmax=vmax,
        dpi=dpi,
    )


def plot_cappi(
    radar,
    field_name,
    altitude,
    prdcfg,
    fname_list,
    beamwidth=1.0,
    beam_spacing=1.0,
    save_fig=True,
):
    """
    plots a Constant Altitude Plan Position Indicator CAPPI

    Parameters
    ----------
    radar : Radar object
        object containing the radar data to plot
    field_name : str
        name of the radar field to plot
    altitude : float
        the altitude [m MSL] to be plotted
    prdcfg : dict
        dictionary containing the product configuration
    fname_list : list of str
        list of names of the files where to store the plot
    beamwidth : float
        The radar beamwidth
    beam_spacing : float
        the ray angle resolution
    save_fig : bool
        if true save the figure. If false it does not close the plot and
        returns the handle to the figure

    Returns
    -------
    fname_list : list of str or
    fig, ax : tupple
        list of names of the saved plots or handle of the figure an axes

    """
    norm, ticks, ticklabs = get_norm(field_name, field_dict=radar.fields[field_name])

    xmin = prdcfg["ppiImageConfig"]["xmin"]
    xmax = prdcfg["ppiImageConfig"]["xmax"]
    ymin = prdcfg["ppiImageConfig"]["ymin"]
    ymax = prdcfg["ppiImageConfig"]["ymax"]

    wfunc = prdcfg.get("wfunc", "NEAREST")
    cappi_res = prdcfg.get("res", 500.0)

    # number of grid points in cappi
    ny = int((ymax - ymin) * 1000.0 / cappi_res) + 1
    nx = int((xmax - xmin) * 1000.0 / cappi_res) + 1

    # parameters to determine the gates to use for each grid point
    if (
        radar.instrument_parameters is not None
        and "radar_beam_width_h" in radar.instrument_parameters
    ):
        beamwidth = radar.instrument_parameters["radar_beam_width_h"]["data"][0]

    if radar.ray_angle_res is not None:
        beam_spacing = radar.ray_angle_res["data"][0]

    lat = float(radar.latitude["data"][0])
    lon = float(radar.longitude["data"][0])
    alt = 0.0

    # cartesian mapping
    grid = pyart.map.grid_from_radars(
        (radar,),
        gridding_algo="map_to_grid",
        weighting_function=wfunc,
        roi_func="dist_beam",
        h_factor=1.0,
        nb=beamwidth,
        bsp=beam_spacing,
        min_radius=cappi_res / 2.0,
        grid_shape=(1, ny, nx),
        grid_limits=(
            (altitude, altitude),
            (ymin * 1000.0, ymax * 1000.0),
            (xmin * 1000.0, xmax * 1000.0),
        ),
        grid_origin=(lat, lon),
        grid_origin_alt=alt,
        fields=[field_name],
    )

    # display data
    dpi = prdcfg["ppiImageConfig"].get("dpi", 72)

    fig = plt.figure(
        figsize=[prdcfg["ppiImageConfig"]["xsize"], prdcfg["ppiImageConfig"]["ysize"]],
        dpi=dpi,
    )
    ax = fig.add_subplot(111, aspect="equal")
    cmap = pyart.config.get_field_colormap(field_name)

    vmin = vmax = None
    if norm is None:  # if norm is set do not override with vmin/vmax
        vmin, vmax = pyart.config.get_field_limits(field_name)

    titl = pyart.graph.common.generate_grid_title(grid, field_name, 0)

    cax = ax.imshow(
        grid.fields[field_name]["data"][0],
        extent=(xmin, xmax, ymin, ymax),
        origin="lower",
        cmap=cmap,
        vmin=vmin,
        vmax=vmax,
        norm=norm,
        interpolation="none",
    )
    ax.set_xlabel("East West distance from radar(km)")
    ax.set_ylabel("North South distance from radar(km)")
    ax.set_title(titl)

    # plot the colorbar and set the label.
    cb = fig.colorbar(cax)
    if ticks is not None:
        cb.set_ticks(ticks)
    if ticklabs:
        cb.set_ticklabels(ticklabs)
    label = get_colobar_label(grid.fields[field_name], field_name)
    cb.set_label(label)

    # Make a tight layout
    fig.tight_layout()

    if save_fig:
        for fname in fname_list:
            fig.savefig(fname, dpi=dpi)
        plt.close(fig)

        return fname_list

    return (fig, ax)


def plot_xsection(
    radar,
    field_name,
    ref_points,
    step,
    vert_res,
    alt_max,
    beamwidth,
    dem,
    prdcfg,
    fname_list,
    titl=None,
    vmin=None,
    vmax=None,
    save_fig=True,
):
    """
    plots a cross-section on polar data

    Parameters
    ----------
    radar : Radar object
        object containing the radar data to plot
    field_name : str
        name of the radar field to plot
    ref_points :  ndarray
            N x 2 array containing the lon, lat coordinates of N reference
            points along the trajectory in WGS84 coordinates,
            for example [[11, 46], [10, 45], [9, 47]]
    step : int
            Step in meters to use between reference points to calculate
            the cross-section (i.e horizontal resolution).
    vert_res : int
            Vertical resolution in meters used to calculate the cross-section
    alt_max : int
        Maximum altitude of the vertical cross-section
    beamwidth : float
        3dB beamwidth in degrees to be used in the calculations
    dem : Grid
        Grid object that contains data from a digital elevation model
        the data ist expected to be in meters
    prdcfg : dict
        dictionary containing the product configuration
    fname_list : list of str
        list of names of the files where to store the plot
    titl : str
        Plot title
    vmin, vmax : float
        The minimum and maximum value. If None the scale is going to be
        obtained from the Py-ART config file.
    save_fig : bool
        if true save the figure. If false it does not close the plot and
        returns the handle to the figure

    Returns
    -------
    fname_list : list of str or
    fig, ax : tupple
        list of names of the saved plots or handle of the figure an axes

    """
    dpi = prdcfg["rhiImageConfig"].get("dpi", 72)

    norm = None
    ticks = None
    ticklabs = None
    if vmin is None or vmax is None:
        norm, ticks, ticklabs = get_norm(
            field_name, field_dict=radar.fields[field_name]
        )
        vmin = None
        vmax = None

    xsize = prdcfg["rhiImageConfig"]["xsize"]
    ysize = prdcfg["rhiImageConfig"]["ysize"]
    fig = plt.figure(figsize=[xsize, ysize], dpi=dpi)

    step = prdcfg.get("step", 1000)
    ax = fig.add_subplot(111)
    display = pyart.graph.RadarDisplay(radar)

    display.plot_xsection(
        field_name,
        ref_points,
        step,
        vert_res,
        alt_max,
        beamwidth,
        dem,
        title=titl,
        norm=norm,
        ticks=ticks,
        ticklabs=ticklabs,
        vmin=vmin,
        vmax=vmax,
        colorbar_orient="horizontal",
        fig=fig,
        ax=ax,
    )

    # Turn on the grid
    ax.grid()

    # Make a tight layout
    fig.tight_layout()

    if save_fig:
        for fname in fname_list:
            fig.savefig(fname, dpi=dpi)
        plt.close(fig)

        return fname_list

    return (fig, ax)

    return fname_list


def plot_traj(
    rng_traj,
    azi_traj,
    ele_traj,
    time_traj,
    prdcfg,
    fname_list,
    rad_alt=None,
    rad_tstart=None,
    ax=None,
    fig=None,
    save_fig=True,
):
    """
    plots a trajectory on a Cartesian surface

    Parameters
    ----------
    rng_traj, azi_traj, ele_traj : float array
        antenna coordinates of the trajectory [m and deg]
    time_traj : datetime array
        trajectory time
    prdcfg : dict
        dictionary containing the product configuration
    fname_list : list of str
        list of names of the files where to store the plot
    rad_alt : float or None
        radar altitude [m MSL]
    rad_tstart : datetime object or None
        start time of the radar scan
    surface_alt : float
        surface altitude [m MSL]
    color_ref : str
        What the color code represents. Can be 'None', 'rel_altitude',
        'altitude' or 'time'
    fig : Figure
        Figure to add the colorbar to. If none a new figure will be created
    ax : Axis
        Axis to plot on. if fig is None a new axis will be created
    save_fig : bool
        if true save the figure if false it does not close the plot and
        returns the handle to the figure

    Returns
    -------
    fname_list : list of str or
    fig, ax : tupple
        list of names of the saved plots or handle of the figure an axes

    """
    color_ref = prdcfg.get("color_ref", "None")
    if "altitude" not in prdcfg and color_ref == "rel_altitude":
        warn(
            "Unable to plot trajectory relative to surface altitude. "
            + "Unknown surface altitude."
        )
        color_ref = "None"
    if rad_tstart is None and color_ref == "time":
        warn(
            "Unable to plot trajectory relative to radar scan start time. "
            + "Unknown radar scan start time."
        )
        color_ref = "None"
    if rad_alt is None and color_ref in ("rel_altitude", "altitude"):
        warn("Unable to plot trajectory altitude. " + "Unknown radar altitude.")
        color_ref = "None"

    x, y, z = pyart.core.antenna_to_cartesian(rng_traj / 1000.0, azi_traj, ele_traj)

    if color_ref == "rel_altitude":
        h = z + rad_alt
        h_rel = h - prdcfg["altitude"]

        marker = "x"
        col = h_rel
        cmap = "coolwarm"
        norm = plt.Normalize(-2000.0, 2000.0)
        cb_label = "Altitude relative to CAPPI [m]"
        plot_cb = True
    elif color_ref == "altitude":
        h = z + rad_alt

        marker = "x"
        col = h
        cmap = "Greys"
        norm = plt.Normalize(h.min(), h.max())
        cb_label = "Altitude [m MSL]"
        plot_cb = True
    elif color_ref == "time":
        td_vec = time_traj - rad_tstart
        tt_s = []
        for td in td_vec:
            tt_s.append(td.total_seconds())
        tt_s = np.asarray(tt_s)

        marker = "x"
        col = tt_s
        cmap = "Greys"
        norm = plt.Normalize(tt_s.min(), tt_s.max())
        cb_label = "Time from start of radar scan [s]"
        plot_cb = True
    else:
        col = "k"
        marker = "x"
        cmap = None
        plot_cb = False

    # display data
    dpi = prdcfg["ppiImageConfig"].get("dpi", 72)
    if fig is None:
        fig = plt.figure(
            figsize=[
                prdcfg["ppiImageConfig"]["xsize"],
                prdcfg["ppiImageConfig"]["ysize"],
            ],
            dpi=dpi,
        )
        ax = fig.add_subplot(111, aspect="equal")
    else:
        ax.autoscale(False)

    cax = ax.scatter(
        x / 1000.0, y / 1000.0, c=col, marker=marker, alpha=0.5, cmap=cmap, norm=norm
    )

    # plot colorbar
    if plot_cb:
        cb = fig.colorbar(cax, orientation="horizontal")
        cb.set_label(cb_label)

    if save_fig:
        for fname in fname_list:
            fig.savefig(fname, dpi=dpi)
        plt.close(fig)

        return fname_list

    return (fig, ax)


def plot_rhi_contour(
    radar,
    field_name,
    ind_az,
    prdcfg,
    fname_list,
    contour_values=None,
    linewidths=1.5,
    ax=None,
    fig=None,
    save_fig=True,
):
    """
    plots contour data on an RHI

    Parameters
    ----------
    radar : Radar object
        object containing the radar data to plot
    field_name : str
        name of the radar field to plot
    ind_az : int
        sweep index to plot
    prdcfg : dict
        dictionary containing the product configuration
    fname_list : list of str
        list of names of the files where to store the plot
    contour_values : float array
        list of contours to plot
    linewidths : float
        width of the contour lines
    fig : Figure
        Figure to add the colorbar to. If none a new figure will be created
    ax : Axis
        Axis to plot on. if fig is None a new axis will be created
    save_fig : bool
        if true save the figure if false it does not close the plot and
        returns the handle to the figure

    Returns
    -------
    fname_list : list of str or
    fig, ax : tupple
        list of names of the saved plots or handle of the figure an axes

    """
    dpi = prdcfg["ppiImageConfig"].get("dpi", 72)

    # get contour intervals
    if contour_values is None:
        field_dict = pyart.config.get_metadata(field_name)
        if "boundaries" in field_dict:
            vmin = field_dict["boundaries"][0]
            vmax = field_dict["boundaries"][-1]
            num = len(field_dict["boundaries"])
        else:
            vmin, vmax = pyart.config.get_field_limits(field_name)
            num = 10

        contour_values = np.linspace(vmin, vmax, num=num)

    # get data and position
    display = pyart.graph.RadarDisplay(radar)
    data = display._get_data(field_name, ind_az, None, True, None)

    x_edges, y_edges, z_edges = display._get_x_y_z(ind_az, True, True)
    delta_x = x_edges[1:, 1:] - x_edges[:-1, :-1]
    delta_y = y_edges[1:, 1:] - y_edges[:-1, :-1]
    delta_z = z_edges[1:, 1:] - z_edges[:-1, :-1]

    x = x_edges[:-1, :-1] + delta_x / 2.0
    y = y_edges[:-1, :-1] + delta_y / 2.0
    z = z_edges[:-1, :-1] + delta_z / 2.0

    R = np.sqrt(x**2 + y**2) * np.sign(x)

    # display data
    if fig is None:
        xsize = prdcfg["rhiImageConfig"]["xsize"]
        ysize = prdcfg["rhiImageConfig"]["ysize"]
        fig = plt.figure(figsize=[xsize, ysize], dpi=dpi)
        ax = fig.add_subplot(111, aspect="equal")

        ax.contour(R, z, data, contour_values, colors="k", linewidths=linewidths)

        display._set_title(field_name, ind_az, None, ax)
        display._label_axes_rhi((None, None), ax)

        display.set_limits(
            ylim=[prdcfg["rhiImageConfig"]["ymin"], prdcfg["rhiImageConfig"]["ymax"]],
            xlim=[prdcfg["rhiImageConfig"]["xmin"], prdcfg["rhiImageConfig"]["xmax"]],
            ax=ax,
        )
        display.plot_cross_hair(5.0, ax=ax)

        # Turn on the grid
        ax.grid()

        # Make a tight layout
        fig.tight_layout()
    else:
        ax.autoscale(False)
        ax.contour(R, z, data, contour_values, colors="k", linewidths=linewidths)

    if save_fig:
        for fname in fname_list:
            fig.savefig(fname, dpi=dpi)
        plt.close(fig)

        return fname_list

    return (fig, ax)


def plot_ppi_contour(
    radar,
    field_name,
    ind_el,
    prdcfg,
    fname_list,
    contour_values=None,
    linewidths=1.5,
    ax=None,
    fig=None,
    save_fig=True,
):
    """
    plots contour data on a PPI

    Parameters
    ----------
    radar : Radar object
        object containing the radar data to plot
    field_name : str
        name of the radar field to plot
    ind_el : int
        sweep index to plot
    prdcfg : dict
        dictionary containing the product configuration
    fname_list : list of str
        list of names of the files where to store the plot
    contour_values : float array
        list of contours to plot
    linewidths : float
        width of the contour lines
    fig : Figure
        Figure to add the contour to. If none a new figure will be created
    ax : Axis
        Axis to plot on. if fig is None a new axis will be created
    save_fig : bool
        if true save the figure if false it does not close the plot and
        returns the handle to the figure

    Returns
    -------
    fname_list : list of str or
    fig, ax : tupple
        list of names of the saved plots or handle of the figure an axes

    """
    dpi = prdcfg["ppiImageConfig"].get("dpi", 72)

    # get contour intervals
    if contour_values is None:
        field_dict = pyart.config.get_metadata(field_name)
        if "boundaries" in field_dict:
            vmin = field_dict["boundaries"][0]
            vmax = field_dict["boundaries"][-1]
            num = len(field_dict["boundaries"])
        else:
            vmin, vmax = pyart.config.get_field_limits(field_name)
            num = 10

        contour_values = np.linspace(vmin, vmax, num=num)

    # get data and position
    display = pyart.graph.RadarDisplay(radar)
    data = display._get_data(field_name, ind_el, None, True, None)

    x_edges, y_edges = display._get_x_y(ind_el, True, True)
    delta_x = x_edges[1:, 1:] - x_edges[:-1, :-1]
    delta_y = y_edges[1:, 1:] - y_edges[:-1, :-1]

    x = x_edges[:-1, :-1] + delta_x / 2.0
    y = y_edges[:-1, :-1] + delta_y / 2.0

    # display data
    if fig is None:
        xsize = prdcfg["ppiImageConfig"]["xsize"]
        ysize = prdcfg["ppiImageConfig"]["ysize"]
        fig = plt.figure(figsize=[xsize, ysize], dpi=dpi)
        ax = fig.add_subplot(111, aspect="equal")

        ax.contour(x, y, data, contour_values, colors="k", linewidths=linewidths)

        display._set_title(field_name, ind_el, None, ax)
        display._label_axes_ppi((None, None), ax)

        display.set_limits(
            ylim=[prdcfg["ppiImageConfig"]["ymin"], prdcfg["ppiImageConfig"]["ymax"]],
            xlim=[prdcfg["ppiImageConfig"]["xmin"], prdcfg["ppiImageConfig"]["xmax"]],
            ax=ax,
        )
        display.plot_cross_hair(5.0, ax=ax)

        # Turn on the grid
        ax.grid()

        # Make a tight layout
        fig.tight_layout()
    else:
        ax.autoscale(False)
        ax.contour(x, y, data, contour_values, colors="k", linewidths=linewidths)

    if save_fig:
        for fname in fname_list:
            fig.savefig(fname, dpi=dpi)
        plt.close(fig)

        return fname_list

    return (fig, ax)


def plot_roi_contour(
    roi_dict,
    prdcfg,
    fname_list,
    plot_center=True,
    xlabel="Lon [Deg]",
    ylabel="Lat [Deg]",
    titl="TRT cell position",
    ax=None,
    fig=None,
    save_fig=True,
):
    """
    plots the contour of a region of interest on a map

    Parameters
    ----------
    roi_dict : dict
        dictionary containing lon_roi, lat_roi, the points defining the
        contour
    prdcfg : dict
        dictionary containing the product configuration
    fname_list : list of str
        list of names of the files where to store the plot
    plot_center : bool
        If True a marked with the center of the roi is plotted
    fig : Figure
        Figure to add the colorbar to. If none a new figure will be created
    ax : Axis
        Axis to plot on. if fig is None a new axis will be created
    save_fig : bool
        if true save the figure if false it does not close the plot and
        returns the handle to the figure

    Returns
    -------
    fname_list : list of str or
    fig, ax : tupple
        list of names of the saved plots or handle of the figure an axes

    """
    if not _SHAPELY_AVAILABLE or not _CARTOPY_AVAILABLE:
        warn("Unable to plot ROI contour: Missing shapely and/or" " cartopy modules")
        return None

    dpi = prdcfg["ppiMapImageConfig"].get("dpi", 72)

    # create polygon to plot
    polygon = shapely.geometry.Polygon(list(zip(roi_dict["lon"], roi_dict["lat"])))

    # display data
    if fig is None:
        xsize = prdcfg["ppiMapImageConfig"]["xsize"]
        ysize = prdcfg["ppiMapImageConfig"]["ysize"]
        lonstep = prdcfg["ppiMapImageConfig"].get("lonstep", 0.5)
        latstep = prdcfg["ppiMapImageConfig"].get("latstep", 0.5)
        min_lon = prdcfg["ppiMapImageConfig"].get("lonmin", 2.5)
        max_lon = prdcfg["ppiMapImageConfig"].get("lonmax", 12.5)
        min_lat = prdcfg["ppiMapImageConfig"].get("latmin", 43.5)
        max_lat = prdcfg["ppiMapImageConfig"].get("latmax", 49.5)
        resolution = prdcfg["ppiMapImageConfig"].get("mapres", "110m")
        if resolution not in ("110m", "50m", "10m"):
            warn("Unknown map resolution: " + resolution)
            resolution = "110m"
        background_zoom = prdcfg["ppiMapImageConfig"].get("background_zoom", 8)

        lon_lines = np.arange(np.floor(min_lon), np.ceil(max_lon) + 1, lonstep)
        lat_lines = np.arange(np.floor(min_lat), np.ceil(max_lat) + 1, latstep)
        limits = [min_lon, max_lon, min_lat, max_lat]

        # get background map instance
        stamen_terrain = Stamen("terrain-background")
        projection = cartopy.crs.PlateCarree()

        fig = plt.figure(figsize=[xsize, ysize], dpi=dpi)

        # draw background
        ax = fig.add_subplot(111, projection=stamen_terrain.crs)
        ax.set_extent(limits, crs=projection)
        ax.add_image(stamen_terrain, background_zoom)

        # add countries
        countries = cartopy.feature.NaturalEarthFeature(
            category="cultural",
            name="admin_0_countries",
            scale=resolution,
            facecolor="none",
        )
        ax.add_feature(countries, edgecolor="black")

        # draw grid lines and labels
        gl = ax.gridlines(xlocs=lon_lines, ylocs=lat_lines, draw_labels=True)
        gl.xlabels_top = False
        gl.ylabels_right = False

        ax.text(
            0.5,
            -0.2,
            xlabel,
            va="bottom",
            ha="center",
            rotation="horizontal",
            rotation_mode="anchor",
            transform=ax.transAxes,
        )

        ax.text(
            -0.1,
            0.55,
            ylabel,
            va="bottom",
            ha="center",
            rotation="vertical",
            rotation_mode="anchor",
            transform=ax.transAxes,
        )

        ax.set_title(titl)
    else:
        ax.autoscale(False)

    ax.add_geometries(
        [polygon], cartopy.crs.PlateCarree(), facecolor="none", edgecolor="k"
    )

    if "lon_center" in roi_dict and "lat_center" in roi_dict and plot_center:
        ax.scatter(
            [roi_dict["lon_center"]],
            [roi_dict["lat_center"]],
            c="k",
            marker="x",
            transform=cartopy.crs.PlateCarree(),
        )

    if save_fig:
        for fname in fname_list:
            fig.savefig(fname, dpi=dpi)
        plt.close(fig)

        return fname_list

    return (fig, ax)


def plot_rhi_profile(
    data_list,
    hvec,
    fname_list,
    labelx="Value",
    labely="Height (m MSL)",
    labels=("Mean"),
    title="RHI profile",
    colors=None,
    linestyles=None,
    vmin=None,
    vmax=None,
    hmin=None,
    hmax=None,
    dpi=72,
):
    """
    plots an RHI profile

    Parameters
    ----------
    data_list : list of float array
        values of the profile
    hvec : float array
        height points of the profile
    fname_list : list of str
        list of names of the files where to store the plot
    labelx : str
        The label of the X axis
    labely : str
        The label of the Y axis
    labels : array of str
        The label of the legend
    title : str
        The figure title
    colors : array of str
        Specifies the colors of each line
    linestyles : array of str
        Specifies the line style of each line
    vmin, vmax: float
        Lower/Upper limit of data values
    hmin, hmax: float
        Lower/Upper limit of altitude
    dpi : int
        dots per inch

    Returns
    -------
    fname_list : list of str
        list of names of the created plots

    """
    fig, ax = plt.subplots(figsize=[10, 6], dpi=dpi)

    lab = None
    col = None
    lstyle = None
    for i, data in enumerate(data_list):
        if labels is not None:
            lab = labels[i]
        if colors is not None:
            col = colors[i]
        if linestyles is not None:
            lstyle = linestyles[i]
        ax.plot(data, hvec, label=lab, color=col, linestyle=lstyle, marker="x")

    ax.set_title(title)
    ax.set_xlabel(labelx)
    ax.set_ylabel(labely)
    ax.set_xlim(left=vmin, right=vmax)
    ax.set_ylim(bottom=hmin, top=hmax)
    ax.legend(loc="best")

    # Turn on the grid
    ax.grid()

    for fname in fname_list:
        fig.savefig(fname, dpi=dpi)
    plt.close(fig)

    return fname_list


def plot_along_coord(
    xval_list,
    yval_list,
    fname_list,
    labelx="coord",
    labely="Value",
    labels=None,
    title="Plot along coordinate",
    colors=None,
    linestyles=None,
    ymin=None,
    ymax=None,
    dpi=72,
    data_on_y=True,
    plot_legend=True,
):
    """
    plots data along a certain radar coordinate

    Parameters
    ----------
    xval_list : list of float arrays
        the x values, range, azimuth or elevation
    yval_list : list of float arrays
        the y values. Parameter to plot
    fname_list : list of str
        list of names of the files where to store the plot
    labelx : str
        The label of the X axis
    labely : str
        The label of the Y axis
    labels : array of str
        The label of the legend
    title : str
        The figure title
    colors : array of str
        Specifies the colors of each line
    linestyles : array of str
        Specifies the line style of each line
    ymin, ymax: float
        Lower/Upper limit of y axis
    dpi : int
        dots per inch
    data_on_y : bool
        If True the data is in the y axis and the coordinate in the x axis.
        False swaps it
    plot_legend : bool
        If True a legend will be plotted

    Returns
    -------
    fname_list : list of str
        list of names of the created plots

    """
    fig, ax = plt.subplots(figsize=[10, 6], dpi=dpi)

    lab = None
    col = None
    lstyle = None

    for i, xval in enumerate(xval_list):
        yval = yval_list[i]
        if labels is not None:
            lab = labels[i]
        if colors is not None:
            col = colors[i]
        if linestyles is not None:
            lstyle = linestyles[i]
        if data_on_y:
            ax.plot(xval, yval, label=lab, color=col, linestyle=lstyle)
        else:
            ax.plot(yval, xval, label=lab, color=col, linestyle=lstyle)

    ax.set_title(title)
    if data_on_y:
        ax.set_xlabel(labelx)
        ax.set_ylabel(labely)
        ax.set_ylim(bottom=ymin, top=ymax)
    else:
        ax.set_xlabel(labely)
        ax.set_ylabel(labelx)
        ax.set_xlim(left=ymin, right=ymax)
    if plot_legend:
        ax.legend(loc="best")

    for fname in fname_list:
        fig.savefig(fname, dpi=dpi)
    plt.close(fig)

    return fname_list


def plot_field_coverage(
    xval_list,
    yval_list,
    fname_list,
    labelx="Azimuth (deg)",
    labely="Range extension [m]",
    labels=None,
    title="Field coverage",
    ymin=None,
    ymax=None,
    xmeanval=None,
    ymeanval=None,
    labelmeanval=None,
    dpi=72,
):
    """
    plots a time series

    Parameters
    ----------
    xval_list : list of float arrays
        the x values, azimuth
    yval_list : list of float arrays
        the y values. Range extension
    fname_list : list of str
        list of names of the files where to store the plot
    labelx : str
        The label of the X axis
    labely : str
        The label of the Y axis
    labels : array of str
        The label of the legend
    title : str
        The figure title
    ymin, ymax : float
        Lower/Upper limit of y axis
    xmeanval, ymeanval : float array
        the x and y values of a mean along elevation
    labelmeanval : str
        the label of the mean
    dpi : int
        dots per inch

    Returns
    -------
    fname_list : list of str
        list of names of the created plots

    """
    fig, ax = plt.subplots(figsize=[10, 6], dpi=dpi)

    lab = None

    for i, xval in enumerate(xval_list):
        yval = yval_list[i]
        if labels is not None:
            lab = labels[i]
        ax.plot(xval, yval, label=lab, linestyle="None", marker="o", fillstyle="full")

    if xmeanval is not None and ymeanval is not None:
        ax.plot(
            xmeanval, ymeanval, label=labelmeanval, linestyle="-", color="r", marker="x"
        )

    ax.set_title(title)
    ax.set_xlabel(labelx)
    ax.set_ylabel(labely)
    ax.set_ylim(bottom=ymin, top=ymax)
    if labels is not None:
        ax.legend(loc="best")

    for fname in fname_list:
        fig.savefig(fname, dpi=dpi)
    plt.close(fig)

    return fname_list
