topolib 0.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- topolib/__init__.py +4 -0
- topolib/analysis/__init__.py +4 -0
- topolib/analysis/metrics.py +80 -0
- topolib/analysis/traffic_matrix.py +344 -0
- topolib/assets/AMRES.json +1265 -0
- topolib/assets/Abilene.json +285 -0
- topolib/assets/Bell_canada.json +925 -0
- topolib/assets/Brazil.json +699 -0
- topolib/assets/CESNET.json +657 -0
- topolib/assets/CORONET.json +1957 -0
- topolib/assets/China.json +1135 -0
- topolib/assets/DT-14.json +470 -0
- topolib/assets/DT-17.json +525 -0
- topolib/assets/DT-50.json +1515 -0
- topolib/assets/ES-30.json +967 -0
- topolib/assets/EURO-16.json +455 -0
- topolib/assets/FR-43.json +1277 -0
- topolib/assets/FUNET.json +317 -0
- topolib/assets/GCN-BG.json +855 -0
- topolib/assets/GRNET.json +1717 -0
- topolib/assets/HyperOne.json +255 -0
- topolib/assets/IT-21.json +649 -0
- topolib/assets/India.json +517 -0
- topolib/assets/JPN-12.json +331 -0
- topolib/assets/KOREN.json +287 -0
- topolib/assets/NORDUNet.json +783 -0
- topolib/assets/NSFNet.json +399 -0
- topolib/assets/PANEURO.json +757 -0
- topolib/assets/PAVLOV.json +465 -0
- topolib/assets/PLN-12.json +343 -0
- topolib/assets/SANReN.json +161 -0
- topolib/assets/SERBIA-MONTENEGRO.json +139 -0
- topolib/assets/Telefonica-21.json +637 -0
- topolib/assets/Turk_Telekom.json +551 -0
- topolib/assets/UKNet.json +685 -0
- topolib/assets/Vega_Telecom.json +819 -0
- topolib/elements/__init__.py +5 -0
- topolib/elements/link.py +121 -0
- topolib/elements/node.py +230 -0
- topolib/topology/__init__.py +4 -0
- topolib/topology/path.py +84 -0
- topolib/topology/topology.py +469 -0
- topolib/visualization/__init__.py +1 -0
- topolib/visualization/_qt_screenshot.py +103 -0
- topolib/visualization/_qt_window.py +78 -0
- topolib/visualization/_templates.py +101 -0
- topolib/visualization/mapview.py +316 -0
- topolib-0.8.0.dist-info/METADATA +148 -0
- topolib-0.8.0.dist-info/RECORD +51 -0
- topolib-0.8.0.dist-info/WHEEL +4 -0
- topolib-0.8.0.dist-info/licenses/LICENSE +22 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HTML templates for MapView visualization.
|
|
3
|
+
|
|
4
|
+
This module contains HTML/CSS templates used to render topology maps.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Template for the topology title overlay
|
|
8
|
+
TITLE_TEMPLATE = """
|
|
9
|
+
<div style="position: fixed;
|
|
10
|
+
top: 10px; left: 50px; width: 300px; height: 50px;
|
|
11
|
+
background-color: white; border:2px solid grey; z-index:9999;
|
|
12
|
+
font-size:16px; font-weight: bold; padding: 10px">
|
|
13
|
+
{title}
|
|
14
|
+
</div>
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
# Template for node popup information
|
|
18
|
+
NODE_POPUP_TEMPLATE = """
|
|
19
|
+
<div style="width: 200px">
|
|
20
|
+
<h4>{name}</h4>
|
|
21
|
+
<b>ID:</b> {id}<br>
|
|
22
|
+
<b>Latitude:</b> {latitude:.6f}<br>
|
|
23
|
+
<b>Longitude:</b> {longitude:.6f}<br>
|
|
24
|
+
{optional_attributes}
|
|
25
|
+
</div>
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
# Template for link popup information
|
|
29
|
+
LINK_POPUP_TEMPLATE = """
|
|
30
|
+
<b>Link {id}</b><br>
|
|
31
|
+
From: {source_name} (ID: {source_id})<br>
|
|
32
|
+
To: {target_name} (ID: {target_id})<br>
|
|
33
|
+
Length: {length:.2f} km
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
# Node marker style configuration
|
|
37
|
+
NODE_MARKER_CONFIG = {
|
|
38
|
+
'radius': 8,
|
|
39
|
+
'color': 'blue',
|
|
40
|
+
'fill': True,
|
|
41
|
+
'fillColor': 'blue',
|
|
42
|
+
'fillOpacity': 0.7
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Link line style configuration
|
|
46
|
+
LINK_LINE_CONFIG = {
|
|
47
|
+
'color': 'gray',
|
|
48
|
+
'weight': 2,
|
|
49
|
+
'opacity': 0.7
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Map default configuration
|
|
53
|
+
MAP_DEFAULT_CONFIG = {
|
|
54
|
+
'zoom_start': 5,
|
|
55
|
+
'tiles': 'OpenStreetMap',
|
|
56
|
+
'control_scale': True
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def format_node_popup(node) -> str:
|
|
61
|
+
"""Format a node's information for popup display.
|
|
62
|
+
|
|
63
|
+
:param node: Node object to format.
|
|
64
|
+
:return: Formatted HTML string.
|
|
65
|
+
:rtype: str
|
|
66
|
+
"""
|
|
67
|
+
optional_attrs = []
|
|
68
|
+
|
|
69
|
+
if hasattr(node, 'weight') and node.weight:
|
|
70
|
+
optional_attrs.append(f"<b>Weight:</b> {node.weight}<br>")
|
|
71
|
+
if hasattr(node, 'pop') and node.pop:
|
|
72
|
+
optional_attrs.append(f"<b>Population:</b> {node.pop:,}<br>")
|
|
73
|
+
if hasattr(node, 'dc') and node.dc:
|
|
74
|
+
optional_attrs.append(f"<b>DC:</b> {node.dc}<br>")
|
|
75
|
+
if hasattr(node, 'ixp') and node.ixp:
|
|
76
|
+
optional_attrs.append(f"<b>IXP:</b> {node.ixp}<br>")
|
|
77
|
+
|
|
78
|
+
return NODE_POPUP_TEMPLATE.format(
|
|
79
|
+
name=node.name,
|
|
80
|
+
id=node.id,
|
|
81
|
+
latitude=node.latitude,
|
|
82
|
+
longitude=node.longitude,
|
|
83
|
+
optional_attributes=''.join(optional_attrs)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def format_link_popup(link) -> str:
|
|
88
|
+
"""Format a link's information for popup display.
|
|
89
|
+
|
|
90
|
+
:param link: Link object to format.
|
|
91
|
+
:return: Formatted HTML string.
|
|
92
|
+
:rtype: str
|
|
93
|
+
"""
|
|
94
|
+
return LINK_POPUP_TEMPLATE.format(
|
|
95
|
+
id=link.id,
|
|
96
|
+
source_name=link.source.name,
|
|
97
|
+
source_id=link.source.id,
|
|
98
|
+
target_name=link.target.name,
|
|
99
|
+
target_id=link.target.id,
|
|
100
|
+
length=link.length
|
|
101
|
+
)
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MapView class for visualizing network topologies.
|
|
3
|
+
|
|
4
|
+
This module provides visualization methods for Topology objects using Folium.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Optional
|
|
8
|
+
import webbrowser
|
|
9
|
+
import tempfile
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
import re
|
|
13
|
+
import urllib.request
|
|
14
|
+
import hashlib
|
|
15
|
+
import subprocess
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from topolib.topology import Topology
|
|
18
|
+
import folium
|
|
19
|
+
from folium import plugins
|
|
20
|
+
from topolib.elements.link import Link
|
|
21
|
+
from topolib.elements.node import Node
|
|
22
|
+
from . import _templates
|
|
23
|
+
|
|
24
|
+
# Global cache directory for downloaded resources
|
|
25
|
+
CACHE_DIR = Path.home() / '.topolib' / 'cache'
|
|
26
|
+
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class MapView:
|
|
30
|
+
"""
|
|
31
|
+
Provides interactive visualization methods for Topology objects using Folium.
|
|
32
|
+
|
|
33
|
+
This class creates interactive HTML maps with OpenStreetMap tiles,
|
|
34
|
+
displaying network nodes as clickable markers and links as lines between them.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, topology: Topology) -> None:
|
|
38
|
+
"""Create a MapView instance for a Topology.
|
|
39
|
+
|
|
40
|
+
:param topology: Topology object to visualize.
|
|
41
|
+
:type topology: topolib.topology.Topology
|
|
42
|
+
"""
|
|
43
|
+
self.topology = topology
|
|
44
|
+
self._map = None
|
|
45
|
+
|
|
46
|
+
def _create_map(self, include_controls: bool = True) -> folium.Map:
|
|
47
|
+
"""Create a Folium map with the current topology.
|
|
48
|
+
|
|
49
|
+
:param include_controls: Whether to include interactive controls (zoom, fullscreen, etc.).
|
|
50
|
+
:type include_controls: bool
|
|
51
|
+
:return: Folium Map object with nodes and links rendered.
|
|
52
|
+
:rtype: folium.Map
|
|
53
|
+
"""
|
|
54
|
+
# Get topology name
|
|
55
|
+
topo_name = getattr(self.topology, "name", None) or "Topology"
|
|
56
|
+
|
|
57
|
+
# Calculate map center
|
|
58
|
+
if self.topology.nodes:
|
|
59
|
+
avg_lat = sum(node.latitude for node in self.topology.nodes) / len(
|
|
60
|
+
self.topology.nodes
|
|
61
|
+
)
|
|
62
|
+
avg_lon = sum(node.longitude for node in self.topology.nodes) / len(
|
|
63
|
+
self.topology.nodes
|
|
64
|
+
)
|
|
65
|
+
else:
|
|
66
|
+
avg_lat, avg_lon = 0, 0
|
|
67
|
+
|
|
68
|
+
# Create base map using template configuration
|
|
69
|
+
map_config = _templates.MAP_DEFAULT_CONFIG.copy()
|
|
70
|
+
if not include_controls:
|
|
71
|
+
map_config['zoom_control'] = False
|
|
72
|
+
map_config['control_scale'] = False
|
|
73
|
+
|
|
74
|
+
m = folium.Map(
|
|
75
|
+
location=[avg_lat, avg_lon],
|
|
76
|
+
**map_config
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Disable attribution if no controls
|
|
80
|
+
if not include_controls:
|
|
81
|
+
m.get_root().html.add_child(folium.Element("""
|
|
82
|
+
<style>
|
|
83
|
+
.leaflet-control-attribution {
|
|
84
|
+
display: none !important;
|
|
85
|
+
}
|
|
86
|
+
.leaflet-control-zoom {
|
|
87
|
+
display: none !important;
|
|
88
|
+
}
|
|
89
|
+
.leaflet-bar {
|
|
90
|
+
display: none !important;
|
|
91
|
+
}
|
|
92
|
+
</style>
|
|
93
|
+
"""))
|
|
94
|
+
|
|
95
|
+
# Add title using template
|
|
96
|
+
title_html = _templates.TITLE_TEMPLATE.format(title=topo_name)
|
|
97
|
+
m.get_root().html.add_child(folium.Element(title_html))
|
|
98
|
+
|
|
99
|
+
# Draw links first (so they appear below nodes)
|
|
100
|
+
for link in self.topology.links:
|
|
101
|
+
coords = [
|
|
102
|
+
[link.source.latitude, link.source.longitude],
|
|
103
|
+
[link.target.latitude, link.target.longitude],
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
popup_text = _templates.format_link_popup(link)
|
|
107
|
+
|
|
108
|
+
folium.PolyLine(
|
|
109
|
+
coords,
|
|
110
|
+
popup=folium.Popup(popup_text, max_width=300) if include_controls else None,
|
|
111
|
+
**_templates.LINK_LINE_CONFIG
|
|
112
|
+
).add_to(m)
|
|
113
|
+
|
|
114
|
+
# Draw nodes
|
|
115
|
+
for node in self.topology.nodes:
|
|
116
|
+
popup_html = _templates.format_node_popup(node)
|
|
117
|
+
|
|
118
|
+
folium.CircleMarker(
|
|
119
|
+
location=[node.latitude, node.longitude],
|
|
120
|
+
popup=folium.Popup(popup_html, max_width=300) if include_controls else None,
|
|
121
|
+
tooltip=node.name if include_controls else None,
|
|
122
|
+
**_templates.NODE_MARKER_CONFIG
|
|
123
|
+
).add_to(m)
|
|
124
|
+
|
|
125
|
+
# Add interactive controls only if requested
|
|
126
|
+
if include_controls:
|
|
127
|
+
# Add fullscreen button
|
|
128
|
+
plugins.Fullscreen().add_to(m)
|
|
129
|
+
|
|
130
|
+
# Add measure control for distance measurement
|
|
131
|
+
plugins.MeasureControl(position='topleft').add_to(m)
|
|
132
|
+
|
|
133
|
+
self._map = m
|
|
134
|
+
return m
|
|
135
|
+
|
|
136
|
+
def show_map(self, mode: str = "window") -> None:
|
|
137
|
+
"""Display the interactive map in a web browser or GUI window.
|
|
138
|
+
|
|
139
|
+
Creates an HTML map and displays it either in the system's default web browser
|
|
140
|
+
or in an interactive PyQt window.
|
|
141
|
+
|
|
142
|
+
The map includes:
|
|
143
|
+
- Interactive nodes with clickable popups showing node information
|
|
144
|
+
- Links between nodes with hover tooltips
|
|
145
|
+
- Zoom and pan controls
|
|
146
|
+
- Fullscreen mode
|
|
147
|
+
- Distance measurement tool
|
|
148
|
+
|
|
149
|
+
:param mode: Display mode - "window" opens in PyQt window, "browser" opens in web browser (default: "window").
|
|
150
|
+
:type mode: str
|
|
151
|
+
:raises ImportError: If mode="window" and PyQt6 is not installed.
|
|
152
|
+
:returns: None
|
|
153
|
+
"""
|
|
154
|
+
# Suppress Qt warnings early
|
|
155
|
+
if mode == "window":
|
|
156
|
+
os.environ['QT_LOGGING_RULES'] = '*.debug=false;qt.webenginecontext.debug=false;qt.qpa.windows=false'
|
|
157
|
+
os.environ['QTWEBENGINE_CHROMIUM_FLAGS'] = '--disable-logging --log-level=3'
|
|
158
|
+
|
|
159
|
+
m = self._create_map()
|
|
160
|
+
|
|
161
|
+
# Save to temporary HTML file
|
|
162
|
+
with tempfile.NamedTemporaryFile(
|
|
163
|
+
mode="w", suffix=".html", delete=False, encoding='utf-8'
|
|
164
|
+
) as tmp:
|
|
165
|
+
tmp_path = tmp.name
|
|
166
|
+
m.save(tmp_path)
|
|
167
|
+
|
|
168
|
+
# For window mode, embed external resources
|
|
169
|
+
if mode == "window":
|
|
170
|
+
self._embed_resources(tmp_path)
|
|
171
|
+
self._show_in_window(tmp_path)
|
|
172
|
+
else:
|
|
173
|
+
# Try to open in default web browser
|
|
174
|
+
try:
|
|
175
|
+
webbrowser.open("file://" + os.path.abspath(tmp_path))
|
|
176
|
+
print(
|
|
177
|
+
f"Map opened in browser. If it didn't open, access: file://{os.path.abspath(tmp_path)}")
|
|
178
|
+
except Exception as e:
|
|
179
|
+
print(f"Could not open browser: {e}")
|
|
180
|
+
print(f"You can manually open: {tmp_path}")
|
|
181
|
+
|
|
182
|
+
def _embed_resources(self, html_path: str) -> None:
|
|
183
|
+
"""Embed external CSS and JS resources directly into the HTML file.
|
|
184
|
+
|
|
185
|
+
:param html_path: Path to the HTML file to modify.
|
|
186
|
+
:type html_path: str
|
|
187
|
+
"""
|
|
188
|
+
with open(html_path, 'r', encoding='utf-8') as f:
|
|
189
|
+
html_content = f.read()
|
|
190
|
+
|
|
191
|
+
# Find all external script and link tags
|
|
192
|
+
script_pattern = r'<script src="(https?://[^"]+)"><\/script>'
|
|
193
|
+
link_pattern = r'<link rel="stylesheet" href="(https?://[^"]+)"[^>]*/?>'
|
|
194
|
+
|
|
195
|
+
# Download and embed scripts with caching
|
|
196
|
+
def replace_script(match):
|
|
197
|
+
url = match.group(1)
|
|
198
|
+
try:
|
|
199
|
+
content = self._get_cached_resource(url)
|
|
200
|
+
return f'<script>{content}</script>'
|
|
201
|
+
except Exception as e:
|
|
202
|
+
print(f"Warning: Could not download {url}: {e}")
|
|
203
|
+
return match.group(0) # Keep original if download fails
|
|
204
|
+
|
|
205
|
+
# Download and embed stylesheets with caching
|
|
206
|
+
def replace_link(match):
|
|
207
|
+
url = match.group(1)
|
|
208
|
+
try:
|
|
209
|
+
content = self._get_cached_resource(url)
|
|
210
|
+
return f'<style>{content}</style>'
|
|
211
|
+
except Exception as e:
|
|
212
|
+
print(f"Warning: Could not download {url}: {e}")
|
|
213
|
+
return match.group(0) # Keep original if download fails
|
|
214
|
+
|
|
215
|
+
html_content = re.sub(script_pattern, replace_script, html_content)
|
|
216
|
+
html_content = re.sub(link_pattern, replace_link, html_content)
|
|
217
|
+
|
|
218
|
+
with open(html_path, 'w', encoding='utf-8') as f:
|
|
219
|
+
f.write(html_content)
|
|
220
|
+
|
|
221
|
+
def _get_cached_resource(self, url: str) -> str:
|
|
222
|
+
"""Get resource from cache or download if not cached.
|
|
223
|
+
|
|
224
|
+
:param url: URL of the resource to download.
|
|
225
|
+
:type url: str
|
|
226
|
+
:return: Content of the resource.
|
|
227
|
+
:rtype: str
|
|
228
|
+
"""
|
|
229
|
+
# Create a hash of the URL to use as filename
|
|
230
|
+
url_hash = hashlib.md5(url.encode()).hexdigest()
|
|
231
|
+
cache_file = CACHE_DIR / url_hash
|
|
232
|
+
|
|
233
|
+
# Check if cached
|
|
234
|
+
if cache_file.exists():
|
|
235
|
+
with open(cache_file, 'r', encoding='utf-8') as f:
|
|
236
|
+
return f.read()
|
|
237
|
+
|
|
238
|
+
# Download and cache
|
|
239
|
+
print(f"Downloading: {url}")
|
|
240
|
+
with urllib.request.urlopen(url, timeout=10) as response:
|
|
241
|
+
content = response.read().decode('utf-8')
|
|
242
|
+
|
|
243
|
+
# Save to cache
|
|
244
|
+
with open(cache_file, 'w', encoding='utf-8') as f:
|
|
245
|
+
f.write(content)
|
|
246
|
+
|
|
247
|
+
return content
|
|
248
|
+
|
|
249
|
+
def _show_in_window(self, html_path: str) -> None:
|
|
250
|
+
"""Display the map in a PyQt window.
|
|
251
|
+
|
|
252
|
+
:param html_path: Path to the HTML file to display.
|
|
253
|
+
:type html_path: str
|
|
254
|
+
:raises ImportError: If PyQt6 is not installed.
|
|
255
|
+
"""
|
|
256
|
+
# Get the path to the Qt window script
|
|
257
|
+
qt_script = Path(__file__).parent / '_qt_window.py'
|
|
258
|
+
|
|
259
|
+
# Run the Qt window in a subprocess for isolation
|
|
260
|
+
window_title = self.topology.name or 'Topology Map'
|
|
261
|
+
subprocess.run([sys.executable, str(qt_script), html_path, window_title], check=True)
|
|
262
|
+
|
|
263
|
+
def export_html(self, filename: str) -> None:
|
|
264
|
+
"""Export the map as a standalone HTML file.
|
|
265
|
+
|
|
266
|
+
:param filename: Output HTML file path.
|
|
267
|
+
:type filename: str
|
|
268
|
+
"""
|
|
269
|
+
m = self._create_map()
|
|
270
|
+
m.save(filename)
|
|
271
|
+
|
|
272
|
+
def export_map_png(self, filename: str, width: int = 1920, height: int = 1080, wait_time: float = 1.0) -> None:
|
|
273
|
+
"""Export the map as a PNG image using PyQt6.
|
|
274
|
+
|
|
275
|
+
:param filename: Output PNG file path.
|
|
276
|
+
:type filename: str
|
|
277
|
+
:param width: Width of the exported image in pixels (default: 1920).
|
|
278
|
+
:type width: int
|
|
279
|
+
:param height: Height of the exported image in pixels (default: 1080).
|
|
280
|
+
:type height: int
|
|
281
|
+
:param wait_time: Time in seconds to wait for map rendering before capture (default: 1.0).
|
|
282
|
+
:type wait_time: float
|
|
283
|
+
:raises ImportError: If PyQt6 is not installed.
|
|
284
|
+
"""
|
|
285
|
+
# Create map without interactive controls for clean export
|
|
286
|
+
m = self._create_map(include_controls=False)
|
|
287
|
+
|
|
288
|
+
# Save to temporary HTML file
|
|
289
|
+
with tempfile.NamedTemporaryFile(
|
|
290
|
+
mode="w", suffix=".html", delete=False, encoding='utf-8'
|
|
291
|
+
) as tmp:
|
|
292
|
+
tmp_path = tmp.name
|
|
293
|
+
m.save(tmp_path)
|
|
294
|
+
|
|
295
|
+
# Embed resources for offline rendering
|
|
296
|
+
self._embed_resources(tmp_path)
|
|
297
|
+
|
|
298
|
+
# Get the path to the screenshot script
|
|
299
|
+
screenshot_script = Path(__file__).parent / '_qt_screenshot.py'
|
|
300
|
+
|
|
301
|
+
# Run the screenshot script in a subprocess
|
|
302
|
+
try:
|
|
303
|
+
subprocess.run(
|
|
304
|
+
[sys.executable, str(screenshot_script), tmp_path, filename, str(width), str(height), str(wait_time)],
|
|
305
|
+
check=True,
|
|
306
|
+
capture_output=True,
|
|
307
|
+
text=True
|
|
308
|
+
)
|
|
309
|
+
except subprocess.CalledProcessError as e:
|
|
310
|
+
raise RuntimeError(f"Failed to capture screenshot: {e.stderr}")
|
|
311
|
+
finally:
|
|
312
|
+
# Clean up temp file
|
|
313
|
+
try:
|
|
314
|
+
os.unlink(tmp_path)
|
|
315
|
+
except:
|
|
316
|
+
pass
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: topolib
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: A compact Python library for modeling, analyzing, and visualizing optical network topologies.
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: network,topology,optical,analysis,visualization
|
|
8
|
+
Author: Danilo Bórquez-Paredes
|
|
9
|
+
Author-email: danilo.borquez.p@uai.cl
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Dist: PyQt6 (>=6.0)
|
|
23
|
+
Requires-Dist: PyQt6-WebEngine (>=6.0)
|
|
24
|
+
Requires-Dist: folium (>=0.14.0)
|
|
25
|
+
Requires-Dist: jsonschema (>=4.0)
|
|
26
|
+
Requires-Dist: networkx (>=2.6)
|
|
27
|
+
Requires-Dist: numpy (>=1.21)
|
|
28
|
+
Project-URL: Documentation, https://topolib.readthedocs.io/
|
|
29
|
+
Project-URL: Homepage, https://gitlab.com/DaniloBorquez/topolib
|
|
30
|
+
Project-URL: Repository, https://gitlab.com/DaniloBorquez/topolib
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# Topolib 🚀
|
|
34
|
+
|
|
35
|
+
[](https://www.python.org/)
|
|
36
|
+
[](LICENSE)
|
|
37
|
+
[](https://gitlab.com/DaniloBorquez/topolib/-/issues)
|
|
38
|
+
[](https://gitlab.com/DaniloBorquez/topolib/-/pipelines?ref=develop)
|
|
39
|
+
[](https://gitlab.com/DaniloBorquez/topolib/-/pipelines?ref=release)
|
|
40
|
+
[](https://topolib.readthedocs.io/en/latest/?badge=latest)
|
|
41
|
+
|
|
42
|
+
> **Topolib** is a compact, modular Python library for modeling, analyzing, and visualizing optical network topologies.
|
|
43
|
+
> **Goal:** Provide researchers and engineers with a simple, extensible toolkit for working with nodes, links, metrics, and map-based visualizations.
|
|
44
|
+
>
|
|
45
|
+
> 🌐 **Model** | 📊 **Analyze** | 🗺️ **Visualize** | 🧩 **Extend**
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 📂 Examples
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
Explore ready-to-run usage examples in the [`examples/`](examples/) folder!
|
|
53
|
+
|
|
54
|
+
- [Show topology on a map](examples/show_topology_in_map.py) 🗺️
|
|
55
|
+
- [Show default topology in map](examples/show_default_topology_in_map.py) 🗺️
|
|
56
|
+
- [Export topology as PNG](examples/export_topology_png.py) 🖼️
|
|
57
|
+
- [Export topology to CSV and JSON](examples/export_csv_json.py) 📄
|
|
58
|
+
- [Export topology and k-shortest paths for FlexNetSim](examples/export_flexnetsim.py) 🔀
|
|
59
|
+
- [Generate traffic demand matrices](examples/traffic_matrices.py) 📊
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 🧭 Overview
|
|
64
|
+
|
|
65
|
+
Topolib is organized into four main modules:
|
|
66
|
+
|
|
67
|
+
- 🧱 **Elements:** `Node`, `Link` — basic building blocks
|
|
68
|
+
- 🕸️ **Topology:** `Topology`, `Path` — manage nodes, links, paths, and adjacency
|
|
69
|
+
- 📈 **Analysis:** `Metrics`, `TrafficMatrix` — compute node degree, link stats, connection matrices, and traffic demand matrices
|
|
70
|
+
- 🖼️ **Visualization:** `MapView` — interactive maps with Folium and PyQt6, clean PNG exports
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## ✨ Features
|
|
75
|
+
|
|
76
|
+
- Modular, extensible design
|
|
77
|
+
- Easy-to-use classes for nodes, links, and paths
|
|
78
|
+
- Built-in metrics and analysis helpers
|
|
79
|
+
- Traffic demand matrix generation with three models (gravitational, DC/IXP, distribution probability)
|
|
80
|
+
- Returns NumPy arrays for efficient mathematical operations
|
|
81
|
+
- Interactive map visualization with Folium and PyQt6
|
|
82
|
+
- Clean PNG export without external dependencies (no Selenium required)
|
|
83
|
+
- Resource caching for faster map rendering
|
|
84
|
+
- JSON import/export and interoperability
|
|
85
|
+
- Ready for Sphinx, Read the Docs, and PyPI
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## ⚡ Quickstart
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
python -m venv .venv
|
|
93
|
+
source .venv/bin/activate
|
|
94
|
+
pip install -U pip
|
|
95
|
+
pip install topolib
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 📚 Documentation
|
|
101
|
+
|
|
102
|
+
Full documentation: [https://topolib.readthedocs.io/](https://topolib.readthedocs.io/)
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 📝 Basic usage
|
|
107
|
+
|
|
108
|
+
### Creating a topology
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from topolib.elements.node import Node
|
|
112
|
+
from topolib.topology.topology import Topology
|
|
113
|
+
|
|
114
|
+
n1 = Node(1, 'A', 10.0, 20.0)
|
|
115
|
+
n2 = Node(2, 'B', 11.0, 21.0)
|
|
116
|
+
topo = Topology(nodes=[n1, n2])
|
|
117
|
+
# Add links, compute metrics, visualize, etc.
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Generating traffic matrices
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from topolib.topology import Topology
|
|
124
|
+
from topolib.analysis import TrafficMatrix
|
|
125
|
+
|
|
126
|
+
# Load a topology
|
|
127
|
+
topo = Topology.load_default_topology("DT-14")
|
|
128
|
+
|
|
129
|
+
# Generate traffic matrix using gravitational model
|
|
130
|
+
matrix = TrafficMatrix.gravitational(topo, rate=0.015)
|
|
131
|
+
# Returns NumPy array: matrix[i, j] = traffic from node i to j (Gbps)
|
|
132
|
+
|
|
133
|
+
# Export to CSV
|
|
134
|
+
TrafficMatrix.to_csv(matrix, topo, "traffic_matrix.csv")
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 🛠️ Development
|
|
140
|
+
|
|
141
|
+
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for development guidelines, commit message rules, and pre-commit setup.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 📄 License
|
|
146
|
+
|
|
147
|
+
MIT — see [`LICENSE`](LICENSE) for details.
|
|
148
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
topolib/__init__.py,sha256=iLmy2rOkHS_4KZWMD8BgT7R3tLMKeaTCDVf3B4FyYxM,91
|
|
2
|
+
topolib/analysis/__init__.py,sha256=946BTvvd_xP47YGgQBlUoC1L0naX-iGycMhsp-hXGzE,111
|
|
3
|
+
topolib/analysis/metrics.py,sha256=2o5PlMVzepDWwSQjzamtKemggqkxh9JuzzCUaeahfok,2555
|
|
4
|
+
topolib/analysis/traffic_matrix.py,sha256=E3PGja6z7t1254AxYCkvUIMFltrQFDBTVeYuMMPKHDQ,10670
|
|
5
|
+
topolib/assets/AMRES.json,sha256=PWQLEOOIpQzDelkWg27EUTg331Ewlqm-jIQGMNyIIdo,27082
|
|
6
|
+
topolib/assets/Abilene.json,sha256=5cnPMbjsNDjmpySdQDYQ2yd83zOFvCRjlAnbX1wyzgk,6010
|
|
7
|
+
topolib/assets/Bell_canada.json,sha256=pjsZBZcp6j-IP57P8MOlcBqnfnLkzbintKzgJxcC7ZE,19921
|
|
8
|
+
topolib/assets/Brazil.json,sha256=_4rSY46CKBd6ylpLLigaUuWjLrP83OYiYorg4wChmFU,14876
|
|
9
|
+
topolib/assets/CESNET.json,sha256=i6ebe-cDZJtTCDbbeuBo-dQwb7dhD4SRdc0I0N0Eczo,13739
|
|
10
|
+
topolib/assets/CORONET.json,sha256=dSpBSR-vBdxeG1IPcnjw5YYwpzujKMceBuLghGt3xQQ,42017
|
|
11
|
+
topolib/assets/China.json,sha256=L3co98t0MYzPApUi0Hv9i-QthDPHZQSsGcCvunspsGA,23820
|
|
12
|
+
topolib/assets/DT-14.json,sha256=IyNZIWelc2lW1eOa6nHOH4HRfCIOGZtBdwFUQhiS5is,9924
|
|
13
|
+
topolib/assets/DT-17.json,sha256=LBqGD3e3ad9nSaFlnJnrIWMk1eEVC6uF6zDElFzPUu8,10972
|
|
14
|
+
topolib/assets/DT-50.json,sha256=4Pe6R3eWaiHB_KOWO2Pprm3veHl6ttZA2dPyh_GxEGQ,31869
|
|
15
|
+
topolib/assets/ES-30.json,sha256=gXrrOGNveIlWNvO214FHcGo08pCCs1mElCRHXdvIJGY,20186
|
|
16
|
+
topolib/assets/EURO-16.json,sha256=LEd_oq9nY9XP-5wzrCcmhe3VI0D_iDlaNg1lAjeRHik,9513
|
|
17
|
+
topolib/assets/FR-43.json,sha256=QK4-95ETMw4C98jMzpgkpL8y2vlSkbiS7mN9-9QCEPw,26797
|
|
18
|
+
topolib/assets/FUNET.json,sha256=RtVbWVmJHGO-a8gSqCiGWkuxjfou-Ep1JWV4b9M2qIc,6650
|
|
19
|
+
topolib/assets/GCN-BG.json,sha256=e24fO_IlVUu6x9S2zpSJWBD7S9Obkw39TkHcKGzayDQ,18299
|
|
20
|
+
topolib/assets/GRNET.json,sha256=xJMwR4DGANRBcLdjv_8gWQp3ymuSSCR2Cws9_sAx9eg,36781
|
|
21
|
+
topolib/assets/HyperOne.json,sha256=jP-BT5jmozezD0AgOkwdb6nXyqiiL6TMy6BjjVOKEGE,5264
|
|
22
|
+
topolib/assets/IT-21.json,sha256=GmYsmC8jycG1WYGwRvVJtnrurZQ7uy8LN0Kmb8Zmg6w,13503
|
|
23
|
+
topolib/assets/India.json,sha256=7wiXKgWBIO0THTpV8pNE8TCriV-zfytI2lPKK6Y3Zoc,10839
|
|
24
|
+
topolib/assets/JPN-12.json,sha256=qz4uZAjE4GvLQnlBpKAiC9Zrp197r5hI8029hsRlDEY,6902
|
|
25
|
+
topolib/assets/KOREN.json,sha256=lKW2if-IdGxQsiX6vpysXpYw__3_6OTWeZ9-oD-h5Eg,5951
|
|
26
|
+
topolib/assets/NORDUNet.json,sha256=X934bx4bTyn98ASz8Eb0-UuEBxnR-3vTy14-gFn8Oms,16412
|
|
27
|
+
topolib/assets/NSFNet.json,sha256=LeU7h6H76HXEyIpUhlzuR6cbs6Q61eEgxmyUMaNUeEk,8375
|
|
28
|
+
topolib/assets/PANEURO.json,sha256=Xh_pmTBOwlusDhAUGpPzJz4tSRXbK-FTsN3Dnlbwf48,15969
|
|
29
|
+
topolib/assets/PAVLOV.json,sha256=YRDqlodGj-zqErX1cwk4bX-XuUkgP1CpiOmtlLIBmIk,9816
|
|
30
|
+
topolib/assets/PLN-12.json,sha256=oxKs0MTe-pEqD-kTa_LATWfDRswwAnlEFubeQlXMFRU,7135
|
|
31
|
+
topolib/assets/SANReN.json,sha256=VUe7JFbzmte6ED3lNyfm1cWhefsapeZ4P-zIG6-rZXc,3394
|
|
32
|
+
topolib/assets/SERBIA-MONTENEGRO.json,sha256=83Mk5yNju7B50JolE1uCwy8wQJgsaBzZ71xxhMGYOSc,2897
|
|
33
|
+
topolib/assets/Telefonica-21.json,sha256=Wom5SQn3lLL-z-ESZ3Du_Fz_ZQ2bzP2_bNhzmnmmW_I,13302
|
|
34
|
+
topolib/assets/Turk_Telekom.json,sha256=5yO0cgtJOmh6ulCCIb8g_nDFSmv8Wn52Yt_Oyoc1Y0w,11533
|
|
35
|
+
topolib/assets/UKNet.json,sha256=9BEkRM9YFeMMpih5kyDY34TsOGFzt5KBGsmgWd6KoB0,14210
|
|
36
|
+
topolib/assets/Vega_Telecom.json,sha256=E07ZCvG4exRj1a5DV8lqS3Sdo5tRm_Lc07IzIjP2EW0,17324
|
|
37
|
+
topolib/elements/__init__.py,sha256=RIQGFgI3_R7Saf679LaP8o8D8kVtM-JadZt6XufJcQ4,75
|
|
38
|
+
topolib/elements/link.py,sha256=2YDEkKdCt91Nv8oiv6gfyECWoeYQaShyhi5DGRvKN2o,3376
|
|
39
|
+
topolib/elements/node.py,sha256=ytxxYvIV9kcsnEV8R0IEFpaXT3-cL5g-djcSkcesBOI,5374
|
|
40
|
+
topolib/topology/__init__.py,sha256=2VRhVm4ZvKBiTDB2T8BDmLZBpwGCVFRF3fvtxxC_d28,86
|
|
41
|
+
topolib/topology/path.py,sha256=oUNwmpBcS6LMMAJIxokROm3MVqr7vRR44M3Fh5ADq_w,2057
|
|
42
|
+
topolib/topology/topology.py,sha256=scAP8ik2rHlpmvcB1CbeHwbVKKUAcPbTNm7oSEvTEOo,16520
|
|
43
|
+
topolib/visualization/__init__.py,sha256=wv065-KB5uDbTaQIASPVfMMW5sE76Bs-q0oai48vAzk,29
|
|
44
|
+
topolib/visualization/_qt_screenshot.py,sha256=npLr_FWYdZq3HVN8_uBsPnPREi6cnbDjU1yaC9m4L04,3541
|
|
45
|
+
topolib/visualization/_qt_window.py,sha256=LI1muAAehkjQ_GyDpPGYVr60GF0dTLCbIxqqzBL7XXs,2503
|
|
46
|
+
topolib/visualization/_templates.py,sha256=BkYlTIZjDI1jEIsYnPStmpQXdItwluODGG4_CVVuUes,2599
|
|
47
|
+
topolib/visualization/mapview.py,sha256=Z4EuKNMAXTHE1VEAmXD7IWPVaPRUIt0ZTMJK5kigZNI,11461
|
|
48
|
+
topolib-0.8.0.dist-info/METADATA,sha256=9s4kosTDA_EFPo7EoA303G39sCikjZJVa8yO1ZjSLfw,5257
|
|
49
|
+
topolib-0.8.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
50
|
+
topolib-0.8.0.dist-info/licenses/LICENSE,sha256=kbnIP0XU6f2ualiTjEawdlU81IGPBbwc-_GF3N-1e9E,1081
|
|
51
|
+
topolib-0.8.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
MIT License
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2025 Danilo Bórquez-Paredes
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|