Skip to content

Commit

Permalink
Merge pull request e2nIEE#2307 from lezneew/issues/ward-and-xward-col…
Browse files Browse the repository at this point in the history
…lections

Modified collections.py and patch_makers.py to handle wards and xwards
  • Loading branch information
KS-HTK authored Jun 14, 2024
2 parents 41a6eab + 6196f31 commit 73b9318
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 12 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ Change Log
- [ADDED] PowerFactory converter: support load types (constI, constZ) and the setting whether to consider voltage dependency of loads
- [FIXED] deprecation of matplotlib.cm.get_cmap(name) -> matplotlib.colormaps[name]

[2.14.7] - 2024-06-14
-------------------------------
- [ADDED] added PathPatch TextPatch and Affine2D imports needed for ward and xward patches
- [ADDED] added ward_patches function to mark wards in a plot
- [ADDED] added ward_patches function to mark wards in a plot
- [ADDED] added ward_patches and xward_patches imports used in the wad and xward collections
- [ADDED] added create_ward_collection function to enable plotting wards
- [ADDED] added create_xward_collection function to enable plotting xwards
- [FIXED] git issue #2199: List of colors does not apply to Polygon Patches
- [CHANGED] Sonar Lint issues for spacing

[2.14.6] - 2024-04-02
-------------------------------
- [FIXED] more futurewarnings and deprecation warnings
Expand Down
97 changes: 96 additions & 1 deletion pandapower/plotting/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class TextPath: # so that the test does not fail
from pandapower import pandapowerNet
from pandapower.auxiliary import soft_dependency_error
from pandapower.plotting.patch_makers import load_patches, node_patches, gen_patches, \
sgen_patches, ext_grid_patches, trafo_patches, storage_patches
sgen_patches, ext_grid_patches, trafo_patches, storage_patches, ward_patches, xward_patches
from pandapower.plotting.plotting_toolbox import _rotate_dim2, coords_from_node_geodata, \
position_on_busbar, get_index_array

Expand Down Expand Up @@ -1376,6 +1376,101 @@ def create_bus_bus_switch_collection(net, size=1., helper_line_style=':', helper
return switches, helper_lines


def create_ward_collection(net, wards=None, ward_buses=None, size=5., bus_geodata=None, infofunc=None, picker=False,
orientation=0, **kwargs):
"""
Creates a matplotlib patch collection of pandapower wards. Wards are plotted as a grounded impedance.
INPUT:
**net** (pandapowerNet) - The pandapower network
OPTIONAL:
**wards** (list of ints, None) - the wards to include in the collection
**ward_buses** (list of ints, None) - the buses connected to the wards
**size** (float, 1) - patch size
**infofunc** (function, None) - infofunction for the patch elem
**picker** (bool, False) - picker argument passed to the patch collectionent
**orientation** (float, np.pi) - orientation of static generator collection. pi is directed\
downwards, increasing values lead to clockwise direction changes.
**kwargs - key word arguments are passed to the patch function
OUTPUT:
**ward_pc** - patch collection
**ward_lc** - line collection
"""
wards = get_index_array(wards, net.ward.index)
if ward_buses is None:
ward_buses = net.ward.bus.loc[wards].values
else:
assert len(wards) == len(ward_buses), \
"Length mismatch between chosen xwards and xward_buses."
infos = [infofunc(i) for i in range(len(wards))] if infofunc is not None else []
node_coords = net.bus.geo.loc[ward_buses].apply(geojson.loads).apply(geojson.utils.coords).apply(next).values.tolist()

color = kwargs.pop("color", "k")

ward_pc, ward_lc = _create_node_element_collection(
node_coords, ward_patches, size=size, infos=infos, orientation=orientation,
picker=picker, line_color=color, **kwargs) # patch_facecolor=color, patch_edgecolor=color
return ward_pc, ward_lc


def create_xward_collection(net, xwards=None, xward_buses=None, size=5., bus_geodata=None, infofunc=None, picker=False,
orientation=0, **kwargs):
"""
Creates a matplotlib patch collection of pandapower xwards. Extended wards are plotted as a grounded impedance with
a generator.
INPUT:
**net** (pandapowerNet) - The pandapower network
OPTIONAL:
**xwards** (list of ints, None) - the wards to include in the collection
**xward_buses** (list of ints, None) - the buses connected to the wards
**size** (float, 1) - patch size
**infofunc** (function, None) - infofunction for the patch elem
**picker** (bool, False) - picker argument passed to the patch collectionent
**orientation** (float, np.pi) - orientation of static generator collection. pi is directed\
downwards, increasing values lead to clockwise direction changes.
**kwargs - key word arguments are passed to the patch function
OUTPUT:
**ward_pc** - patch collection
**ward_lc** - line collection
"""
xwards = get_index_array(xwards, net.xward.index)
if xward_buses is None:
xward_buses = net.xward.bus.loc[xwards].values
else:
assert len(xwards) == len(xward_buses), \
"Length mismatch between chosen xwards and xward_buses."
infos = [infofunc(i) for i in range(len(xwards))] if infofunc is not None else []
node_coords = net.bus.geo.loc[xward_buses].apply(geojson.loads).apply(geojson.utils.coords).apply(next).values.tolist()

color = kwargs.pop("color", "w")

xward_pc, xward_lc = _create_node_element_collection(
node_coords, xward_patches, size=size, infos=infos, orientation=orientation,
picker=picker, line_color=color, **kwargs)
return xward_pc, xward_lc


def draw_collections(collections, figsize=(10, 8), ax=None, plot_colorbars=True, set_aspect=True,
axes_visible=(False, False), copy_collections=True, draw=True, aspect=('equal', 'datalim'),
autoscale=(True, True, True)):
Expand Down
114 changes: 103 additions & 11 deletions pandapower/plotting/patch_makers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@

import geojson.utils
import numpy as np

try:
from matplotlib.patches import RegularPolygon, Arc, Circle, Rectangle, Ellipse
from matplotlib.patches import RegularPolygon, Arc, Circle, Rectangle, Ellipse, PathPatch
from matplotlib.textpath import TextPath
from matplotlib.transforms import Affine2D

MATPLOTLIB_INSTALLED = True
except ImportError:
MATPLOTLIB_INSTALLED = False
Expand Down Expand Up @@ -86,7 +90,7 @@ def ellipse_patches(node_coords, width, height, angle=0, color=None, **kwargs):
:return: patches - list of ellipse patches for the nodes
"""
if not MATPLOTLIB_INSTALLED:
soft_dependency_error(str(sys._getframe().f_code.co_name)+"()", "matplotlib")
soft_dependency_error(str(sys._getframe().f_code.co_name) + "()", "matplotlib")
patches = list()
angles = get_angle_list(angle, len(node_coords))
if color is not None:
Expand Down Expand Up @@ -116,7 +120,7 @@ def rectangle_patches(node_coords, width, height, color=None, **kwargs):
:return: patches - list of rectangle patches for the nodes
"""
if not MATPLOTLIB_INSTALLED:
soft_dependency_error(str(sys._getframe().f_code.co_name)+"()", "matplotlib")
soft_dependency_error(str(sys._getframe().f_code.co_name) + "()", "matplotlib")
patches = list()
if color is not None:
colors = get_color_list(color, len(node_coords))
Expand Down Expand Up @@ -147,12 +151,12 @@ def polygon_patches(node_coords, radius, num_edges, color=None, **kwargs):
:return: patches - list of rectangle patches for the nodes
"""
if not MATPLOTLIB_INSTALLED:
soft_dependency_error(str(sys._getframe().f_code.co_name)+"()", "matplotlib")
soft_dependency_error(str(sys._getframe().f_code.co_name) + "()", "matplotlib")
patches = list()
if color is not None:
colors = get_color_list(color, len(node_coords))
for (x, y), col in zip(node_coords, colors):
patches.append(RegularPolygon([x, y], numVertices=num_edges, radius=radius, color=color,
patches.append(RegularPolygon([x, y], numVertices=num_edges, radius=radius, color=col,
**kwargs))
else:
for x, y in node_coords:
Expand All @@ -179,7 +183,7 @@ def load_patches(node_coords, size, angles, **kwargs):
- keywords (set) - set of keywords removed from kwargs
"""
if not MATPLOTLIB_INSTALLED:
soft_dependency_error(str(sys._getframe().f_code.co_name)+"()", "matplotlib")
soft_dependency_error(str(sys._getframe().f_code.co_name) + "()", "matplotlib")
offset = kwargs.get("offset", 1.2 * size)
all_angles = get_angle_list(angles, len(node_coords))
edgecolor = kwargs.get("patch_edgecolor", "w")
Expand Down Expand Up @@ -215,7 +219,7 @@ def gen_patches(node_coords, size, angles, **kwargs):
- keywords (set) - set of keywords removed from kwargs
"""
if not MATPLOTLIB_INSTALLED:
soft_dependency_error(str(sys._getframe().f_code.co_name)+"()", "matplotlib")
soft_dependency_error(str(sys._getframe().f_code.co_name) + "()", "matplotlib")
polys, lines = list(), list()
offset = kwargs.get("offset", 2. * size)
all_angles = get_angle_list(angles, len(node_coords))
Expand Down Expand Up @@ -255,7 +259,7 @@ def sgen_patches(node_coords, size, angles, **kwargs):
- keywords (set) - set of keywords removed from kwargs
"""
if not MATPLOTLIB_INSTALLED:
soft_dependency_error(str(sys._getframe().f_code.co_name)+"()", "matplotlib")
soft_dependency_error(str(sys._getframe().f_code.co_name) + "()", "matplotlib")
polys, lines = list(), list()
offset = kwargs.get("offset", 2 * size)
r_triangle = kwargs.get("r_triangles", size * 0.4)
Expand Down Expand Up @@ -312,7 +316,7 @@ def storage_patches(node_coords, size, angles, **kwargs):
mid_tri1 = mid_circ + _rotate_dim2(np.array([-r_triangle, -r_triangle]), angles[i])

# dropped perpendicular foot of triangle1
perp_foot1 = mid_tri1 + _rotate_dim2(np.array([r_triangle * 0.5, -r_triangle/4]), angles[i])
perp_foot1 = mid_tri1 + _rotate_dim2(np.array([r_triangle * 0.5, -r_triangle / 4]), angles[i])
line_end1 = perp_foot1 + _rotate_dim2(np.array([1 * r_triangle, 0]), angles[i])

perp_foot2 = mid_tri1 + _rotate_dim2(np.array([0, -r_triangle]), angles[i])
Expand Down Expand Up @@ -343,7 +347,7 @@ def ext_grid_patches(node_coords, size, angles, **kwargs):
- keywords (set) - set of keywords removed from kwargs (empty
"""
if not MATPLOTLIB_INSTALLED:
soft_dependency_error(str(sys._getframe().f_code.co_name)+"()", "matplotlib")
soft_dependency_error(str(sys._getframe().f_code.co_name) + "()", "matplotlib")
offset = kwargs.get("offset", 2 * size)
all_angles = get_angle_list(angles, len(node_coords))
edgecolor = kwargs.get("patch_edgecolor", "w")
Expand Down Expand Up @@ -378,7 +382,7 @@ def trafo_patches(coords, size, **kwargs):
- circles (list of Circle) - list containing the transformer patches (rings)
"""
if not MATPLOTLIB_INSTALLED:
soft_dependency_error(str(sys._getframe().f_code.co_name)+"()", "matplotlib")
soft_dependency_error(str(sys._getframe().f_code.co_name) + "()", "matplotlib")
edgecolor = kwargs.get("patch_edgecolor", "w")
facecolor = kwargs.get("patch_facecolor", (1, 0, 0, 0))
edgecolors = get_color_list(edgecolor, len(coords))
Expand Down Expand Up @@ -413,3 +417,91 @@ def trafo_patches(coords, size, **kwargs):
lines.append([p1, lp1])
lines.append([p2, lp2])
return lines, circles, {"patch_edgecolor", "patch_facecolor"}


def ward_patches(node_coords, size, angles, **kwargs):
"""
Creation function of patches for wards.
:param node_coords: coordinates of the nodes that the wards belong to.
:type node_coords: iterable
:param size: size of the patch
:type size: float
:param angles: angles by which to rotate the patches (in radians)
:type angles: iterable(float), float
:param kwargs: additional keyword arguments (might contain parameters "offset",\
"patch_edgecolor" and "patch_facecolor")
:type kwargs:
:return: Return values are: \
- lines (list) - list of coordinates for lines leading to ward patches\
- polys (list of RegularPolygon) - list containing the ward patches\
- keywords (set) - set of keywords removed from kwargs (empty)
"""
if not MATPLOTLIB_INSTALLED:
soft_dependency_error(str(sys._getframe().f_code.co_name) + "()", "matplotlib")
offset = kwargs.get("offset", 2 * size)
all_angles = get_angle_list(angles, len(node_coords))
edgecolor = kwargs.get("patch_edgecolor", "w")
facecolor = kwargs.get("patch_facecolor", "w")
edgecolors = get_color_list(edgecolor, len(node_coords))
facecolors = get_color_list(facecolor, len(node_coords))
polys, lines = list(), list()
for i, node_geo in enumerate(node_coords):
p2 = node_geo + _rotate_dim2(np.array([0, offset]), all_angles[i])
p_ll = p2 + _rotate_dim2(np.array([-size, 0]), all_angles[i])
polys.append(Rectangle(p_ll, 2 * size, 2 * size, angle=(-all_angles[i] / np.pi * 180),
fc=facecolors[i], ec=edgecolors[i]))
lines.append((node_geo, p2))

text_foot = p2 + np.array([-1.425 * size, -0.34 * size]) + _rotate_dim2(np.array([size, size]), all_angles[i])

text_path = TextPath((0, 0), "W", size=0.9 * size, prop=dict(weight='light'))
transform = Affine2D().translate(text_foot[0], text_foot[1]).rotate(all_angles[i])
text_patch = PathPatch(transform.transform_path(text_path), edgecolor='black', facecolor='black', lw=0.5)
polys.append(text_patch)

return lines, polys, {"offset", "patch_edgecolor", "patch_facecolor"}


def xward_patches(node_coords, size, angles, **kwargs):
"""
Creation function of patches for xwards.
:param node_coords: coordinates of the nodes that the xwards belong to.
:type node_coords: iterable
:param size: size of the patch
:type size: float
:param angles: angles by which to rotate the patches (in radians)
:type angles: iterable(float), float
:param kwargs: additional keyword arguments (might contain parameters "offset",\
"patch_edgecolor" and "patch_facecolor")
:type kwargs:
:return: Return values are: \
- lines (list) - list of coordinates for lines leading to elements of xward patches\
- polys (list of RegularPolygon) - list containing the xward patches\
- keywords (set) - set of keywords removed from kwargs (empty)
"""
if not MATPLOTLIB_INSTALLED:
soft_dependency_error(str(sys._getframe().f_code.co_name) + "()", "matplotlib")
offset = kwargs.get("offset", 2 * size)
all_angles = get_angle_list(angles, len(node_coords))
edgecolor = kwargs.get("patch_edgecolor", "k")
facecolor = kwargs.get("patch_facecolor", "w")
edgecolors = get_color_list(edgecolor, len(node_coords))
facecolors = get_color_list(facecolor, len(node_coords))
polys, lines = list(), list()
for i, node_geo in enumerate(node_coords):
p2 = node_geo + _rotate_dim2(np.array([0, offset]), all_angles[i])
p_ll = p2 + _rotate_dim2(np.array([-size, 0]), all_angles[i])
polys.append(Rectangle(p_ll, 2 * size, 2 * size, angle=(-all_angles[i] / np.pi * 180),
fc=facecolors[i], ec=edgecolors[i]))
lines.append((node_geo, p2))

text_foot = p2 + np.array([-1.755 * size, -0.34 * size]) + _rotate_dim2(np.array([size, size]), all_angles[i])

text_path = TextPath((0, 0), "XW", size=0.9 * size, prop=dict(weight='light'))
transform = Affine2D().translate(text_foot[0], text_foot[1]).rotate(all_angles[i])
text_patch = PathPatch(transform.transform_path(text_path), edgecolor='black', facecolor='black', lw=0.5)
polys.append(text_patch)

return lines, polys, {"offset", "patch_edgecolor", "patch_facecolor"}

0 comments on commit 73b9318

Please sign in to comment.