def _get_shadowprice_data(scenario_id):
    """Gets data necessary for plotting shadow price

    :param str/int scenario_id: scenario id
    :return: (*tuple*) -- interconnect as a str, bus data as a data frame, lmp data
        as a data frame, branch data as a data frame and congestion data as a data
        frame
    """
    s = Scenario(scenario_id)

    interconnect = s.info["interconnect"]
    interconnect = " ".join(interconnect.split("_"))

    s_grid = s.state.get_grid()

    # Get bus and add location data
    bus_map = project_bus(s_grid.bus)

    # get branch and add location data
    branch_map = project_branch(s_grid.branch)

    # get congestion
    congu = s.state.get_congu()
    congl = s.state.get_congl()
    cong_abs = pd.DataFrame(
        np.maximum(congu.to_numpy(), congl.to_numpy()),
        columns=congu.columns,
        index=congu.index,
    )

    return interconnect, bus_map, s.state.get_lmp(), branch_map, cong_abs
def plot_powerflow_snapshot(
    scenario,
    hour,
    b2b_dclines=None,
    demand_centers=None,
    ac_branch_color="#8B36FF",
    dc_branch_color="#01D4ED",
    solar_color="#FFBB45",
    wind_color="#78D911",
    demand_color="gray",
    figsize=(1400, 800),
    circle_scale_factor=0.25,
    bg_width_scale_factor=0.001,
    pf_width_scale_factor=0.00125,
    arrow_pf_threshold=3000,
    arrow_dist_threshold=20,
    num_ac_arrows=1,
    num_dc_arrows=1,
    min_arrow_size=5,
    branch_alpha=0.5,
    state_borders_kwargs=None,
    x_range=None,
    y_range=None,
    legend_font_size=None,
):
    """Plot a snapshot of powerflow.

    :param powersimdata.scenario.scenario.Scenario scenario: scenario to plot.
    :param pandas.Timestamp/numpy.datetime64/datetime.datetime hour: snapshot interval.
    :param dict b2b_dclines: which DC lines are actually B2B facilities. Keys are:
        {"from", "to"}, values are iterables of DC line indices to plot (indices in
        "from" get plotted at the "from" end, and vice versa).
    :param pandas.DataFrame demand_centers: lat/lon centers at which to plot the demand
        from each load zone.
    :param str ac_branch_color: color to plot AC branches.
    :param str dc_branch_color: color to plot DC branches.
    :param str solar_color: color to plot solar generation.
    :param str wind_color: color to plot wind generation.
    :param str demand_color: color to plot demand.
    :param tuple figsize: size of the bokeh figure (in pixels).
    :param int/float circle_scale_factor: scale factor for demand/solar/wind circles.
    :param int/float bg_width_scale_factor: scale factor for grid capacities.
    :param int/float pf_width_scale_factor: scale factor for power flows.
    :param int/float arrow_pf_threshold: minimum power flow (MW) for adding arrows.
    :param int/float arrow_dist_threshold: minimum distance (miles) for adding arrows.
    :param int num_ac_arrows: number of arrows for each AC branch.
    :param int num_dc_arrows: number of arrows for each DC branch.
    :param int/float min_arrow_size: minimum arrow size.
    :param int/float branch_alpha: opaqueness of branches.
    :param dict state_borders_kwargs: keyword arguments to be passed to
        :func:`postreise.plot.plot_states.add_state_borders`.
    :param tuple(float, float) x_range: x range to zoom plot to (EPSG:3857).
    :param tuple(float, float) y_range: y range to zoom plot to (EPSG:3857).
    :param int/str legend_font_size: size to display legend specified as e.g. 12/'12pt'.
    :return: (*bokeh.plotting.figure*) -- power flow snapshot map.
    """
    _check_scenario_is_in_analyze_state(scenario)
    _check_date_range_in_scenario(scenario, hour, hour)
    # Get scenario data
    grid = scenario.state.get_grid()
    bus = grid.bus
    plant = grid.plant
    # Augment the branch dataframe with extra info needed for plotting
    branch = grid.branch
    branch["pf"] = scenario.state.get_pf().loc[hour]
    branch = branch.query("pf != 0").copy()
    branch["dist"] = branch.apply(lambda x: haversine((x.from_lat, x.from_lon),
                                                      (x.to_lat, x.to_lon)),
                                  axis=1)
    branch["arrow_size"] = branch["pf"].abs(
    ) * pf_width_scale_factor + min_arrow_size
    branch = project_branch(branch)
    # Augment the dcline dataframe with extra info needed for plotting
    dcline = grid.dcline
    dcline["pf"] = scenario.state.get_dcline_pf().loc[hour]
    dcline["from_lat"] = dcline.apply(lambda x: bus.loc[x.from_bus_id, "lat"],
                                      axis=1)
    dcline["from_lon"] = dcline.apply(lambda x: bus.loc[x.from_bus_id, "lon"],
                                      axis=1)
    dcline["to_lat"] = dcline.apply(lambda x: bus.loc[x.to_bus_id, "lat"],
                                    axis=1)
    dcline["to_lon"] = dcline.apply(lambda x: bus.loc[x.to_bus_id, "lon"],
                                    axis=1)
    dcline["dist"] = dcline.apply(lambda x: haversine((x.from_lat, x.from_lon),
                                                      (x.to_lat, x.to_lon)),
                                  axis=1)
    dcline["arrow_size"] = dcline["pf"].abs(
    ) * pf_width_scale_factor + min_arrow_size
    dcline = project_branch(dcline)
    # Create a dataframe for demand plotting, if necessary
    if demand_centers is not None:
        demand = scenario.state.get_demand()
        demand_centers["demand"] = demand.loc[hour]
        demand_centers = project_bus(demand_centers)

    # create canvas
    canvas = create_map_canvas(figsize=figsize,
                               x_range=x_range,
                               y_range=y_range)

    # Add state borders
    default_state_borders_kwargs = {"fill_alpha": 0.0, "background_map": False}
    all_state_borders_kwargs = ({
        **default_state_borders_kwargs,
        **state_borders_kwargs
    } if state_borders_kwargs is not None else default_state_borders_kwargs)
    _check_func_kwargs(add_state_borders, set(all_state_borders_kwargs),
                       "state_borders_kwargs")
    canvas = add_state_borders(canvas, **all_state_borders_kwargs)

    if b2b_dclines is not None:
        # Append the pseudo AC lines to the branch dataframe, remove from dcline
        all_b2b_dclines = list(b2b_dclines["to"]) + list(b2b_dclines["from"])
        pseudo_ac_lines = dcline.loc[all_b2b_dclines]
        pseudo_ac_lines["rateA"] = pseudo_ac_lines[["Pmin",
                                                    "Pmax"]].abs().max(axis=1)
        branch = branch.append(pseudo_ac_lines)
        # Construct b2b dataframe so that all get plotted at their 'from' x/y
        b2b_from = dcline.loc[b2b_dclines["from"]]
        b2b_to = dcline.loc[b2b_dclines["to"]].rename(
            {
                "from_x": "to_x",
                "from_y": "to_y",
                "to_x": "from_x",
                "to_y": "from_y"
            },
            axis=1,
        )
        b2b = pd.concat([b2b_from, b2b_to])
        dcline = dcline.loc[~dcline.index.isin(all_b2b_dclines)]

    # Plot grid background in grey
    canvas.multi_line(
        branch[["from_x", "to_x"]].to_numpy().tolist(),
        branch[["from_y", "to_y"]].to_numpy().tolist(),
        color="gray",
        alpha=branch_alpha,
        line_width=(branch["rateA"].abs() * bg_width_scale_factor),
    )
    canvas.multi_line(
        dcline[["from_x", "to_x"]].to_numpy().tolist(),
        dcline[["from_y", "to_y"]].to_numpy().tolist(),
        color="gray",
        alpha=branch_alpha,
        line_width=(dcline[["Pmin", "Pmax"]].abs().max(axis=1) *
                    bg_width_scale_factor),
    )
    if b2b_dclines is not None:
        canvas.scatter(
            x=b2b.from_x,
            y=b2b.from_y,
            color="gray",
            alpha=0.5,
            marker="triangle",
            size=(b2b[["Pmin", "Pmax"]].abs().max(axis=1) *
                  bg_width_scale_factor),
        )

    fake_location = branch.iloc[0].drop("x").rename({
        "from_x": "x",
        "from_y": "y"
    })
    # Legend entries
    canvas.multi_line(
        (fake_location.x, fake_location.x),
        (fake_location.y, fake_location.y),
        color=dc_branch_color,
        alpha=branch_alpha,
        line_width=10,
        legend_label="HVDC powerflow",
        visible=False,
    )
    canvas.multi_line(
        (fake_location.x, fake_location.x),
        (fake_location.y, fake_location.y),
        color=ac_branch_color,
        alpha=branch_alpha,
        line_width=10,
        legend_label="AC powerflow",
        visible=False,
    )
    canvas.circle(
        fake_location.x,
        fake_location.y,
        color=solar_color,
        alpha=0.6,
        size=5,
        legend_label="Solar Gen.",
        visible=False,
    )
    canvas.circle(
        fake_location.x,
        fake_location.y,
        color=wind_color,
        alpha=0.6,
        size=5,
        legend_label="Wind Gen.",
        visible=False,
    )

    # Plot demand
    if demand_centers is not None:
        canvas.circle(
            fake_location.x,
            fake_location.y,
            color=demand_color,
            alpha=0.3,
            size=5,
            legend_label="Demand",
            visible=False,
        )
        canvas.circle(
            demand_centers.x,
            demand_centers.y,
            color=demand_color,
            alpha=0.3,
            size=(demand_centers.demand * circle_scale_factor)**0.5,
        )

    # Aggregate solar and wind for plotting
    plant_with_pg = plant.copy()
    plant_with_pg["pg"] = scenario.state.get_pg().loc[hour]
    grouped_solar = aggregate_plant_generation(
        plant_with_pg.query("type == 'solar'"))
    grouped_wind = aggregate_plant_generation(
        plant_with_pg.query("type == 'wind'"))
    # Plot solar, wind
    canvas.circle(
        grouped_solar.x,
        grouped_solar.y,
        color=solar_color,
        alpha=0.6,
        size=(grouped_solar.pg * circle_scale_factor)**0.5,
    )
    canvas.circle(
        grouped_wind.x,
        grouped_wind.y,
        color=wind_color,
        alpha=0.6,
        size=(grouped_wind.pg * circle_scale_factor)**0.5,
    )

    # Plot powerflow on AC branches
    canvas.multi_line(
        branch[["from_x", "to_x"]].to_numpy().tolist(),
        branch[["from_y", "to_y"]].to_numpy().tolist(),
        color=ac_branch_color,
        alpha=branch_alpha,
        line_width=(branch["pf"].abs() * pf_width_scale_factor),
    )
    add_arrows(
        canvas,
        branch,
        color=ac_branch_color,
        pf_threshold=arrow_pf_threshold,
        dist_threshold=arrow_dist_threshold,
        n=num_ac_arrows,
    )

    # Plot powerflow on DC lines
    canvas.multi_line(
        dcline[["from_x", "to_x"]].to_numpy().tolist(),
        dcline[["from_y", "to_y"]].to_numpy().tolist(),
        color=dc_branch_color,
        alpha=branch_alpha,
        line_width=(dcline["pf"].abs() * pf_width_scale_factor),
    )
    add_arrows(
        canvas,
        dcline,
        color=dc_branch_color,
        pf_threshold=0,
        dist_threshold=0,
        n=num_dc_arrows,
    )
    # B2Bs
    if b2b_dclines is not None:
        canvas.scatter(
            x=b2b.from_x,
            y=b2b.from_y,
            color=dc_branch_color,
            alpha=0.5,
            marker="triangle",
            size=(b2b["pf"].abs() * pf_width_scale_factor * 5),
        )

    canvas.legend.location = "bottom_left"
    if legend_font_size is not None:
        if isinstance(legend_font_size, (int, float)):
            legend_font_size = f"{legend_font_size}pt"
        canvas.legend.label_text_font_size = legend_font_size

    return canvas
Beispiel #3
0
def add_transmission_upgrades(
    canvas,
    branch_merge,
    dc_merge,
    b2b_indices=None,
    diff_threshold=100,
    all_branch_scale=1,
    diff_branch_scale=1,
    all_branch_min=0.1,
    diff_branch_min=1.0,
    b2b_scale=5,
    dcline_upgrade_dist_threshold=0,
):
    """Make map of branches showing upgrades.

    :param bokeh.plotting.figure.Figure canvas: canvas to add upgrades to.
    :param pandas.DataFrame branch_merge: branch of scenarios 1 and 2
    :param pandas.DataFrame dc_merge: dclines for scenarios 1 and 2
    :param list/set/tuple b2b_indices: indices of HVDC lines which are back-to-backs.
    :param int/float diff_threshold: difference threshold (in MW), above which branches
        are highlighted.
    :param int/float all_branch_scale: scale factor for plotting all branches
        (pixels/GW).
    :param int/float diff_branch_scale: scale factor for plotting branches with
        differences above the threshold (pixels/GW).
    :param int/float all_branch_min: minimum width to plot all branches.
    :param int/float diff_branch_min: minimum width to plot branches with significant
        differences.
    :param int/float b2b_scale: scale factor for plotting b2b facilities (pixels/GW).
    :param int/float dcline_upgrade_dist_threshold: minimum distance (miles) for
        plotting DC line upgrades (if none are longer, no legend entry will be created).
    :return: (*bokeh.plotting.figure.Figure*) -- Bokeh map plot of color-coded upgrades.
    """
    # plotting constants
    legend_alpha = 0.9
    all_elements_alpha = 0.5
    differences_alpha = 0.8
    # convert scale factors from pixels/GW to pixels/MW (base units for our grid data)
    all_branch_scale_MW = all_branch_scale / 1000  # noqa: N806
    diff_branch_scale_MW = diff_branch_scale / 1000  # noqa: N806
    b2b_scale_MW = b2b_scale / 1000  # noqa: N806

    # data prep
    branch_all = project_branch(branch_merge)
    branch_dc = project_branch(dc_merge)

    # For these, we will plot a triangle for the B2B location, plus 'pseudo' AC lines
    # get_level_values allows us to index into MultiIndex as necessary
    b2b_indices = {} if b2b_indices is None else b2b_indices
    b2b_mask = branch_dc.index.get_level_values(0).isin(b2b_indices)
    # .copy() avoids a pandas SettingWithCopyError later
    b2b = branch_dc.iloc[b2b_mask].copy()
    branch_dc_lines = branch_dc.loc[~b2b_mask].copy()

    # Color branches based on upgraded capacity
    branch_all["color"] = np.nan
    branch_all.loc[branch_all["diff"] > diff_threshold,
                   "color"] = colors.be_blue
    branch_all.loc[branch_all["diff"] < -1 * diff_threshold,
                   "color"] = colors.be_purple
    # Color pseudo AC branches based on upgraded capacity
    b2b["color"] = np.nan
    b2b.loc[b2b["diff"] > diff_threshold, "color"] = colors.be_blue
    b2b.loc[b2b["diff"] < -1 * diff_threshold, "color"] = colors.be_purple
    b2b = b2b[~b2b.color.isnull()]
    # Color DC lines based on upgraded capacity
    branch_dc_lines["dist"] = branch_dc_lines.apply(lambda x: haversine(
        (x.from_lat, x.from_lon), (x.to_lat, x.to_lon)),
                                                    axis=1)
    branch_dc_lines = branch_dc_lines.loc[
        branch_dc_lines.dist >= dcline_upgrade_dist_threshold]
    branch_dc_lines.loc[:, "color"] = np.nan
    branch_dc_lines.loc[branch_dc_lines["diff"] > 0, "color"] = colors.be_green
    branch_dc_lines.loc[branch_dc_lines["diff"] < 0,
                        "color"] = colors.be_lightblue

    # Create ColumnDataSources for bokeh to plot with
    source_all_ac = ColumnDataSource({
        "xs":
        branch_all[["from_x", "to_x"]].values.tolist(),
        "ys":
        branch_all[["from_y", "to_y"]].values.tolist(),
        "cap":
        branch_all["rateA"] * all_branch_scale_MW + all_branch_min,
        "color":
        branch_all["color"],
    })
    # AC branches with significant differences
    ac_diff_branches = branch_all.loc[~branch_all.color.isnull()]
    source_ac_difference = ColumnDataSource({
        "xs":
        ac_diff_branches[["from_x", "to_x"]].values.tolist(),
        "ys":
        ac_diff_branches[["from_y", "to_y"]].values.tolist(),
        "diff": (ac_diff_branches["diff"].abs() * diff_branch_scale_MW +
                 diff_branch_min),
        "color":
        ac_diff_branches["color"],
    })
    source_all_dc = ColumnDataSource({
        "xs":
        branch_dc_lines[["from_x", "to_x"]].values.tolist(),
        "ys":
        branch_dc_lines[["from_y", "to_y"]].values.tolist(),
        "cap":
        branch_dc_lines.Pmax * all_branch_scale_MW + all_branch_min,
        "color":
        branch_dc_lines["color"],
    })
    dc_diff_lines = branch_dc_lines.loc[~branch_dc_lines.color.isnull()]
    source_dc_differences = ColumnDataSource({
        "xs":
        dc_diff_lines[["from_x", "to_x"]].values.tolist(),
        "ys":
        dc_diff_lines[["from_y", "to_y"]].values.tolist(),
        "diff":
        (dc_diff_lines["diff"].abs() * diff_branch_scale_MW + diff_branch_min),
        "color":
        dc_diff_lines["color"],
    })
    source_pseudoac = ColumnDataSource(  # pseudo ac scen 1
        {
            "xs": b2b[["from_x", "to_x"]].values.tolist(),
            "ys": b2b[["from_y", "to_y"]].values.tolist(),
            "cap": b2b.Pmax * all_branch_scale_MW + all_branch_min,
            "diff": b2b["diff"].abs() * diff_branch_scale_MW + diff_branch_min,
            "color": b2b["color"],
        }
    )

    # Build the legend
    leg_x = [-8.1e6] * 2
    leg_y = [5.2e6] * 2

    # These are 'dummy' series to populate the legend with
    if len(branch_dc_lines[branch_dc_lines["diff"] > 0]) > 0:
        canvas.multi_line(
            leg_x,
            leg_y,
            color=colors.be_green,
            alpha=legend_alpha,
            line_width=10,
            legend_label="Additional HVDC Capacity",
        )
    if len(branch_dc_lines[branch_dc_lines["diff"] < 0]) > 0:
        canvas.multi_line(
            leg_x,
            leg_y,
            color=colors.be_lightblue,
            alpha=legend_alpha,
            line_width=10,
            legend_label="Reduced HVDC Capacity",
        )
    if len(branch_all[branch_all["diff"] < 0]) > 0:
        canvas.multi_line(
            leg_x,
            leg_y,
            color=colors.be_purple,
            alpha=legend_alpha,
            line_width=10,
            legend_label="Reduced AC Transmission",
        )
    if len(branch_all[branch_all["diff"] > 0]) > 0:
        canvas.multi_line(
            leg_x,
            leg_y,
            color=colors.be_blue,
            alpha=legend_alpha,
            line_width=10,
            legend_label="Upgraded AC transmission",
        )
    if len(b2b[b2b["diff"] > 0]) > 0:
        canvas.scatter(
            x=b2b.from_x[1],
            y=b2b.from_y[1],
            color=colors.be_magenta,
            marker="triangle",
            legend_label="Upgraded B2B capacity",
            size=30,
            alpha=legend_alpha,
        )

    # Everything below gets plotted into the 'main' figure
    background_plot_dicts = [
        {
            "source": source_all_ac,
            "color": "gray",
            "line_width": "cap"
        },
        {
            "source": source_all_dc,
            "color": "gray",
            "line_width": "cap"
        },
        {
            "source": source_pseudoac,
            "color": "gray",
            "line_width": "cap"
        },
    ]
    for d in background_plot_dicts:
        canvas.multi_line(
            "xs",
            "ys",
            color=d["color"],
            line_width=d["line_width"],
            source=d["source"],
            alpha=all_elements_alpha,
        )

    # all B2Bs
    canvas.scatter(
        x=b2b.from_x,
        y=b2b.from_y,
        color="gray",
        marker="triangle",
        size=b2b["Pmax"].abs() * b2b_scale_MW,
        alpha=all_elements_alpha,
    )

    difference_plot_dicts = [
        {
            "source": source_pseudoac,
            "color": "color",
            "line_width": "diff"
        },
        {
            "source": source_ac_difference,
            "color": "color",
            "line_width": "diff"
        },
        {
            "source": source_dc_differences,
            "color": "color",
            "line_width": "diff"
        },
    ]

    for d in difference_plot_dicts:
        canvas.multi_line(
            "xs",
            "ys",
            color=d["color"],
            line_width=d["line_width"],
            source=d["source"],
            alpha=differences_alpha,
        )

    # B2Bs with differences
    canvas.scatter(
        x=b2b.from_x,
        y=b2b.from_y,
        color=colors.be_magenta,
        marker="triangle",
        size=b2b["diff"].abs() * b2b_scale_MW,
    )

    return canvas
def map_interconnections(
    grid,
    branch_distance_cutoff=5,
    figsize=(1400, 800),
    branch_width_scale_factor=0.5,
    hvdc_width_scale_factor=1,
    b2b_size_scale_factor=50,
    state_borders_kwargs=None,
):
    """Map transmission lines color coded by interconnection.

    :param powersimdata.input.grid.Grid grid: grid object.
    :param int/float branch_distance_cutoff: distance cutoff for branch display.
    :param tuple figsize: size of the bokeh figure (in pixels).
    :param int/float branch_width_scale_factor: scale factor for branch capacities.
    :param int/float hvdc_width_scale_factor: scale factor for hvdc capacities.
    :param int/float b2b_size_scale_factor: scale factor for back-to_back capacities.
    :param dict state_borders_kwargs: keyword arguments to be passed to
        :func:`postreise.plot.plot_states.add_state_borders`.
    :return: (*bokeh.plotting.figure*) -- interconnection map with lines and nodes.
    :raises TypeError:
        if ``branch_device_cutoff`` is not ``float``.
        if ``branch_width_scale_factor`` is not ``int`` or ``float``.
        if ``hvdc_width_scale_factor`` is not ``int`` or ``float``.
        if ``b2b_size_scale_factor`` is not ``int`` or ``float``.
    :raises ValueError:
        if ``branch_device_cutoff`` is negative.
        if ``branch_width_scale_factor`` is negative.
        if ``hvdc_width_scale_factor`` is negative.
        if ``b2b_size_scale_factor`` is negative.
        if grid model is not supported.
    """
    _check_grid_type(grid)
    if not isinstance(branch_distance_cutoff, (int, float)):
        raise TypeError("branch_distance_cutoff must be an int")
    if branch_distance_cutoff <= 0:
        raise ValueError("branch_distance_cutoff must be strictly positive")
    if not isinstance(branch_width_scale_factor, (int, float)):
        raise TypeError("branch_width_scale_factor must be a int/float")
    if branch_width_scale_factor < 0:
        raise ValueError("branch_width_scale_factor must be positive")
    if not isinstance(hvdc_width_scale_factor, (int, float)):
        raise TypeError("hvdc_width_scale_factor must be a int/float")
    if hvdc_width_scale_factor < 0:
        raise ValueError("hvdc_width_scale_factor must be positive")
    if not isinstance(b2b_size_scale_factor, (int, float)):
        raise TypeError("b2b_size_scale_factor must be a int/float")
    if b2b_size_scale_factor < 0:
        raise ValueError("b2b_size_scale_factor must be positive")

    # branches
    branch = grid.branch.copy()
    branch["to_coord"] = list(zip(branch["to_lat"], branch["to_lon"]))
    branch["from_coord"] = list(zip(branch["from_lat"], branch["from_lon"]))
    branch["dist"] = branch.apply(
        lambda row: distance.haversine(row["to_coord"], row["from_coord"]),
        axis=1)
    branch = branch.loc[branch["dist"] > branch_distance_cutoff]
    branch = project_branch(branch)

    branch_west = branch.loc[branch["interconnect"] == "Western"]
    branch_east = branch.loc[branch["interconnect"] == "Eastern"]
    branch_tx = branch.loc[branch["interconnect"] == "Texas"]

    # HVDC lines
    all_dcline = grid.dcline.copy()
    all_dcline["from_lon"] = grid.bus.loc[all_dcline["from_bus_id"],
                                          "lon"].values
    all_dcline["from_lat"] = grid.bus.loc[all_dcline["from_bus_id"],
                                          "lat"].values
    all_dcline["to_lon"] = grid.bus.loc[all_dcline["to_bus_id"], "lon"].values
    all_dcline["to_lat"] = grid.bus.loc[all_dcline["to_bus_id"], "lat"].values
    all_dcline = project_branch(all_dcline)

    if grid.grid_model == "usa_tamu":
        b2b_id = range(9)
    else:
        raise ValueError("grid model is not supported")
    dcline = all_dcline.iloc[~all_dcline.index.isin(b2b_id)]
    b2b = all_dcline.iloc[b2b_id]

    # create canvas
    canvas = create_map_canvas(figsize=figsize)

    # add state borders
    default_state_borders_kwargs = {
        "line_width": 2,
        "fill_alpha": 0,
        "background_map": False,
    }
    all_state_borders_kwargs = ({
        **default_state_borders_kwargs,
        **state_borders_kwargs
    } if state_borders_kwargs is not None else default_state_borders_kwargs)
    _check_func_kwargs(add_state_borders, set(all_state_borders_kwargs),
                       "state_borders_kwargs")
    canvas = add_state_borders(canvas, **all_state_borders_kwargs)

    # add state tooltips
    state_counts = count_nodes_per_state(grid)
    state2label = {
        s: c
        for s, c in zip(state_counts.index, state_counts.to_numpy())
    }
    canvas = add_state_tooltips(canvas, "nodes", state2label)

    canvas.multi_line(
        branch_west[["from_x", "to_x"]].to_numpy().tolist(),
        branch_west[["from_y", "to_y"]].to_numpy().tolist(),
        color="#006ff9",
        line_width=branch_west["rateA"].abs() * 1e-3 *
        branch_width_scale_factor,
        legend_label="Western",
    )

    canvas.multi_line(
        branch_east[["from_x", "to_x"]].to_numpy().tolist(),
        branch_east[["from_y", "to_y"]].to_numpy().tolist(),
        color="#8B36FF",
        line_width=branch_east["rateA"].abs() * 1e-3 *
        branch_width_scale_factor,
        legend_label="Eastern",
    )

    canvas.multi_line(
        branch_tx[["from_x", "to_x"]].to_numpy().tolist(),
        branch_tx[["from_y", "to_y"]].to_numpy().tolist(),
        color="#01D4ED",
        line_width=branch_tx["rateA"].abs() * 1e-3 * branch_width_scale_factor,
        legend_label="Texas",
    )

    canvas.multi_line(
        dcline[["from_x", "to_x"]].to_numpy().tolist(),
        dcline[["from_y", "to_y"]].to_numpy().tolist(),
        color="#FF2370",
        line_width=dcline["Pmax"] * 1e-3 * hvdc_width_scale_factor,
        legend_label="HVDC",
    )

    canvas.scatter(
        x=b2b["from_x"],
        y=b2b["from_y"],
        color="#FF2370",
        marker="triangle",
        size=b2b["Pmax"] * 1e-3 * b2b_size_scale_factor,
        legend_label="Back-to-Back",
    )

    canvas.legend.location = "bottom_left"
    canvas.legend.label_text_font_size = "12pt"

    return canvas
def map_risk_bind(
        risk_or_bind,
        scenario=None,
        congestion_stats=None,
        branch=None,
        us_states_dat=None,
        vmin=None,
        vmax=None,
        color_bar_width=500,
        palette=None,
        all_branch_scale_factor=0.5,
        all_branch_min_width=0.2,
        select_branch_scale_factor=1,
        select_branch_min_width=2,
        figsize=(1400, 800),
        show_color_bar=True,
        state_borders_kwargs=None,
):
    """Make map showing risk or binding incidents on US states map.
    Either ``scenario`` XOR (``congestion_stats`` AND ``branch``) must be specified.

    :param str risk_or_bind: specify plotting "risk" or "bind".
    :param powersimdata.scenario.scenario.Scenario scenario: scenario to analyze.
    :param pandas.DataFrame congestion_stats: data frame as returned by
        :func:`postreise.analyze.transmission.utilization.generate_cong_stats`.
    :param pandas.DataFrame branch: branch data frame.
    :param int/float vmin: minimum value for color range. If None, use data minimum.
    :param int/float vmax: maximum value for color range. If None, use data maximum.
    :param int color_bar_width: width of color bar (pixels).
    :param iterable palette: sequence of colors used for color range, passed as
        `palette` kwarg to :func:`bokeh.transform.linear_cmap`.
        If None, default to `postreise.plot.colors.traffic_palette`.
    :param int/float all_branch_scale_factor: scale factor for unhighlighted branches
        (pixels/GW).
    :param int/float all_branch_min_width: minimum width for unhighlighted branches
        (pixels).
    :param int/float select_branch_scale_factor: scale factor for highlighted branches
        (pixels/GW).
    :param int/float select_branch_min_width: minimum width for highlighted branches
        (pixels).
    :param tuple(int, int) figsize: size of the bokeh figure (in pixels).
    :param bool show_color_bar: whether to render the color bar on the figure.
    :param dict state_borders_kwargs: keyword arguments to be passed to
        :func:`postreise.plot.plot_states.add_state_borders`.
    :raises ValueError: if (``scenario`` XOR (``congestion_stats`` AND ``branch``)) are
        not properly specified.
    :return: (*bokeh.plotting.figure*) -- map of lines with risk and bind incidents
        color coded.
    """
    unit_labels = {"risk": "Risk (MWH)", "bind": "Binding incidents"}
    if risk_or_bind not in unit_labels:
        raise ValueError("risk_or_bind must be either 'risk' or 'bind'")
    risk_or_bind_units = unit_labels[risk_or_bind]

    # Check that we've appropriately specified:
    #    `scenario` XOR (`congestion_stats` AND `branch`)
    if scenario is not None:
        branch = scenario.state.get_grid().branch
        congestion_stats = generate_cong_stats(scenario.state.get_pf(), branch)
    elif congestion_stats is not None and branch is not None:
        pass
    else:
        raise ValueError(
            "Either scenario XOR (congestion_stats AND branch) must be specified"
        )

    if palette is None:
        palette = list(traffic_palette)

    # projection steps for mapping
    branch_congestion = pd.concat(
        [branch.loc[congestion_stats.index], congestion_stats], axis=1)
    branch_map_all = project_branch(branch)

    branch_congestion = branch_congestion[branch_congestion[risk_or_bind] > 0]
    branch_congestion.sort_values(by=[risk_or_bind])
    branch_map = project_branch(branch_congestion)
    min_val = branch_congestion[risk_or_bind].min() if vmin is None else vmin
    max_val = branch_congestion[risk_or_bind].max() if vmax is None else vmax
    mapper = linear_cmap(field_name=risk_or_bind,
                         palette=palette,
                         low=min_val,
                         high=max_val)
    multi_line_source = ColumnDataSource({
        "xs":
        branch_map[["from_x", "to_x"]].values.tolist(),
        "ys":
        branch_map[["from_y", "to_y"]].values.tolist(),
        risk_or_bind:
        branch_map[risk_or_bind],
        "value":
        branch_map[risk_or_bind].round(),
        "cap": (branch_map["capacity"] * select_branch_scale_factor / 1000 +
                select_branch_min_width),
        "capacity":
        branch_map.rateA.round(),
    })

    # Create canvas
    canvas = create_map_canvas(figsize=figsize)

    # Add state borders
    default_state_borders_kwargs = {"fill_alpha": 0.0, "background_map": True}
    all_state_borders_kwargs = ({
        **default_state_borders_kwargs,
        **state_borders_kwargs
    } if state_borders_kwargs is not None else default_state_borders_kwargs)
    _check_func_kwargs(add_state_borders, set(all_state_borders_kwargs),
                       "state_borders_kwargs")
    canvas = add_state_borders(canvas, **all_state_borders_kwargs)

    # Add color bar
    if show_color_bar:
        color_bar = ColorBar(
            color_mapper=mapper["transform"],
            width=color_bar_width,
            height=5,
            location=(0, 0),
            title=risk_or_bind_units,
            orientation="horizontal",
            padding=5,
        )
        canvas.add_layout(color_bar, "center")

    canvas.multi_line(
        branch_map_all[["from_x", "to_x"]].to_numpy().tolist(),
        branch_map_all[["from_y", "to_y"]].to_numpy().tolist(),
        color="gray",
        line_width=(branch_map_all["rateA"] * all_branch_scale_factor / 1000 +
                    all_branch_min_width),
        alpha=0.5,
    )
    lines = canvas.multi_line("xs",
                              "ys",
                              color=mapper,
                              line_width="cap",
                              source=multi_line_source)
    hover = HoverTool(
        tooltips=[
            ("Capacity MW", "@capacity"),
            (risk_or_bind_units, "@value"),
        ],
        renderers=[lines],
    )
    canvas.add_tools(hover)
    return canvas
def map_utilization(
        scenario=None,
        utilization_df=None,
        branch=None,
        vmin=None,
        vmax=None,
        color_bar_width=500,
        palette=None,
        branch_scale_factor=0.5,
        branch_min_width=0.2,
        figsize=(1400, 800),
        show_color_bar=True,
        state_borders_kwargs=None,
):
    """Make map showing utilization. Utilization input can either be medians
    only, or can be normalized utilization dataframe.
    Either ``scenario`` XOR (``utilization_df`` AND ``branch``) must be specified.

    :param powersimdata.scenario.scenario.Scenario scenario: scenario to analyze.
    :param pandas.DataFrame utilization_df: utilization returned by
        :func:`postreise.analyze.transmission.utilization.get_utilization`
    :param pandas.DataFrame branch: branch data frame.
    :param int/float vmin: minimum value for color range. If None, use data minimum.
    :param int/float vmax: maximum value for color range. If None, use data maximum.
    :param int color_bar_width: width of color bar (pixels).
    :param iterable palette: sequence of colors used for color range, passed as
        `palette` kwarg to :func:`bokeh.transform.linear_cmap`.
        If None, default to `postreise.plot.colors.traffic_palette`.
    :param int/float branch_scale_factor: scale factor for branches (pixels/GW).
    :param int/float branch_min_width: minimum width for branches (pixels).
    :param tuple(int, int) figsize: size of the bokeh figure (in pixels).
    :param dict state_borders_kwargs: keyword arguments to be passed to
        :func:`postreise.plot.plot_states.add_state_borders`.
    :raises ValueError: if (``scenario`` XOR (``utilization_df`` AND ``branch``)) are
        not properly specified.
    :return: (*bokeh.plotting.figure*) -- map of lines with median utilization color
        coded.
    """
    # Check that we've appropriately specified:
    #    `scenario` XOR (`utilization_df` AND `branch`)
    if scenario is not None:
        branch = scenario.state.get_grid().branch
        utilization_df = get_utilization(branch,
                                         scenario.state.get_pf(),
                                         median=True)
    elif utilization_df is not None and branch is not None:
        pass
    else:
        raise ValueError(
            "Either scenario XOR (utilization_df AND branch) must be specified"
        )

    if palette is None:
        palette = list(traffic_palette)

    branch_mask = branch.rateA != 0
    median_util = utilization_df[branch.loc[branch_mask].index].median()
    branch_utilization = pd.concat(
        [branch.loc[branch_mask],
         median_util.rename("median_utilization")],
        axis=1)
    lines = branch_utilization.loc[(
        branch_utilization["branch_device_type"] == "Line")]

    min_val = lines["median_utilization"].min() if vmin is None else vmin
    max_val = lines["median_utilization"].max() if vmax is None else vmax

    mapper = linear_cmap(
        field_name="median_utilization",
        palette=palette,
        low=min_val,
        high=max_val,
    )

    branch_map = project_branch(branch_utilization)
    branch_map = branch_map.sort_values(by=["median_utilization"])
    branch_map = branch_map[~branch_map.isin([np.nan, np.inf, -np.inf]).any(1)]

    multi_line_source = ColumnDataSource({
        "xs":
        branch_map[["from_x", "to_x"]].values.tolist(),
        "ys":
        branch_map[["from_y", "to_y"]].values.tolist(),
        "median_utilization":
        branch_map.median_utilization,
        "width":
        branch_map.rateA * branch_scale_factor / 1000 + branch_min_width,
        "util":
        branch_map.median_utilization.round(2),
        "capacity":
        branch_map.rateA.round(),
    })

    # Create canvas
    canvas = create_map_canvas(figsize=figsize)

    # Add state borders
    default_state_borders_kwargs = {"fill_alpha": 0.0, "line_width": 2}
    all_state_borders_kwargs = ({
        **default_state_borders_kwargs,
        **state_borders_kwargs
    } if state_borders_kwargs is not None else default_state_borders_kwargs)
    _check_func_kwargs(add_state_borders, set(all_state_borders_kwargs),
                       "state_borders_kwargs")
    canvas = add_state_borders(canvas, **all_state_borders_kwargs)

    # Add color bar
    if show_color_bar:
        color_bar = ColorBar(
            color_mapper=mapper["transform"],
            width=color_bar_width,
            height=5,
            location=(0, 0),
            title="median utilization",
            orientation="horizontal",
            padding=5,
        )
        canvas.add_layout(color_bar, "center")

    lines = canvas.multi_line("xs",
                              "ys",
                              color=mapper,
                              line_width="width",
                              source=multi_line_source)
    hover = HoverTool(
        tooltips=[
            ("Capacity MW", "@capacity"),
            ("Utilization", "@util{f0.00}"),
        ],
        renderers=[lines],
    )
    canvas.add_tools(hover)
    return canvas
Beispiel #7
0
def map_risk_bind(
    risk_or_bind,
    congestion_stats,
    branch,
    us_states_dat=None,
    vmin=None,
    vmax=None,
    is_website=False,
):
    """Makes map showing risk or binding incidents on US states map.

    :param str risk_or_bind: specify plotting "risk" or "bind"
    :param pandas.DataFrame congestion_stats: data frame as returned by
        :func:`postreise.analyze.transmission.utilization.generate_cong_stats`.
    :param pandas.DataFrame branch: branch data frame.
    :param dict us_states_dat: dictionary of state border lats/lons. If None, get
        from :func:`postreise.plot.plot_states.get_state_borders`.
    :param int/float vmin: minimum value for color range. If None, use data minimum.
    :param int/float vmax: maximum value for color range. If None, use data maximum.
    :param bool is_website: changes text/legend formatting to look better on the website
    :return:  -- map of lines with risk and bind incidents color coded
    """
    if us_states_dat is None:
        us_states_dat = get_state_borders()

    if risk_or_bind == "risk":
        risk_or_bind_units = "Risk (MWH)"

    if risk_or_bind == "bind":
        risk_or_bind_units = "Binding incidents"

    # projection steps for mapping
    branch_congestion = pd.concat(
        [branch.loc[congestion_stats.index], congestion_stats], axis=1)
    branch_map_all = project_branch(branch)
    multi_line_source_all = ColumnDataSource({
        "xs":
        branch_map_all[["from_x", "to_x"]].values.tolist(),
        "ys":
        branch_map_all[["from_y", "to_y"]].values.tolist(),
        "cap":
        branch_map_all["rateA"] / 2000 + 0.2,
    })
    a, b = project_borders(us_states_dat)
    tools: str = "pan,wheel_zoom,reset,save"

    branch_congestion = branch_congestion[branch_congestion[risk_or_bind] > 0]
    branch_congestion.sort_values(by=[risk_or_bind])
    branch_map = project_branch(branch_congestion)
    min_val = branch_congestion[risk_or_bind].min() if vmin is None else vmin
    max_val = branch_congestion[risk_or_bind].max() if vmax is None else vmax
    mapper = linear_cmap(field_name=risk_or_bind,
                         palette=traffic_palette,
                         low=min_val,
                         high=max_val)
    color_bar = ColorBar(
        color_mapper=mapper["transform"],
        width=385 if is_website else 500,
        height=5,
        location=(0, 0),
        title=risk_or_bind_units,
        orientation="horizontal",
        padding=5,
    )
    multi_line_source = ColumnDataSource({
        "xs":
        branch_map[["from_x", "to_x"]].values.tolist(),
        "ys":
        branch_map[["from_y", "to_y"]].values.tolist(),
        risk_or_bind:
        branch_map[risk_or_bind],
        "value":
        branch_map[risk_or_bind].round(),
        "cap":
        branch_map["capacity"] / 1000 + 2,
        "capacity":
        branch_map.rateA.round(),
    })

    # Set up figure
    p = figure(
        tools=tools,
        x_axis_location=None,
        y_axis_location=None,
        plot_width=800,
        plot_height=800,
        output_backend="webgl",
        sizing_mode="stretch_both",
        match_aspect=True,
    )
    p.add_layout(color_bar, "center")
    p.add_tile(get_provider(Vendors.CARTODBPOSITRON))
    p.patches(a, b, fill_alpha=0.0, line_color="gray", line_width=1)
    p.multi_line(
        "xs",
        "ys",
        color="gray",
        line_width="cap",
        source=multi_line_source_all,
        alpha=0.5,
    )
    lines = p.multi_line("xs",
                         "ys",
                         color=mapper,
                         line_width="cap",
                         source=multi_line_source)
    hover = HoverTool(
        tooltips=[
            ("Capacity MW", "@capacity"),
            (risk_or_bind_units, "@value"),
        ],
        renderers=[lines],
    )
    p.add_tools(hover)
    return p
Beispiel #8
0
def map_utilization(utilization_df,
                    branch,
                    us_states_dat=None,
                    vmin=None,
                    vmax=None,
                    is_website=False):
    """Makes map showing utilization. Utilization input can either be medians
    only, or can be normalized utilization dataframe

    :param pandas.DataFrame utilization_df: utilization returned by
        :func:`postreise.analyze.transmission.utilization.get_utilization`
    :param pandas.DataFrame branch: branch data frame.
    :param dict us_states_dat: dictionary of state border lats/lons. If None, get
        from :func:`postreise.plot.plot_states.get_state_borders`.
    :param int/float vmin: minimum value for color range. If None, use data minimum.
    :param int/float vmax: maximum value for color range. If None, use data maximum.
    :param bool is_website: changes text/legend formatting to look better on the website
    :return:  -- map of lines with median utilization color coded
    """
    if us_states_dat is None:
        us_states_dat = get_state_borders()

    branch_mask = branch.rateA != 0
    median_util = utilization_df[branch.loc[branch_mask].index].median()
    branch_utilization = pd.concat(
        [branch.loc[branch_mask],
         median_util.rename("median_utilization")],
        axis=1)
    lines = branch_utilization.loc[(
        branch_utilization["branch_device_type"] == "Line")]

    min_val = lines["median_utilization"].min() if vmin is None else vmin
    max_val = lines["median_utilization"].max() if vmax is None else vmax

    mapper1 = linear_cmap(
        field_name="median_utilization",
        palette=traffic_palette,
        low=min_val,
        high=max_val,
    )

    color_bar = ColorBar(
        color_mapper=mapper1["transform"],
        width=385 if is_website else 500,
        height=5,
        location=(0, 0),
        title="median utilization",
        orientation="horizontal",
        padding=5,
    )

    branch_map = project_branch(branch_utilization)
    branch_map = branch_map.sort_values(by=["median_utilization"])
    branch_map = branch_map[~branch_map.isin([np.nan, np.inf, -np.inf]).any(1)]

    # state borders
    a, b = project_borders(us_states_dat)

    multi_line_source = ColumnDataSource({
        "xs":
        branch_map[["from_x", "to_x"]].values.tolist(),
        "ys":
        branch_map[["from_y", "to_y"]].values.tolist(),
        "median_utilization":
        branch_map.median_utilization,
        "cap":
        branch_map.rateA / 2000 + 0.2,
        "util":
        branch_map.median_utilization.round(2),
        "capacity":
        branch_map.rateA.round(),
    })

    # Set up figure
    tools: str = "pan,wheel_zoom,reset,save"

    p = figure(
        tools=tools,
        x_axis_location=None,
        y_axis_location=None,
        plot_width=800,
        plot_height=800,
        output_backend="webgl",
        sizing_mode="stretch_both",
        match_aspect=True,
    )
    p.add_layout(color_bar, "center")
    p.add_tile(get_provider(Vendors.CARTODBPOSITRON))
    p.patches(a, b, fill_alpha=0.0, line_color="gray", line_width=2)
    lines = p.multi_line("xs",
                         "ys",
                         color=mapper1,
                         line_width="cap",
                         source=multi_line_source)
    hover = HoverTool(
        tooltips=[
            ("Capacity MW", "@capacity"),
            ("Utilization", "@util{f0.00}"),
        ],
        renderers=[lines],
    )
    p.add_tools(hover)
    return p
Beispiel #9
0
def map_interconnections(
    grid, state_counts, hover_choice, hvdc_width=1, us_states_dat=None
):
    """Maps transmission lines color coded by interconnection.

    :param powersimdata.input.grid.Grid grid: grid object.
    :param pandas.DataFrame state_counts: state names and node counts, created by
        :func:`count_nodes_per_state`.
    :param str hover_choice: "nodes" for state_counts nodes per state, otherwise HVDC
        capacity in hover over tool tips for hvdc lines only.
    :param float hvdc_width: adjust width of HVDC lines on map.
    :param dict us_states_dat: dictionary of state border lats/lons. If None, get
        from :func:`postreise.plot.plot_states.get_state_borders`.
    :return: (*bokeh.plotting.figure*) -- map of transmission lines.
    """
    if us_states_dat is None:
        us_states_dat = get_state_borders()

    # projection steps for mapping
    branch = grid.branch
    branch_bus = grid.bus
    branch_map = project_branch(branch)
    branch_map["point1"] = list(zip(branch_map.to_lat, branch_map.to_lon))
    branch_map["point2"] = list(zip(branch_map.from_lat, branch_map.from_lon))
    branch_map["dist"] = branch_map.apply(
        lambda row: distance.haversine(row["point1"], row["point2"]), axis=1
    )

    # speed rendering on website by removing very short branches
    branch_map = branch_map.loc[branch_map.dist > 5]

    branch_west = branch_map.loc[branch_map.interconnect == "Western"]
    branch_east = branch_map.loc[branch_map.interconnect == "Eastern"]
    branch_tx = branch_map.loc[branch_map.interconnect == "Texas"]
    branch_mdc = grid.dcline

    branch_mdc["from_lon"] = branch_bus.loc[branch_mdc.from_bus_id, "lon"].values
    branch_mdc["from_lat"] = branch_bus.loc[branch_mdc.from_bus_id, "lat"].values
    branch_mdc["to_lon"] = branch_bus.loc[branch_mdc.to_bus_id, "lon"].values
    branch_mdc["to_lat"] = branch_bus.loc[branch_mdc.to_bus_id, "lat"].values
    branch_mdc = project_branch(branch_mdc)

    # back to backs are index 0-8, treat separately
    branch_mdc1 = branch_mdc.iloc[
        9:,
    ]
    b2b = branch_mdc.iloc[
        0:9,
    ]

    branch_mdc_leg = branch_mdc
    branch_mdc_leg.loc[0:8, ["to_x"]] = np.nan
    branch_mdc_leg["to_x"] = branch_mdc_leg["to_x"].fillna(branch_mdc_leg["from_x"])
    branch_mdc_leg.loc[0:8, ["to_y"]] = np.nan
    branch_mdc_leg["to_y"] = branch_mdc_leg["to_y"].fillna(branch_mdc_leg["from_y"])

    # pseudolines for legend to show hvdc and back to back, plot UNDER map
    multi_line_source6 = ColumnDataSource(
        {
            "xs": branch_mdc_leg[["from_x", "to_x"]].values.tolist(),
            "ys": branch_mdc_leg[["from_y", "to_y"]].values.tolist(),
            "capacity": branch_mdc_leg.Pmax.astype(float) * 0.00023 + 0.2,
            "cap": branch_mdc_leg.Pmax.astype(float),
        }
    )

    # state borders
    a, b = project_borders(us_states_dat, state_list=list(state_counts["state"]))

    # transmission data sources
    line_width_const = 0.000225

    multi_line_source = ColumnDataSource(
        {
            "xs": branch_west[["from_x", "to_x"]].values.tolist(),
            "ys": branch_west[["from_y", "to_y"]].values.tolist(),
            "capacity": branch_west.rateA * line_width_const + 0.1,
        }
    )

    multi_line_source2 = ColumnDataSource(
        {
            "xs": branch_east[["from_x", "to_x"]].values.tolist(),
            "ys": branch_east[["from_y", "to_y"]].values.tolist(),
            "capacity": branch_east.rateA * line_width_const + 0.1,
        }
    )

    multi_line_source3 = ColumnDataSource(
        {
            "xs": branch_tx[["from_x", "to_x"]].values.tolist(),
            "ys": branch_tx[["from_y", "to_y"]].values.tolist(),
            "capacity": branch_tx.rateA * line_width_const + 0.1,
        }
    )
    # hvdc
    multi_line_source4 = ColumnDataSource(
        {
            "xs": branch_mdc1[["from_x", "to_x"]].values.tolist(),
            "ys": branch_mdc1[["from_y", "to_y"]].values.tolist(),
            "capacity": branch_mdc1.Pmax.astype(float) * line_width_const * hvdc_width
            + 0.1,
            "cap": branch_mdc1.Pmax.astype(float),
        }
    )

    # pseudolines for ac
    multi_line_source5 = ColumnDataSource(
        {
            "xs": b2b[["from_x", "to_x"]].values.tolist(),
            "ys": b2b[["from_y", "to_y"]].values.tolist(),
            "capacity": b2b.Pmax.astype(float) * 0.00023 + 0.2,
            "cap": b2b.Pmax.astype(float),
            "col": (
                "#006ff9",
                "#006ff9",
                "#006ff9",
                "#006ff9",
                "#006ff9",
                "#006ff9",
                "#006ff9",
                "#8B36FF",
                "#8B36FF",
            ),
        }
    )

    # lower 48 states, patches
    source = ColumnDataSource(
        dict(
            xs=a,
            ys=b,
            col=["gray" for i in range(48)],
            col2=["gray" for i in range(48)],
            label=list(state_counts["count"]),
            state_name=list(state_counts["state"]),
        )
    )

    # Set up figure
    tools: str = "pan, wheel_zoom, reset, save"

    p = figure(
        tools=tools,
        x_axis_location=None,
        y_axis_location=None,
        plot_width=800,
        plot_height=800,
        output_backend="webgl",
        sizing_mode="stretch_both",
        match_aspect=True,
    )

    # for legend, hidden lines
    leg_clr = ["#006ff9", "#8B36FF", "#01D4ED", "#FF2370"]
    leg_lab = ["Western", "Eastern", "Texas", "HVDC"]
    leg_xs = [-1.084288e07] * 4
    leg_ys = [4.639031e06] * 4

    for (colr, leg, x, y) in zip(leg_clr, leg_lab, leg_xs, leg_ys):
        p.line(x, y, color=colr, width=5, legend=leg)

    # pseudo lines for hover tips
    lines = p.multi_line(
        "xs", "ys", color="black", line_width="capacity", source=multi_line_source6
    )

    # background tiles
    p.add_tile(get_provider(Vendors.CARTODBPOSITRON))

    # state borders
    patch = p.patches("xs", "ys", fill_alpha=0.0, line_color="col", source=source)

    # branches
    source_list = [multi_line_source, multi_line_source2, multi_line_source3]

    for (colr, source) in zip(leg_clr[0:3], source_list):
        p.multi_line("xs", "ys", color=colr, line_width="capacity", source=source)

    p.multi_line(
        "xs", "ys", color="#FF2370", line_width="capacity", source=multi_line_source4
    )
    # pseudo ac
    p.multi_line(
        "xs", "ys", color="col", line_width="capacity", source=multi_line_source5
    )

    # triangles for b2b
    p.scatter(
        x=b2b.from_x,
        y=b2b.from_y,
        color="#FF2370",
        marker="triangle",
        size=b2b.Pmax / 50 + 5,
        legend="Back-to-Back",
    )

    # legend formatting
    p.legend.location = "bottom_right"
    p.legend.label_text_font_size = "12pt"

    if hover_choice == "nodes":
        hover = HoverTool(
            tooltips=[
                ("State", "@state_name"),
                ("Nodes", "@label"),
            ],
            renderers=[patch],
        )

    else:
        hover = HoverTool(
            tooltips=[
                ("HVDC capacity MW", "@cap"),
            ],
            renderers=[lines],
        )

    p.add_tools(hover)

    return p
Beispiel #10
0
def _map_upgrades(
    branch_merge,
    dc_merge,
    state_shapes,
    b2b_indices,
    diff_threshold=100,
    all_branch_scale=1e-3,
    diff_branch_scale=1e-3,
    all_branch_min=0.1,
    diff_branch_min=1.0,
    b2b_scale=5e-3,
):
    """Makes map of branches showing upgrades

    :param pandas.DataFrame branch_merge: branch of scenarios 1 and 2
    :param pandas.DataFrame dc_merge: dclines for scenarios 1 and 2
    :param dict state_shapes: dictionary of state outlines. Keys are state
        abbreviations, values are dict with keys of {"lat", "lon"}, values are
        coordinates, padded by nan values to indicate the end of each polygon before
        the start of the next one. Can be created from shapefile via
        :func:postreise.plot.projection_helpers.`convert_shapefile_to_latlon_dict`.
    :param iterable b2b_indices: list/set/tuple of 'DC lines' which are back-to-backs.
    :param int/float diff_threshold: difference threshold (in MW), above which branches
        are highlighted.
    :param int/float all_branch_scale: scale factor for plotting all branches.
    :param int/float diff_branch_scale: scale factor for plotting branches with
        differences above the threshold.
    :param int/float all_branch_min: minimum width to plot all branches.
    :param int/float diff_branch_min: minimum width to plot branches with significant
        differences.
    :param int/float b2b_scale: scale factor for plotting b2b facilities.
    :return: (*bokeh.plotting.figure.Figure*) -- Bokeh map plot of color-coded upgrades.
    """
    # plotting constants
    bokeh_tools = "pan,wheel_zoom,reset,save"
    legend_alpha = 0.9
    all_elements_alpha = 0.5
    differences_alpha = 0.8

    # data prep
    state_xs, state_ys = project_borders(state_shapes)
    branch_all = project_branch(branch_merge)
    branch_dc = project_branch(dc_merge)

    # For these, we will plot a triangle for the B2B location, plus 'pseudo' AC lines
    # get_level_values allows us to index into MultiIndex as necessary
    b2b_mask = branch_dc.index.get_level_values(0).isin(b2b_indices)
    # .copy() avoids a pandas SettingWithCopyError later
    b2b = branch_dc.iloc[b2b_mask].copy()
    branch_dc_lines = branch_dc.loc[~b2b_mask].copy()

    # Color branches based on upgraded capacity
    branch_all["color"] = np.nan
    branch_all.loc[branch_all["diff"] > diff_threshold,
                   "color"] = colors.be_blue
    branch_all.loc[branch_all["diff"] < -1 * diff_threshold,
                   "color"] = colors.be_purple
    # Color pseudo AC branches based on upgraded capacity
    b2b["color"] = np.nan
    b2b.loc[b2b["diff"] > diff_threshold, "color"] = colors.be_blue
    b2b.loc[b2b["diff"] < -1 * diff_threshold, "color"] = colors.be_purple
    b2b = b2b[~b2b.color.isnull()]
    # Color DC lines based on upgraded capacity
    branch_dc_lines.loc[:, "color"] = np.nan
    branch_dc_lines.loc[branch_dc_lines["diff"] > 0, "color"] = colors.be_green
    branch_dc_lines.loc[branch_dc_lines["diff"] < 0,
                        "color"] = colors.be_lightblue

    # Create ColumnDataSources for bokeh to plot with
    source_all_ac = ColumnDataSource({
        "xs":
        branch_all[["from_x", "to_x"]].values.tolist(),
        "ys":
        branch_all[["from_y", "to_y"]].values.tolist(),
        "cap":
        branch_all["rateA"] * all_branch_scale + all_branch_min,
        "color":
        branch_all["color"],
    })
    # AC branches with significant differences
    ac_diff_branches = branch_all.loc[~branch_all.color.isnull()]
    source_ac_difference = ColumnDataSource({
        "xs":
        ac_diff_branches[["from_x", "to_x"]].values.tolist(),
        "ys":
        ac_diff_branches[["from_y", "to_y"]].values.tolist(),
        "diff":
        (ac_diff_branches["diff"].abs() * diff_branch_scale + diff_branch_min),
        "color":
        ac_diff_branches["color"],
    })
    source_all_dc = ColumnDataSource({
        "xs":
        branch_dc_lines[["from_x", "to_x"]].values.tolist(),
        "ys":
        branch_dc_lines[["from_y", "to_y"]].values.tolist(),
        "cap":
        branch_dc_lines.Pmax * all_branch_scale + all_branch_min,
        "color":
        branch_dc_lines["color"],
    })
    dc_diff_lines = branch_dc_lines.loc[~branch_dc_lines.color.isnull()]
    source_dc_differences = ColumnDataSource({
        "xs":
        dc_diff_lines[["from_x", "to_x"]].values.tolist(),
        "ys":
        dc_diff_lines[["from_y", "to_y"]].values.tolist(),
        "diff":
        dc_diff_lines["diff"].abs() * diff_branch_scale + diff_branch_min,
        "color":
        dc_diff_lines["color"],
    })
    source_pseudoac = ColumnDataSource(  # pseudo ac scen 1
        {
            "xs": b2b[["from_x", "to_x"]].values.tolist(),
            "ys": b2b[["from_y", "to_y"]].values.tolist(),
            "cap": b2b.Pmax * all_branch_scale + all_branch_min,
            "diff": b2b["diff"].abs() * diff_branch_scale + diff_branch_min,
            "color": b2b["color"],
        }
    )

    # Set up figure
    p = figure(
        tools=bokeh_tools,
        x_axis_location=None,
        y_axis_location=None,
        plot_width=1400,
        plot_height=800,
        output_backend="webgl",
        match_aspect=True,
    )
    p.xgrid.visible = False
    p.ygrid.visible = False

    # Build the legend
    leg_x = [-8.1e6] * 2
    leg_y = [5.2e6] * 2

    # These are 'dummy' series to populate the legend with
    if len(branch_dc_lines[branch_dc_lines["diff"] > 0]) > 0:
        p.multi_line(
            leg_x,
            leg_y,
            color=colors.be_green,
            alpha=legend_alpha,
            line_width=10,
            legend_label="Additional HVDC Capacity",
        )
    if len(branch_dc_lines[branch_dc_lines["diff"] < 0]) > 0:
        p.multi_line(
            leg_x,
            leg_y,
            color=colors.be_lightblue,
            alpha=legend_alpha,
            line_width=10,
            legend_label="Reduced HVDC Capacity",
        )
    if len(branch_all[branch_all["diff"] < 0]) > 0:
        p.multi_line(
            leg_x,
            leg_y,
            color=colors.be_purple,
            alpha=legend_alpha,
            line_width=10,
            legend_label="Reduced AC Transmission",
        )
    if len(branch_all[branch_all["diff"] > 0]) > 0:
        p.multi_line(
            leg_x,
            leg_y,
            color=colors.be_blue,
            alpha=legend_alpha,
            line_width=10,
            legend_label="Upgraded AC transmission",
        )
    if len(b2b[b2b["diff"] > 0]) > 0:
        p.scatter(
            x=b2b.from_x[1],
            y=b2b.from_y[1],
            color=colors.be_magenta,
            marker="triangle",
            legend_label="Upgraded B2B capacity",
            size=30,
            alpha=legend_alpha,
        )
    p.legend.location = "bottom_left"
    p.legend.label_text_font_size = "20pt"

    # Everything below gets plotted into the 'main' figure
    # state outlines
    p.patches(
        "xs",
        "ys",
        source=ColumnDataSource({
            "xs": state_xs,
            "ys": state_ys
        }),
        fill_color="white",
        line_color="slategrey",
        line_width=1,
        fill_alpha=1,
    )

    background_plot_dicts = [
        {
            "source": source_all_ac,
            "color": "gray",
            "line_width": "cap"
        },
        {
            "source": source_all_dc,
            "color": "gray",
            "line_width": "cap"
        },
        {
            "source": source_pseudoac,
            "color": "gray",
            "line_width": "cap"
        },
    ]
    for d in background_plot_dicts:
        p.multi_line(
            "xs",
            "ys",
            color=d["color"],
            line_width=d["line_width"],
            source=d["source"],
            alpha=all_elements_alpha,
        )

    # all B2Bs
    p.scatter(
        x=b2b.from_x,
        y=b2b.from_y,
        color="gray",
        marker="triangle",
        size=b2b["Pmax"].abs() * b2b_scale,
        alpha=all_elements_alpha,
    )

    difference_plot_dicts = [
        {
            "source": source_pseudoac,
            "color": "color",
            "line_width": "diff"
        },
        {
            "source": source_ac_difference,
            "color": "color",
            "line_width": "diff"
        },
        {
            "source": source_dc_differences,
            "color": "color",
            "line_width": "diff"
        },
    ]

    for d in difference_plot_dicts:
        p.multi_line(
            "xs",
            "ys",
            color=d["color"],
            line_width=d["line_width"],
            source=d["source"],
            alpha=differences_alpha,
        )

    # B2Bs with differences
    p.scatter(
        x=b2b.from_x,
        y=b2b.from_y,
        color=colors.be_magenta,
        marker="triangle",
        size=b2b["diff"].abs() * b2b_scale,
    )

    return p