From 23c37e90dee66740a3cc6fe24bb38db6ae5e933b Mon Sep 17 00:00:00 2001 From: Christian Biasuzzi Date: Tue, 3 Sep 2024 19:00:03 +0200 Subject: [PATCH 1/3] adds tie and hvdc lines to the map-viewer widget Signed-off-by: Christian Biasuzzi --- examples/demo_mapviewer_features.ipynb | 39 ++++++++++++++++++ js/networkmapwidget.jsx | 22 ++++++++--- src/pypowsybl_jupyter/networkmapwidget.py | 48 ++++++++++++++++++++++- 3 files changed, 102 insertions(+), 7 deletions(-) diff --git a/examples/demo_mapviewer_features.ipynb b/examples/demo_mapviewer_features.ipynb index 3b3a3db..29fab7c 100644 --- a/examples/demo_mapviewer_features.ipynb +++ b/examples/demo_mapviewer_features.ipynb @@ -114,6 +114,45 @@ "# center the map on a specific substation\n", "network_map2.center_on_substation('P1')" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Example 3\n", + "# creates a network that contains lines and hvdc lines, and adds a substation and a tie line.\n", + "# Then, adds some geographical data extensions, not present in the original network (fictitious coordinates).\n", + "# Finally, display the network using the mapviewer widget\n", + "\n", + "four_substations_network = pn.create_four_substations_node_breaker_network()\n", + "\n", + "import pandas as pd\n", + "four_substations_network.create_substations(id='S5')\n", + "four_substations_network.create_voltage_levels(id='S5VL1', substation_id='S5', topology_kind='BUS_BREAKER', nominal_v=400)\n", + "four_substations_network.create_buses(id='S5VL1B1', voltage_level_id='S5VL1')\n", + "four_substations_network.create_dangling_lines(id='d1', voltage_level_id='S4VL1', node=1, p0=10, q0=3, r=0, x=5, g=0, b=1e-6)\n", + "four_substations_network.create_dangling_lines(id='d2', voltage_level_id='S5VL1', bus_id='S5VL1B1', p0=10, q0=3, r=0, x=5, g=0, b=1e-6)\n", + "four_substations_network.create_tie_lines(id='t1', dangling_line1_id='d1', dangling_line2_id='d2')\n", + "voltage_levels = four_substations_network.get_voltage_levels()\n", + "voltage_levels['name']=voltage_levels.index\n", + "coords = pd.DataFrame(\n", + " [\n", + " [voltage_levels.loc[voltage_levels[\"name\"] == \"S1VL1\", \"substation_id\"].iloc[0], 2.9653506942899255, 46.648820589226624],\n", + " [voltage_levels.loc[voltage_levels[\"name\"] == \"S2VL1\", \"substation_id\"].iloc[0], 4.4868965299190675, 45.224159481986970],\n", + " [voltage_levels.loc[voltage_levels[\"name\"] == \"S3VL1\", \"substation_id\"].iloc[0], 2.4022589283484335, 44.090773206380350],\n", + " [voltage_levels.loc[voltage_levels[\"name\"] == \"S4VL1\", \"substation_id\"].iloc[0], 6.0803264207747825, 44.279779791063575],\n", + " [voltage_levels.loc[voltage_levels[\"name\"] == \"S5VL1\", \"substation_id\"].iloc[0], 7.4221621183374900, 44.279779791063575]\n", + " ], columns=[\"id\", \"longitude\", \"latitude\"]\n", + ").set_index(\"id\")\n", + "\n", + "four_substations_network.remove_extensions('substationPosition', four_substations_network.get_extensions('substationPosition').index)\n", + "four_substations_network.create_extensions('substationPosition', coords[[\"latitude\", \"longitude\"]])\n", + "\n", + "network_map3=NetworkMapWidget(four_substations_network)\n", + "display(network_map3)" + ] } ], "metadata": { diff --git a/js/networkmapwidget.jsx b/js/networkmapwidget.jsx index 62d5bfe..fbc2757 100644 --- a/js/networkmapwidget.jsx +++ b/js/networkmapwidget.jsx @@ -64,14 +64,16 @@ const darkTheme = createTheme({ }); class WidgetMapEquipments extends MapEquipments { - initEquipments(smapdata, lmapdata) { + initEquipments(smapdata, lmapdata, tlmapdata, hlmapdata) { this.updateSubstations(smapdata, true); this.updateLines(lmapdata, true); + this.updateTieLines(tlmapdata, true); + this.updateHvdcLines(hlmapdata, true); } - constructor(smapdata, lmapdata) { + constructor(smapdata, lmapdata, tlmapdata, hlmapdata) { super(); - this.initEquipments(smapdata, lmapdata); + this.initEquipments(smapdata, lmapdata, tlmapdata, hlmapdata); } } @@ -94,6 +96,8 @@ const render = createRender(() => { const [lpos] = useModelState('lpos'); const [smap] = useModelState('smap'); const [lmap] = useModelState('lmap'); + const [tlmap] = useModelState('tlmap'); + const [hlmap] = useModelState('hlmap'); const [use_name] = useModelState('use_name'); @@ -109,7 +113,7 @@ const render = createRender(() => { const [equipmentData, setEquipmentData] = useState({ gdata: new GeoData(new Map(), new Map()), - edata: new WidgetMapEquipments([], []), + edata: new WidgetMapEquipments([], [], [], []), }); useEffect(() => { @@ -119,7 +123,9 @@ const render = createRender(() => { geoData.setLinePositions(JSON.parse(lpos)); const mapEquipments = new WidgetMapEquipments( JSON.parse(smap), - JSON.parse(lmap) + JSON.parse(lmap), + JSON.parse(tlmap), + JSON.parse(hlmap) ); resolve({ gdata: geoData, edata: mapEquipments }); }); @@ -259,6 +265,12 @@ const render = createRender(() => { onLineMenuClick={(equipment, x, y) => showEquipmentMenu(equipment, x, y, 'line') } + onTieLineMenuClick={(equipment, x, y) => + showEquipmentMenu(equipment, x, y, 'tie-line') + } + onHvdcLineMenuClick={(equipment, x, y) => + showEquipmentMenu(equipment, x, y, 'hvdc-line') + } onVoltageLevelMenuClick={(equipment, x, y) => { console.log( `# VoltageLevel menu click: ${JSON.stringify( diff --git a/src/pypowsybl_jupyter/networkmapwidget.py b/src/pypowsybl_jupyter/networkmapwidget.py index 368c1e0..d639c9f 100644 --- a/src/pypowsybl_jupyter/networkmapwidget.py +++ b/src/pypowsybl_jupyter/networkmapwidget.py @@ -16,6 +16,8 @@ CallbackDispatcher ) +import pandas as pd + from pypowsybl.network import Network class NetworkMapWidget(anywidget.AnyWidget): @@ -48,6 +50,8 @@ class NetworkMapWidget(anywidget.AnyWidget): lpos = traitlets.Unicode().tag(sync=True) smap = traitlets.Unicode().tag(sync=True) lmap = traitlets.Unicode().tag(sync=True) + tlmap = traitlets.Unicode().tag(sync=True) + hlmap = traitlets.Unicode().tag(sync=True) use_name = traitlets.Bool().tag(sync=True) @@ -61,11 +65,13 @@ class NetworkMapWidget(anywidget.AnyWidget): def __init__(self, network:Network, sub_id:str = None, use_name:bool = True, display_lines:bool = True, use_line_geodata:bool = False, nominal_voltages_top_tiers_filter = -1, **kwargs): super().__init__(**kwargs) - (lmap, lpos, smap, spos, vl_subs, sub_vls, subs_ids) = self.extract_map_data(network, display_lines, use_line_geodata) + (lmap, lpos, smap, spos, vl_subs, sub_vls, subs_ids, tlmap, hlmap) = self.extract_map_data(network, display_lines, use_line_geodata) self.lmap=json.dumps(lmap) self.lpos=json.dumps(lpos) self.smap=json.dumps(smap) self.spos=json.dumps(spos) + self.tlmap=json.dumps(tlmap) + self.hlmap=json.dumps(hlmap) self.use_name=use_name self.params={"subId": sub_id} self.vl_subs=vl_subs @@ -101,11 +107,45 @@ def center_on_voltage_level(self, vl_id): if sub_id is not None: self.params = {"subId": sub_id} + def get_tie_lines_info(self, network, vls_with_coords): + ties_df=network.get_tie_lines().reset_index()[['id', 'name', 'dangling_line1_id', 'dangling_line2_id']] + danglings_df=network.get_dangling_lines().reset_index()[['id', 'name', 'p', 'i', 'voltage_level_id', 'connected']] + tie_lines_info=[] + if not(ties_df.empty or danglings_df.empty): + tie_d_1 = pd.merge(ties_df, danglings_df, left_on='dangling_line1_id', right_on='id', suffixes=('', '_d1')) + tie_d_1.rename(columns={'name': 'name_T', 'dangling_line1_id_d1': 'dangling_line1_id_d1_D', 'id_d1': 'd_id1_D'}, inplace=True) + tie_d_2 = pd.merge(tie_d_1, danglings_df, left_on='dangling_line2_id', right_on='id', suffixes=('_d1', '_d2')) + tie_res = tie_d_2[['id_d1' ,'name_T', 'voltage_level_id_d1', 'voltage_level_id_d2', 'connected_d1', 'connected_d2', 'p_d1', 'p_d2', 'i_d1', 'i_d2']] + tie_res = tie_res.fillna(0) + tie_res.columns = ['id', 'name', 'voltageLevelId1', 'voltageLevelId2', 'terminal1Connected', 'terminal2Connected', 'p1', 'p2', 'i1', 'i2'] + tie_res=tie_res[tie_res['voltageLevelId1'].isin(vls_with_coords.index) & tie_res['voltageLevelId2'].isin(vls_with_coords.index)] + tie_lines_info = tie_res.to_dict(orient='records') + return tie_lines_info + + def get_hvdc_lines_info(self, network, vls_with_coords): + hvdc_lines_df = network.get_hvdc_lines().reset_index()[['id', 'name', 'converters_mode', 'converter_station1_id', 'converter_station2_id', 'connected1', 'connected2']] + lcc_stations_df = network.get_lcc_converter_stations().reset_index()[['id', 'name', 'p', 'i', 'voltage_level_id']] + vsc_stations_df = network.get_vsc_converter_stations().reset_index()[['id', 'name', 'p', 'i', 'voltage_level_id']] + stations_df = pd.concat([lcc_stations_df, vsc_stations_df]) + hvdc_lines_info = [] + if not(hvdc_lines_df.empty or stations_df.empty): + hvdc_s_1=pd.merge(hvdc_lines_df, stations_df, left_on='converter_station1_id', right_on='id', suffixes=('', '_s1')) + hvdc_s_1.rename(columns={'name': 'name_S', 'id_s1': 'id_s1_S'}, inplace=True) + hvdc_s_2=pd.merge(hvdc_s_1, stations_df, left_on='converter_station2_id', right_on='id', suffixes=('_s1', '_s2')) + h_res= hvdc_s_2[['id_s1' ,'name_S', 'voltage_level_id_s1', 'voltage_level_id_s2', 'connected1', 'connected2', 'p_s1', 'p_s2', 'i_s1', 'i_s2']] + h_res = h_res.fillna(0) + h_res.columns = ['id', 'name', 'voltageLevelId1', 'voltageLevelId2', 'terminal1Connected', 'terminal2Connected', 'p1', 'p2', 'i1', 'i2'] + h_res=h_res[h_res['voltageLevelId1'].isin(vls_with_coords.index) & h_res['voltageLevelId2'].isin(vls_with_coords.index)] + hvdc_lines_info = h_res.to_dict(orient='records') + return hvdc_lines_info + def extract_map_data(self, network, display_lines, use_line_geodata): lmap = [] lpos = [] smap = [] spos = [] + tlmap = [] + hlmap = [] vl_subs = dict() sub_vls = dict() @@ -151,6 +191,10 @@ def extract_map_data(self, network, display_lines, use_line_geodata): coordinates = [{'lat': coord['latitude'], 'lon': coord['longitude']} for coord in lines_positions_from_extensions_grouped_df.get(id_val, [])] if coordinates: lpos.append({'id': id_val, 'coordinates': coordinates}) + + vls_with_coords = vls_subs_df.set_index('id')[[]] + tlmap = self.get_tie_lines_info(network, vls_with_coords) + hlmap = self.get_hvdc_lines_info(network, vls_with_coords) # note that if there are no linePositions for a line, the viewer component draws the lines using the substation positions @@ -184,7 +228,7 @@ def extract_map_data(self, network, display_lines, use_line_geodata): sub_vls = vls_df.groupby('substation_id')['id'].apply(list).to_dict() subs_ids = set(network.get_substations().reset_index()['id']) - return (lmap, lpos, smap, spos, vl_subs, sub_vls, subs_ids) + return (lmap, lpos, smap, spos, vl_subs, sub_vls, subs_ids, tlmap, hlmap) def extract_nominal_voltage_list(self, network, nvls_top_tiers): nvls_filtered = [] From 7f4b3cd7075d70c7b0ca2585757f06789058235b Mon Sep 17 00:00:00 2001 From: Christian Biasuzzi Date: Wed, 4 Sep 2024 11:29:35 +0200 Subject: [PATCH 2/3] fixes demo notebook Signed-off-by: Christian Biasuzzi --- examples/demo_mapviewer_features.ipynb | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/examples/demo_mapviewer_features.ipynb b/examples/demo_mapviewer_features.ipynb index 29fab7c..a2a57ee 100644 --- a/examples/demo_mapviewer_features.ipynb +++ b/examples/demo_mapviewer_features.ipynb @@ -135,20 +135,16 @@ "four_substations_network.create_dangling_lines(id='d1', voltage_level_id='S4VL1', node=1, p0=10, q0=3, r=0, x=5, g=0, b=1e-6)\n", "four_substations_network.create_dangling_lines(id='d2', voltage_level_id='S5VL1', bus_id='S5VL1B1', p0=10, q0=3, r=0, x=5, g=0, b=1e-6)\n", "four_substations_network.create_tie_lines(id='t1', dangling_line1_id='d1', dangling_line2_id='d2')\n", - "voltage_levels = four_substations_network.get_voltage_levels()\n", - "voltage_levels['name']=voltage_levels.index\n", "coords = pd.DataFrame(\n", " [\n", - " [voltage_levels.loc[voltage_levels[\"name\"] == \"S1VL1\", \"substation_id\"].iloc[0], 2.9653506942899255, 46.648820589226624],\n", - " [voltage_levels.loc[voltage_levels[\"name\"] == \"S2VL1\", \"substation_id\"].iloc[0], 4.4868965299190675, 45.224159481986970],\n", - " [voltage_levels.loc[voltage_levels[\"name\"] == \"S3VL1\", \"substation_id\"].iloc[0], 2.4022589283484335, 44.090773206380350],\n", - " [voltage_levels.loc[voltage_levels[\"name\"] == \"S4VL1\", \"substation_id\"].iloc[0], 6.0803264207747825, 44.279779791063575],\n", - " [voltage_levels.loc[voltage_levels[\"name\"] == \"S5VL1\", \"substation_id\"].iloc[0], 7.4221621183374900, 44.279779791063575]\n", - " ], columns=[\"id\", \"longitude\", \"latitude\"]\n", - ").set_index(\"id\")\n", - "\n", - "four_substations_network.remove_extensions('substationPosition', four_substations_network.get_extensions('substationPosition').index)\n", - "four_substations_network.create_extensions('substationPosition', coords[[\"latitude\", \"longitude\"]])\n", + " ['S1', 2.9653506942899255, 46.648820589226624],\n", + " ['S2', 4.4868965299190675, 45.224159481986970],\n", + " ['S3', 2.4022589283484335, 44.090773206380350],\n", + " ['S4', 6.0803264207747825, 44.279779791063575],\n", + " ['S5', 7.4221621183374900, 44.279779791063575]\n", + " ], columns=['id', 'longitude', 'latitude']\n", + ").set_index('id')\n", + "four_substations_network.create_extensions('substationPosition', coords[['latitude', 'longitude']])\n", "\n", "network_map3=NetworkMapWidget(four_substations_network)\n", "display(network_map3)" From b2cc80285052d4c8367520afecb3c2b42fe8fc3e Mon Sep 17 00:00:00 2001 From: Florian Dupuy Date: Wed, 4 Sep 2024 11:49:41 +0200 Subject: [PATCH 3/3] Fixes Signed-off-by: Florian Dupuy --- examples/demo_mapviewer_features.ipynb | 32 +++++++++++++------------- examples/demo_network_explorer.ipynb | 4 ++-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/examples/demo_mapviewer_features.ipynb b/examples/demo_mapviewer_features.ipynb index a2a57ee..cd1dac8 100644 --- a/examples/demo_mapviewer_features.ipynb +++ b/examples/demo_mapviewer_features.ipynb @@ -122,31 +122,31 @@ "outputs": [], "source": [ "# Example 3\n", - "# creates a network that contains lines and hvdc lines, and adds a substation and a tie line.\n", + "# creates a network that contains lines and HVDC lines, and adds a substation and a tie line.\n", "# Then, adds some geographical data extensions, not present in the original network (fictitious coordinates).\n", "# Finally, display the network using the mapviewer widget\n", "\n", - "four_substations_network = pn.create_four_substations_node_breaker_network()\n", + "network3 = pn.create_four_substations_node_breaker_network()\n", "\n", "import pandas as pd\n", - "four_substations_network.create_substations(id='S5')\n", - "four_substations_network.create_voltage_levels(id='S5VL1', substation_id='S5', topology_kind='BUS_BREAKER', nominal_v=400)\n", - "four_substations_network.create_buses(id='S5VL1B1', voltage_level_id='S5VL1')\n", - "four_substations_network.create_dangling_lines(id='d1', voltage_level_id='S4VL1', node=1, p0=10, q0=3, r=0, x=5, g=0, b=1e-6)\n", - "four_substations_network.create_dangling_lines(id='d2', voltage_level_id='S5VL1', bus_id='S5VL1B1', p0=10, q0=3, r=0, x=5, g=0, b=1e-6)\n", - "four_substations_network.create_tie_lines(id='t1', dangling_line1_id='d1', dangling_line2_id='d2')\n", + "network3.create_substations(id='S5')\n", + "network3.create_voltage_levels(id='S5VL1', substation_id='S5', topology_kind='BUS_BREAKER', nominal_v=400)\n", + "network3.create_buses(id='S5VL1B1', voltage_level_id='S5VL1')\n", + "network3.create_dangling_lines(id='d1', voltage_level_id='S4VL1', node=1, p0=10, q0=3, r=0, x=5, g=0, b=1e-6)\n", + "network3.create_dangling_lines(id='d2', voltage_level_id='S5VL1', bus_id='S5VL1B1', p0=10, q0=3, r=0, x=5, g=0, b=1e-6)\n", + "network3.create_tie_lines(id='t1', dangling_line1_id='d1', dangling_line2_id='d2')\n", "coords = pd.DataFrame(\n", " [\n", - " ['S1', 2.9653506942899255, 46.648820589226624],\n", - " ['S2', 4.4868965299190675, 45.224159481986970],\n", - " ['S3', 2.4022589283484335, 44.090773206380350],\n", - " ['S4', 6.0803264207747825, 44.279779791063575],\n", - " ['S5', 7.4221621183374900, 44.279779791063575]\n", - " ], columns=['id', 'longitude', 'latitude']\n", + " ['S1', 46.648820589226624, 2.9653506942899255],\n", + " ['S2', 45.224159481986970, 4.4868965299190675],\n", + " ['S3', 44.090773206380350, 2.4022589283484335],\n", + " ['S4', 44.279779791063575, 6.0803264207747825],\n", + " ['S5', 44.279779791063575, 7.4221621183374900]\n", + " ], columns=['id', 'latitude', 'longitude']\n", ").set_index('id')\n", - "four_substations_network.create_extensions('substationPosition', coords[['latitude', 'longitude']])\n", + "network3.create_extensions('substationPosition', coords[['latitude', 'longitude']])\n", "\n", - "network_map3=NetworkMapWidget(four_substations_network)\n", + "network_map3=NetworkMapWidget(network3)\n", "display(network_map3)" ] } diff --git a/examples/demo_network_explorer.ipynb b/examples/demo_network_explorer.ipynb index 30ab5e2..9869c27 100644 --- a/examples/demo_network_explorer.ipynb +++ b/examples/demo_network_explorer.ipynb @@ -28,7 +28,7 @@ "metadata": {}, "outputs": [], "source": [ - "#activate the network explorer. Note that, since the network doesn't include geo data, the Metwork map tab is empty.\n", + "#activate the network explorer. Note that, since the network doesn't include geo data, the Network map tab is empty.\n", "network_explorer(network, depth=4)" ] }, @@ -50,7 +50,7 @@ "metadata": {}, "outputs": [], "source": [ - "#load a network containig geo data, imported from a CGMES file containing a GL profile (Graphical Layout)\n", + "#load a network containing geo data, imported from a CGMES file containing a GL profile (Graphical Layout)\n", "network_microgrid = pn.load('./data/MicroGridTestConfiguration_T4_BE_BB_Complete_v2.zip', {'iidm.import.cgmes.post-processors': 'cgmesGLImport'})" ] },