multimodalrouter 0.1.4__tar.gz → 0.1.5__tar.gz
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.
Potentially problematic release.
This version of multimodalrouter might be problematic. Click here for more details.
- multimodalrouter-0.1.5/NOTICE.md +44 -0
- {multimodalrouter-0.1.4/src/multiModalRouter.egg-info → multimodalrouter-0.1.5}/PKG-INFO +13 -3
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/README.md +8 -2
- multimodalrouter-0.1.5/docs/FlightPathPlot.png +0 -0
- multimodalrouter-0.1.5/docs/examples/flightRouter/__pycache__/plot.cpython-313.pyc +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/docs/examples/flightRouter/main.py +7 -6
- multimodalrouter-0.1.5/docs/examples/flightRouter/plot.py +25 -0
- multimodalrouter-0.1.5/docs/examples/mazePathfinder/__pycache__/main.cpython-313.pyc +0 -0
- multimodalrouter-0.1.5/docs/examples/mazePathfinder/__pycache__/plot.cpython-313.pyc +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/docs/examples/mazePathfinder/data/createMaze.py +15 -3
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/docs/examples/mazePathfinder/main.py +21 -11
- multimodalrouter-0.1.5/docs/examples/mazePathfinder/plot.py +32 -0
- multimodalrouter-0.1.5/docs/visualization.md +108 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/pyproject.toml +5 -3
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5/src/multiModalRouter.egg-info}/PKG-INFO +13 -3
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/src/multiModalRouter.egg-info/SOURCES.txt +10 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/src/multiModalRouter.egg-info/requires.txt +4 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/src/multimodalrouter/graph/dataclasses.py +1 -0
- multimodalrouter-0.1.5/src/multimodalrouter/graphics/__init__.py +1 -0
- multimodalrouter-0.1.5/src/multimodalrouter/graphics/graphicsWrapper.py +323 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/LICENSE.md +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/MANIFEST.in +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/docs/cli.md +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/docs/examples/demoData.csv +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/docs/examples/flightRouter/data/fullDataset.csv +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/docs/examples/mazePathfinder/data/maze.csv +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/docs/graph.md +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/docs/installation.md +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/docs/solvedMaze1.png +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/docs/utils.md +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/setup.cfg +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/src/multiModalRouter.egg-info/dependency_links.txt +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/src/multiModalRouter.egg-info/entry_points.txt +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/src/multiModalRouter.egg-info/top_level.txt +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/src/multimodalrouter/__init__.py +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/src/multimodalrouter/graph/__init__.py +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/src/multimodalrouter/graph/graph.py +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/src/multimodalrouter/router/__init__.py +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/src/multimodalrouter/router/build.py +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/src/multimodalrouter/router/route.py +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/src/multimodalrouter/utils/__init__.py +0 -0
- {multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/src/multimodalrouter/utils/preprocessor.py +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Dependencies and Licenses
|
|
2
|
+
|
|
3
|
+
This project `MultiModalRouter` depends on the following libraries. All licenses are permissive and compatible with MIT licensing for this project.
|
|
4
|
+
|
|
5
|
+
| Package | Version | License | License Link |
|
|
6
|
+
|---------|---------|---------|--------------|
|
|
7
|
+
| colorama | >=0.4.6 | BSD 3-Clause | [License](https://github.com/tartley/colorama/blob/master/LICENSE) |
|
|
8
|
+
| dill | >=0.4.0 | BSD | [License](https://github.com/uqfoundation/dill/blob/main/LICENSE) |
|
|
9
|
+
| filelock | >=3.19.1 | MIT | [License](https://github.com/tox-dev/py-filelock/blob/main/LICENSE) |
|
|
10
|
+
| fsspec | >=2025.9.0 | Apache 2.0 | [License](https://github.com/fsspec/filesystem_spec/blob/main/LICENSE) |
|
|
11
|
+
| Jinja2 | >=3.1.6 | BSD-3-Clause | [License](https://github.com/pallets/jinja/blob/main/LICENSE) |
|
|
12
|
+
| MarkupSafe | >=3.0.2 | BSD-3-Clause | [License](https://github.com/pallets/markupsafe/blob/main/LICENSE) |
|
|
13
|
+
| mpmath | >=1.3.0 | BSD | [License](https://github.com/fredrik-johansson/mpmath/blob/master/LICENSE) |
|
|
14
|
+
| networkx | >=3.5 | BSD | [License](https://github.com/networkx/networkx/blob/main/LICENSE.txt) |
|
|
15
|
+
| numpy | >=2.3.3 | BSD | [License](https://github.com/numpy/numpy/blob/main/LICENSE.txt) |
|
|
16
|
+
| pandas | >=2.3.2 | BSD-3-Clause | [License](https://github.com/pandas-dev/pandas/blob/main/LICENSE) |
|
|
17
|
+
| parquet | >=1.3.1 | Apache 2.0 | [License](https://github.com/urschrei/parquet-python/blob/master/LICENSE) |
|
|
18
|
+
| ply | >=3.11 | BSD | [License](https://github.com/dabeaz/ply/blob/master/LICENSE.txt) |
|
|
19
|
+
| pyarrow | >=21.0.0 | Apache 2.0 | [License](https://github.com/apache/arrow/blob/master/LICENSE) |
|
|
20
|
+
| python-dateutil | >=2.9.0.post0 | BSD | [License](https://github.com/dateutil/dateutil/blob/master/LICENSE.txt) |
|
|
21
|
+
| pytz | >=2025.2 | MIT | [License](https://github.com/stub42/pytz/blob/master/LICENSE) |
|
|
22
|
+
| setuptools | >=80.9.0 | MIT | [License](https://github.com/pypa/setuptools/blob/main/LICENSE) |
|
|
23
|
+
| six | >=1.17.0 | MIT | [License](https://github.com/benjaminp/six/blob/master/LICENSE) |
|
|
24
|
+
| sympy | >=1.14.0 | BSD | [License](https://github.com/sympy/sympy/blob/master/LICENSE) |
|
|
25
|
+
| thriftpy2 | >=0.5.3 | MIT | [License](https://github.com/Thriftpy/thriftpy2/blob/master/LICENSE) |
|
|
26
|
+
| tqdm | >=4.67.1 | MPL 2.0 | [License](https://github.com/tqdm/tqdm/blob/master/LICENSE) |
|
|
27
|
+
| typing_extensions | >=4.15.0 | PSF | [License](https://github.com/python/typing_extensions/blob/main/LICENSE) |
|
|
28
|
+
| tzdata | >=2025.2 | Public Domain | [License](https://github.com/python/tzdata) |
|
|
29
|
+
|
|
30
|
+
## Optional Dependencies
|
|
31
|
+
|
|
32
|
+
| Package | Version | License | License Link |
|
|
33
|
+
|---------|---------|---------|--------------|
|
|
34
|
+
| torch | >=2.8.0 | BSD | [License](https://github.com/pytorch/pytorch/blob/master/LICENSE) |
|
|
35
|
+
| plotly | >=6.3.0 | MIT | [License](https://github.com/plotly/plotly.py/blob/master/LICENSE) |
|
|
36
|
+
| pytest | >=8.0 | MIT | [License](https://github.com/pytest-dev/pytest/blob/main/LICENSE) |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
### Notes
|
|
41
|
+
|
|
42
|
+
1. All packages listed above are permissively licensed (MIT, BSD, Apache 2.0, or Public Domain), so they are compatible with MIT licensing for this project.
|
|
43
|
+
2. If distributing this library, include this `DEPENDENCIES.md` file and your own MIT license file to give proper attribution.
|
|
44
|
+
3. Optional dependencies should be listed in documentation or `pyproject.toml` extras.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multimodalrouter
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: A graph-based routing library for dynamic routing.
|
|
5
5
|
Author-email: Tobias Karusseit <karusseittobi@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -19,6 +19,7 @@ Project-URL: Repository, https://github.com/K-T0BIAS/MultiModalRouter
|
|
|
19
19
|
Requires-Python: >=3.11
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
License-File: LICENSE.md
|
|
22
|
+
License-File: NOTICE.md
|
|
22
23
|
Requires-Dist: colorama>=0.4.6
|
|
23
24
|
Requires-Dist: dill>=0.4.0
|
|
24
25
|
Requires-Dist: filelock>=3.19.1
|
|
@@ -45,14 +46,15 @@ Provides-Extra: torch
|
|
|
45
46
|
Requires-Dist: torch>=2.8.0; extra == "torch"
|
|
46
47
|
Provides-Extra: dev
|
|
47
48
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
49
|
+
Requires-Dist: plotly>=6.3.0; extra == "dev"
|
|
50
|
+
Provides-Extra: plotly
|
|
51
|
+
Requires-Dist: plotly>=6.3.0; extra == "plotly"
|
|
48
52
|
Dynamic: license-file
|
|
49
53
|
|
|
50
54
|
# Multi Modal Router
|
|
51
55
|
|
|
52
56
|
The Multi Modal Router is a graph-based routing engine that allows you to build and query any hub-based network. It supports multiple transport modes like driving, flying, or shipping, and lets you optimize routes by distance, time, or custom metrics. It can be expanded to any n-dimensional space making it versatile in any coordinate space
|
|
53
57
|
|
|
54
|
-
> NEWS: v0.1.3 now on pypi ([installation guide](./docs/installation.md))
|
|
55
|
-
|
|
56
58
|
> NOTE: This project is a work in progress and features might be added and or changed
|
|
57
59
|
|
|
58
60
|
# In depth Documentation
|
|
@@ -97,6 +99,12 @@ The graph can be build from any data aslong as the required fields are present (
|
|
|
97
99
|
|
|
98
100
|

|
|
99
101
|
|
|
102
|
+
## graph visualizations
|
|
103
|
+
|
|
104
|
+
Use the build-in [visualization](./docs/visualization.md) tool to plot any `2D` or `3D` Graph.
|
|
105
|
+
|
|
106
|
+

|
|
107
|
+
|
|
100
108
|
## Important considerations for your usecase
|
|
101
109
|
|
|
102
110
|
Depending on your usecase and datasets some features may not be usable see solutions below
|
|
@@ -118,4 +126,6 @@ Depending on your usecase and datasets some features may not be usable see solut
|
|
|
118
126
|
|
|
119
127
|
[see here](./LICENSE.md)
|
|
120
128
|
|
|
129
|
+
[dependencies](./NOTICE.md)
|
|
130
|
+
|
|
121
131
|
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
The Multi Modal Router is a graph-based routing engine that allows you to build and query any hub-based network. It supports multiple transport modes like driving, flying, or shipping, and lets you optimize routes by distance, time, or custom metrics. It can be expanded to any n-dimensional space making it versatile in any coordinate space
|
|
4
4
|
|
|
5
|
-
> NEWS: v0.1.3 now on pypi ([installation guide](./docs/installation.md))
|
|
6
|
-
|
|
7
5
|
> NOTE: This project is a work in progress and features might be added and or changed
|
|
8
6
|
|
|
9
7
|
# In depth Documentation
|
|
@@ -48,6 +46,12 @@ The graph can be build from any data aslong as the required fields are present (
|
|
|
48
46
|
|
|
49
47
|

|
|
50
48
|
|
|
49
|
+
## graph visualizations
|
|
50
|
+
|
|
51
|
+
Use the build-in [visualization](./docs/visualization.md) tool to plot any `2D` or `3D` Graph.
|
|
52
|
+
|
|
53
|
+

|
|
54
|
+
|
|
51
55
|
## Important considerations for your usecase
|
|
52
56
|
|
|
53
57
|
Depending on your usecase and datasets some features may not be usable see solutions below
|
|
@@ -69,4 +73,6 @@ Depending on your usecase and datasets some features may not be usable see solut
|
|
|
69
73
|
|
|
70
74
|
[see here](./LICENSE.md)
|
|
71
75
|
|
|
76
|
+
[dependencies](./NOTICE.md)
|
|
77
|
+
|
|
72
78
|
|
|
Binary file
|
|
Binary file
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
from multimodalrouter import RouteGraph
|
|
6
6
|
import os
|
|
7
7
|
|
|
8
|
+
|
|
8
9
|
def main():
|
|
9
10
|
path = os.path.dirname(os.path.abspath(__file__))
|
|
10
11
|
# initialize the graph
|
|
@@ -17,21 +18,21 @@ def main():
|
|
|
17
18
|
# build the graph
|
|
18
19
|
graph.build()
|
|
19
20
|
# set start and end points
|
|
20
|
-
start = [60.866699
|
|
21
|
-
end = [60.872747
|
|
21
|
+
start = [60.866699, -162.272996] # Atmautluak Airport
|
|
22
|
+
end = [60.872747, -162.5247] # Kasigluk Airport
|
|
22
23
|
|
|
23
24
|
start_hub = graph.findClosestHub(["airport"], start) # find the hubs
|
|
24
25
|
end_hub = graph.findClosestHub(["airport"], end)
|
|
25
26
|
# find the route
|
|
26
27
|
route = graph.find_shortest_path(
|
|
27
|
-
start_hub.id,
|
|
28
|
+
start_hub.id,
|
|
28
29
|
end_hub.id,
|
|
29
|
-
allowed_modes=["plane","car"],
|
|
30
|
+
allowed_modes=["plane", "car"],
|
|
30
31
|
verbose=True
|
|
31
|
-
|
|
32
|
+
)
|
|
32
33
|
# print the route
|
|
33
34
|
print(route.flatPath if route else "No route found")
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
if __name__ == "__main__":
|
|
37
|
-
main()
|
|
38
|
+
main()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# dataclasses.py
|
|
2
|
+
# Copyright (c) 2025 Tobias Karusseit
|
|
3
|
+
# Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from multimodalrouter import RouteGraph
|
|
7
|
+
from multimodalrouter.graphics import GraphDisplay
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
if __name__ == "__main__":
|
|
11
|
+
path = os.path.dirname(os.path.abspath(__file__))
|
|
12
|
+
graph = RouteGraph(
|
|
13
|
+
maxDistance=50,
|
|
14
|
+
transportModes={"airport": "fly", },
|
|
15
|
+
dataPaths={"airport": os.path.join(path, "data", "fullDataset.csv")},
|
|
16
|
+
compressed=False,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
graph.build()
|
|
20
|
+
display = GraphDisplay(graph)
|
|
21
|
+
display.display(
|
|
22
|
+
displayEarth=True,
|
|
23
|
+
nodeTransform=GraphDisplay.degreesToCartesian3D,
|
|
24
|
+
edgeTransform=GraphDisplay.curvedEdges
|
|
25
|
+
)
|
|
Binary file
|
|
Binary file
|
{multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/docs/examples/mazePathfinder/data/createMaze.py
RENAMED
|
@@ -5,15 +5,17 @@
|
|
|
5
5
|
import random
|
|
6
6
|
import pandas as pd
|
|
7
7
|
|
|
8
|
+
|
|
8
9
|
# simple cell class for the maze
|
|
9
10
|
class Cell:
|
|
10
11
|
def __init__(self, x, y):
|
|
11
|
-
self.id = f"cell-{x,y}"
|
|
12
|
+
self.id = f"cell-{x, y}"
|
|
12
13
|
self.x = x
|
|
13
14
|
self.y = y
|
|
14
15
|
self.visited = False
|
|
15
16
|
self.connected = []
|
|
16
17
|
|
|
18
|
+
|
|
17
19
|
def main():
|
|
18
20
|
# init a 10x10 maze
|
|
19
21
|
mazeHeight = 10
|
|
@@ -53,7 +55,15 @@ def main():
|
|
|
53
55
|
cellStack.pop()
|
|
54
56
|
|
|
55
57
|
# init the dataframe
|
|
56
|
-
data = pd.DataFrame(columns=[
|
|
58
|
+
data = pd.DataFrame(columns=[
|
|
59
|
+
"source",
|
|
60
|
+
"destination",
|
|
61
|
+
"distance",
|
|
62
|
+
"source_lat",
|
|
63
|
+
"source_lng",
|
|
64
|
+
"destination_lat",
|
|
65
|
+
"destination_lng"
|
|
66
|
+
])
|
|
57
67
|
# add the edges to the dataframe
|
|
58
68
|
for cell in cells:
|
|
59
69
|
for neighbor in cell.connected:
|
|
@@ -61,4 +71,6 @@ def main():
|
|
|
61
71
|
# save the dataframe
|
|
62
72
|
data.to_csv("docs/examples/mazePathfinder/data/maze.csv", index=False)
|
|
63
73
|
|
|
64
|
-
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
main()
|
|
@@ -6,22 +6,26 @@ from multimodalrouter import RouteGraph
|
|
|
6
6
|
import os
|
|
7
7
|
import pandas as pd
|
|
8
8
|
|
|
9
|
+
|
|
9
10
|
def main():
|
|
10
11
|
try:
|
|
11
12
|
import matplotlib.pyplot as plt
|
|
12
13
|
except ImportError:
|
|
13
14
|
raise ImportError("matplotlib is not installed. Please install matplotlib to use this example.")
|
|
14
|
-
|
|
15
|
+
|
|
15
16
|
path = os.path.dirname(os.path.abspath(__file__))
|
|
16
17
|
# init the maze df for the plot
|
|
17
18
|
mazeDf = pd.read_csv(os.path.join(path, "data", "maze.csv"))
|
|
18
19
|
# init the plot
|
|
19
|
-
plt.figure(figsize=(10,10))
|
|
20
|
+
plt.figure(figsize=(10, 10))
|
|
20
21
|
# draw the maze
|
|
22
|
+
# draw the maze (grid lines)
|
|
21
23
|
for _, row in mazeDf.iterrows():
|
|
22
|
-
plt.plot(
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
plt.plot(
|
|
25
|
+
[row.source_lng, row.destination_lng], # x = "lng" column
|
|
26
|
+
[row.source_lat, row.destination_lat], # y = "lat" column
|
|
27
|
+
"k-"
|
|
28
|
+
)
|
|
25
29
|
|
|
26
30
|
# initialize the graph
|
|
27
31
|
graph = RouteGraph(
|
|
@@ -35,7 +39,7 @@ def main():
|
|
|
35
39
|
graph.build()
|
|
36
40
|
# find the shortest route
|
|
37
41
|
route = graph.find_shortest_path(
|
|
38
|
-
start_id="cell-(0, 0)",
|
|
42
|
+
start_id="cell-(0, 0)",
|
|
39
43
|
end_id="cell-(0, 9)",
|
|
40
44
|
allowed_modes=["walk"],
|
|
41
45
|
verbose=True,
|
|
@@ -49,11 +53,17 @@ def main():
|
|
|
49
53
|
if s_prev is not None:
|
|
50
54
|
h1 = graph.getHubById(s_prev)
|
|
51
55
|
h2 = graph.getHubById(s)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
# Swap coords so x=column, y=row
|
|
57
|
+
plt.plot(
|
|
58
|
+
[h1.coords[1], h2.coords[1]], # x-axis
|
|
59
|
+
[h1.coords[0], h2.coords[0]], # y-axis
|
|
60
|
+
"b-"
|
|
61
|
+
)
|
|
55
62
|
s_prev = s
|
|
63
|
+
|
|
56
64
|
# display the plot
|
|
57
65
|
plt.show()
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
main()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# dataclasses.py
|
|
2
|
+
# Copyright (c) 2025 Tobias Karusseit
|
|
3
|
+
# Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from multimodalrouter import RouteGraph
|
|
7
|
+
from multimodalrouter.graphics import GraphDisplay
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# custom transform to make lat lng to x y (-> lng lat)
|
|
12
|
+
def NodeTransform(coords):
|
|
13
|
+
for coord in coords:
|
|
14
|
+
yield list((coord[0], coord[1]))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if __name__ == "__main__":
|
|
18
|
+
path = os.path.dirname(os.path.abspath(__file__))
|
|
19
|
+
# initialize the graph
|
|
20
|
+
graph = RouteGraph(
|
|
21
|
+
maxDistance=50,
|
|
22
|
+
transportModes={"cell": "walk", },
|
|
23
|
+
dataPaths={"cell": os.path.join(path, "data", "maze.csv")},
|
|
24
|
+
compressed=False,
|
|
25
|
+
drivingEnabled=False
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
graph.build()
|
|
29
|
+
# init the display
|
|
30
|
+
display = GraphDisplay(graph)
|
|
31
|
+
# display the graph (uses the transform to swap lat lng to x y)
|
|
32
|
+
display.display(nodeTransform=NodeTransform)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
[HOME](../README.md)
|
|
2
|
+
|
|
3
|
+
# Graph Plotting
|
|
4
|
+
|
|
5
|
+
Using the build-in graph plotting tool you can [plotly](https://plotly.com/python/) plot any graph in `2D` or `3D`, while defining [transformations](#transformations) for your coordiante space or even path curvature etc.
|
|
6
|
+
|
|
7
|
+
## GraphDisplay
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
graph: RouteGraph,
|
|
13
|
+
name: str = "Graph",
|
|
14
|
+
iconSize: int = 10
|
|
15
|
+
) -> None:
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
#### args:
|
|
19
|
+
- graph: RouteDisplay = the graph instance you want to plot
|
|
20
|
+
- name: str = (not in use at the moment)
|
|
21
|
+
- iconSize: int = the size of the nodes in the plot
|
|
22
|
+
|
|
23
|
+
#### example
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
gd = GraphDisplay(myGraphInstance)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
[flight path CODE example on sphere](./examples/flightRouter/plot.py)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
### display()
|
|
33
|
+
|
|
34
|
+
The display function will collect data from your Graph and create a [plotly](https://plotly.com/python/) plot from it.
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
def display(
|
|
38
|
+
self,
|
|
39
|
+
nodeTransform=None,
|
|
40
|
+
edgeTransform=None,
|
|
41
|
+
displayEarth=False
|
|
42
|
+
):
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
#### args:
|
|
46
|
+
|
|
47
|
+
- nodeTransform: function = a [transformation](#transformations) function that transformes all node coordinates
|
|
48
|
+
- edgeTransform: funstion = a function that [transformes](#transformations) all your edges
|
|
49
|
+
- displayEarth: bool = if True -> will display a sphere that (roughly) matches earth
|
|
50
|
+
|
|
51
|
+
#### example:
|
|
52
|
+
|
|
53
|
+
this call will create the plot for your graph while mapping all coords onto the surface of the earth
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
gd.display(
|
|
57
|
+
nodeTransform = gd.degreesToCartesian3D,
|
|
58
|
+
displayEarth: True
|
|
59
|
+
)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### transformations
|
|
63
|
+
|
|
64
|
+
#### base function style
|
|
65
|
+
|
|
66
|
+
IF you want to implement your own transformation function note that the call must adhere to the following parameters:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
def customNodeTrandsform(coords: list[list[float]]):
|
|
70
|
+
return list[list[float]]
|
|
71
|
+
|
|
72
|
+
def customEdgeTransform(start: list[list[float]], end: list[list[float]]):
|
|
73
|
+
return list[list[list[float]]]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### args
|
|
77
|
+
|
|
78
|
+
- coords: list[list[float]] = a nested list of coordinates for all nodes
|
|
79
|
+
- start: list[list[float]] = a nested list of all start coordinates
|
|
80
|
+
- end: list[list[float]] = a nested list of all end coordinates
|
|
81
|
+
|
|
82
|
+
#### returns:
|
|
83
|
+
|
|
84
|
+
- list[list[float]] = a list of all transformed node coordinates
|
|
85
|
+
- list[list[list[float]]] = a list of curves whare each curve / edge can have n points defining it
|
|
86
|
+
|
|
87
|
+
### build-in Node Transforms:
|
|
88
|
+
|
|
89
|
+
#### degreesToCartesian3D
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
@staticmethod
|
|
93
|
+
def degreesToCartesian3D(coords):
|
|
94
|
+
```
|
|
95
|
+
This function maps any valid `2D` coordinates (best if in degrees) to spherical coords on the surface of earth
|
|
96
|
+
|
|
97
|
+
### build-in Edge Transformations
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
@staticmethod
|
|
101
|
+
def curvedEdges(start, end, R=6371.0, H=0.05, n=20):
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
curves edges for coordinates on spheres (here earth) so that the edges curve along the spherical surface with a curvature that places the midpoint of the curve at $H \dot R$ above the surface. (great for displaying flights).
|
|
105
|
+
|
|
106
|
+
If torch is installed this will use great-circle distance for the curves
|
|
107
|
+
|
|
108
|
+
> Note if torch is not installed this will fall back to using `math` with quadratic bezier curves -> some curves may end up inside the sphere to bezier inaccuracy
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "multimodalrouter"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.5"
|
|
8
8
|
description = "A graph-based routing library for dynamic routing."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE.md" }
|
|
@@ -46,12 +46,14 @@ Repository = "https://github.com/K-T0BIAS/MultiModalRouter"
|
|
|
46
46
|
[project.optional-dependencies]
|
|
47
47
|
torch = ["torch>=2.8.0"]
|
|
48
48
|
dev = [
|
|
49
|
-
"pytest>=8.0"
|
|
49
|
+
"pytest>=8.0",
|
|
50
|
+
"plotly>=6.3.0"
|
|
50
51
|
]
|
|
52
|
+
plotly = ["plotly>=6.3.0"]
|
|
51
53
|
|
|
52
54
|
[tool.setuptools]
|
|
53
55
|
package-dir = {"" = "src"}
|
|
54
|
-
packages = ["multimodalrouter", "multimodalrouter.graph", "multimodalrouter.router", "multimodalrouter.utils"]
|
|
56
|
+
packages = ["multimodalrouter", "multimodalrouter.graph", "multimodalrouter.router", "multimodalrouter.utils", "multimodalrouter.graphics"]
|
|
55
57
|
|
|
56
58
|
[project.scripts]
|
|
57
59
|
multiModalRouter-build = "multimodalrouter.router.build:main"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multimodalrouter
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: A graph-based routing library for dynamic routing.
|
|
5
5
|
Author-email: Tobias Karusseit <karusseittobi@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -19,6 +19,7 @@ Project-URL: Repository, https://github.com/K-T0BIAS/MultiModalRouter
|
|
|
19
19
|
Requires-Python: >=3.11
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
License-File: LICENSE.md
|
|
22
|
+
License-File: NOTICE.md
|
|
22
23
|
Requires-Dist: colorama>=0.4.6
|
|
23
24
|
Requires-Dist: dill>=0.4.0
|
|
24
25
|
Requires-Dist: filelock>=3.19.1
|
|
@@ -45,14 +46,15 @@ Provides-Extra: torch
|
|
|
45
46
|
Requires-Dist: torch>=2.8.0; extra == "torch"
|
|
46
47
|
Provides-Extra: dev
|
|
47
48
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
49
|
+
Requires-Dist: plotly>=6.3.0; extra == "dev"
|
|
50
|
+
Provides-Extra: plotly
|
|
51
|
+
Requires-Dist: plotly>=6.3.0; extra == "plotly"
|
|
48
52
|
Dynamic: license-file
|
|
49
53
|
|
|
50
54
|
# Multi Modal Router
|
|
51
55
|
|
|
52
56
|
The Multi Modal Router is a graph-based routing engine that allows you to build and query any hub-based network. It supports multiple transport modes like driving, flying, or shipping, and lets you optimize routes by distance, time, or custom metrics. It can be expanded to any n-dimensional space making it versatile in any coordinate space
|
|
53
57
|
|
|
54
|
-
> NEWS: v0.1.3 now on pypi ([installation guide](./docs/installation.md))
|
|
55
|
-
|
|
56
58
|
> NOTE: This project is a work in progress and features might be added and or changed
|
|
57
59
|
|
|
58
60
|
# In depth Documentation
|
|
@@ -97,6 +99,12 @@ The graph can be build from any data aslong as the required fields are present (
|
|
|
97
99
|
|
|
98
100
|

|
|
99
101
|
|
|
102
|
+
## graph visualizations
|
|
103
|
+
|
|
104
|
+
Use the build-in [visualization](./docs/visualization.md) tool to plot any `2D` or `3D` Graph.
|
|
105
|
+
|
|
106
|
+

|
|
107
|
+
|
|
100
108
|
## Important considerations for your usecase
|
|
101
109
|
|
|
102
110
|
Depending on your usecase and datasets some features may not be usable see solutions below
|
|
@@ -118,4 +126,6 @@ Depending on your usecase and datasets some features may not be usable see solut
|
|
|
118
126
|
|
|
119
127
|
[see here](./LICENSE.md)
|
|
120
128
|
|
|
129
|
+
[dependencies](./NOTICE.md)
|
|
130
|
+
|
|
121
131
|
|
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
LICENSE.md
|
|
2
2
|
MANIFEST.in
|
|
3
|
+
NOTICE.md
|
|
3
4
|
README.md
|
|
4
5
|
pyproject.toml
|
|
6
|
+
docs/FlightPathPlot.png
|
|
5
7
|
docs/cli.md
|
|
6
8
|
docs/graph.md
|
|
7
9
|
docs/installation.md
|
|
8
10
|
docs/solvedMaze1.png
|
|
9
11
|
docs/utils.md
|
|
12
|
+
docs/visualization.md
|
|
10
13
|
docs/examples/demoData.csv
|
|
11
14
|
docs/examples/flightRouter/main.py
|
|
15
|
+
docs/examples/flightRouter/plot.py
|
|
16
|
+
docs/examples/flightRouter/__pycache__/plot.cpython-313.pyc
|
|
12
17
|
docs/examples/flightRouter/data/fullDataset.csv
|
|
13
18
|
docs/examples/mazePathfinder/main.py
|
|
19
|
+
docs/examples/mazePathfinder/plot.py
|
|
20
|
+
docs/examples/mazePathfinder/__pycache__/main.cpython-313.pyc
|
|
21
|
+
docs/examples/mazePathfinder/__pycache__/plot.cpython-313.pyc
|
|
14
22
|
docs/examples/mazePathfinder/data/createMaze.py
|
|
15
23
|
docs/examples/mazePathfinder/data/maze.csv
|
|
16
24
|
src/multiModalRouter.egg-info/PKG-INFO
|
|
@@ -29,6 +37,8 @@ src/multimodalrouter.egg-info/top_level.txt
|
|
|
29
37
|
src/multimodalrouter/graph/__init__.py
|
|
30
38
|
src/multimodalrouter/graph/dataclasses.py
|
|
31
39
|
src/multimodalrouter/graph/graph.py
|
|
40
|
+
src/multimodalrouter/graphics/__init__.py
|
|
41
|
+
src/multimodalrouter/graphics/graphicsWrapper.py
|
|
32
42
|
src/multimodalrouter/router/__init__.py
|
|
33
43
|
src/multimodalrouter/router/build.py
|
|
34
44
|
src/multimodalrouter/router/route.py
|
|
@@ -50,6 +50,7 @@ class Hub:
|
|
|
50
50
|
self.coords: list[float] = coords
|
|
51
51
|
self.id = id
|
|
52
52
|
self.hubType = hubType
|
|
53
|
+
# dict like {mode -> {dest_id -> EdgeMetadata}}
|
|
53
54
|
self.outgoing: dict[str, dict[str, EdgeMetadata]] = {}
|
|
54
55
|
|
|
55
56
|
def addOutgoing(self, mode: str, dest_id: str, metrics: EdgeMetadata):
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .graphicsWrapper import GraphDisplay # noqa: F401
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# dataclasses.py
|
|
2
|
+
# Copyright (c) 2025 Tobias Karusseit
|
|
3
|
+
# Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from ..graph import RouteGraph
|
|
7
|
+
import plotly.graph_objects as go
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GraphDisplay():
|
|
11
|
+
|
|
12
|
+
def __init__(self, graph: RouteGraph, name: str = "Graph", iconSize: int = 10) -> None:
|
|
13
|
+
self.graph: RouteGraph = graph
|
|
14
|
+
self.name: str = name
|
|
15
|
+
self.iconSize: int = iconSize
|
|
16
|
+
|
|
17
|
+
def _toPlotlyFormat(
|
|
18
|
+
self,
|
|
19
|
+
nodeTransform=None,
|
|
20
|
+
edgeTransform=None
|
|
21
|
+
):
|
|
22
|
+
"""
|
|
23
|
+
transform the graph data into plotly format.to use the display function
|
|
24
|
+
|
|
25
|
+
args:
|
|
26
|
+
- nodeTransform: function to transform the node coordinates (default = None)
|
|
27
|
+
- edgeTransform: function to transform the edge coordinates (default = None)
|
|
28
|
+
returns:
|
|
29
|
+
- None (modifies self.nodes and self.edges)
|
|
30
|
+
"""
|
|
31
|
+
self.nodes = {
|
|
32
|
+
f"{hub.hubType}-{hub.id}": {
|
|
33
|
+
"coords": hub.coords,
|
|
34
|
+
"hubType": hub.hubType,
|
|
35
|
+
"id": hub.id
|
|
36
|
+
}
|
|
37
|
+
for hub in self.graph._allHubs()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
self.edges = [
|
|
41
|
+
{
|
|
42
|
+
"from": f"{hub.hubType}-{hub.id}",
|
|
43
|
+
"to": f"{self.graph.getHubById(dest).hubType}-{dest}",
|
|
44
|
+
**edge.allMetrics
|
|
45
|
+
}
|
|
46
|
+
for hub in self.graph._allHubs()
|
|
47
|
+
for _, edge in hub.outgoing.items()
|
|
48
|
+
for dest, edge in edge.items()
|
|
49
|
+
]
|
|
50
|
+
self.dim = max(len(node.get("coords")) for node in self.nodes.values())
|
|
51
|
+
|
|
52
|
+
if nodeTransform is not None:
|
|
53
|
+
expandedCoords = [node.get("coords") + [0] * (self.dim - len(node.get("coords"))) for node in self.nodes.values()]
|
|
54
|
+
transformedCoords = nodeTransform(expandedCoords)
|
|
55
|
+
for node, coords in zip(self.nodes.values(), transformedCoords):
|
|
56
|
+
node["coords"] = coords
|
|
57
|
+
|
|
58
|
+
self.dim = max(len(node.get("coords")) for node in self.nodes.values())
|
|
59
|
+
|
|
60
|
+
if edgeTransform is not None:
|
|
61
|
+
starts = [edge["from"] for edge in self.edges]
|
|
62
|
+
startCoords = [self.nodes[start]["coords"] for start in starts]
|
|
63
|
+
ends = [edge["to"] for edge in self.edges]
|
|
64
|
+
endCoords = [self.nodes[end]["coords"] for end in ends]
|
|
65
|
+
|
|
66
|
+
transformedEdges = edgeTransform(startCoords, endCoords)
|
|
67
|
+
for edge, transformedEdge in zip(self.edges, transformedEdges):
|
|
68
|
+
edge["curve"] = transformedEdge
|
|
69
|
+
|
|
70
|
+
def display(
|
|
71
|
+
self,
|
|
72
|
+
nodeTransform=None,
|
|
73
|
+
edgeTransform=None,
|
|
74
|
+
displayEarth=False
|
|
75
|
+
):
|
|
76
|
+
"""
|
|
77
|
+
function to display any 2D or 3D RouteGraph
|
|
78
|
+
|
|
79
|
+
args:
|
|
80
|
+
- nodeTransform: function to transform the node coordinates (default = None)
|
|
81
|
+
- edgeTransform: function to transform the edge coordinates (default = None)
|
|
82
|
+
- displayEarth: whether to display the earth as a background (default = False, only in 3D)
|
|
83
|
+
|
|
84
|
+
returns:
|
|
85
|
+
- None (modifies self.nodes and self.edges opens the plot in a browser)
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
# transform the graph
|
|
89
|
+
self._toPlotlyFormat(nodeTransform, edgeTransform)
|
|
90
|
+
# init plotly placeholders
|
|
91
|
+
node_x, node_y, node_z, text, colors = [], [], [], [], []
|
|
92
|
+
edge_x, edge_y, edge_z, edge_text = [], [], [], []
|
|
93
|
+
|
|
94
|
+
# add all the nodes
|
|
95
|
+
for node_key, node_data in self.nodes.items():
|
|
96
|
+
x, y, *rest = node_data["coords"]
|
|
97
|
+
node_x.append(x)
|
|
98
|
+
node_y.append(y)
|
|
99
|
+
if self.dim == 3:
|
|
100
|
+
node_z.append(node_data["coords"][2])
|
|
101
|
+
text.append(f"{node_data['id']}<br>Type: {node_data['hubType']}")
|
|
102
|
+
colors.append(hash(node_data['hubType']) % 10)
|
|
103
|
+
|
|
104
|
+
# add all the edges
|
|
105
|
+
for edge in self.edges:
|
|
106
|
+
# check if edge has been transformed
|
|
107
|
+
if "curve" in edge:
|
|
108
|
+
curve = edge["curve"]
|
|
109
|
+
# add all the points of the edge
|
|
110
|
+
for point in curve:
|
|
111
|
+
edge_x.append(point[0])
|
|
112
|
+
edge_y.append(point[1])
|
|
113
|
+
if self.dim == 3:
|
|
114
|
+
edge_z.append(point[2])
|
|
115
|
+
edge_x.append(None)
|
|
116
|
+
edge_y.append(None)
|
|
117
|
+
# if 3d add the extra none to close the edge
|
|
118
|
+
if self.dim == 3:
|
|
119
|
+
edge_z.append(None)
|
|
120
|
+
else:
|
|
121
|
+
source = self.nodes[edge["from"]]["coords"]
|
|
122
|
+
target = self.nodes[edge["to"]]["coords"]
|
|
123
|
+
|
|
124
|
+
edge_x += [source[0], target[0], None]
|
|
125
|
+
edge_y += [source[1], target[1], None]
|
|
126
|
+
|
|
127
|
+
if self.dim == 3:
|
|
128
|
+
edge_z += [source[2], target[2], None]
|
|
129
|
+
|
|
130
|
+
# add text and hover display
|
|
131
|
+
hover = f"{edge['from']} → {edge['to']}"
|
|
132
|
+
metrics = {k: v for k, v in edge.items() if k not in ("from", "to", "curve")}
|
|
133
|
+
if metrics:
|
|
134
|
+
hover += "<br>" + "<br>".join(f"{k}: {v}" for k, v in metrics.items())
|
|
135
|
+
edge_text.append(hover)
|
|
136
|
+
|
|
137
|
+
if self.dim == 2:
|
|
138
|
+
# ceate the plot in 2d
|
|
139
|
+
node_trace = go.Scatter(
|
|
140
|
+
x=node_x,
|
|
141
|
+
y=node_y,
|
|
142
|
+
mode="markers",
|
|
143
|
+
hoverinfo="text",
|
|
144
|
+
text=text,
|
|
145
|
+
marker=dict(
|
|
146
|
+
size=self.iconSize,
|
|
147
|
+
color=colors,
|
|
148
|
+
colorscale="Viridis",
|
|
149
|
+
showscale=True
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
edge_trace = go.Scatter(
|
|
154
|
+
x=edge_x,
|
|
155
|
+
y=edge_y,
|
|
156
|
+
line=dict(width=2, color="#888"),
|
|
157
|
+
hoverinfo="text",
|
|
158
|
+
text=edge_text,
|
|
159
|
+
mode="lines"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
elif self.dim == 3:
|
|
163
|
+
# create the plot in 3d
|
|
164
|
+
node_trace = go.Scatter3d(
|
|
165
|
+
x=node_x,
|
|
166
|
+
y=node_y,
|
|
167
|
+
z=node_z,
|
|
168
|
+
mode="markers",
|
|
169
|
+
hoverinfo="text",
|
|
170
|
+
text=text,
|
|
171
|
+
marker=dict(
|
|
172
|
+
size=self.iconSize,
|
|
173
|
+
color=colors,
|
|
174
|
+
colorscale="Viridis",
|
|
175
|
+
showscale=True
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
edge_trace = go.Scatter3d(
|
|
180
|
+
x=edge_x,
|
|
181
|
+
y=edge_y,
|
|
182
|
+
z=edge_z,
|
|
183
|
+
line=dict(width=1, color="#888"),
|
|
184
|
+
hoverinfo="text",
|
|
185
|
+
text=edge_text,
|
|
186
|
+
mode="lines",
|
|
187
|
+
opacity=0.6
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# create the plotly figure
|
|
191
|
+
fig = go.Figure(data=[edge_trace, node_trace])
|
|
192
|
+
# render earth / sphere in 3d
|
|
193
|
+
if self.dim == 3 and displayEarth:
|
|
194
|
+
try:
|
|
195
|
+
import numpy as np
|
|
196
|
+
R = 6369.9 # sphere radius
|
|
197
|
+
u = np.linspace(0, 2 * np.pi, 50) # azimuthal angle
|
|
198
|
+
v = np.linspace(0, np.pi, 50) # polar angle
|
|
199
|
+
u, v = np.meshgrid(u, v)
|
|
200
|
+
|
|
201
|
+
# Cartesian coordinates
|
|
202
|
+
x = R * np.cos(u) * np.sin(v)
|
|
203
|
+
y = R * np.sin(u) * np.sin(v)
|
|
204
|
+
z = R * np.cos(v)
|
|
205
|
+
except ImportError:
|
|
206
|
+
raise ImportError("numpy is required to display the earth")
|
|
207
|
+
|
|
208
|
+
sphere_surface = go.Surface(
|
|
209
|
+
x=x, y=y, z=z,
|
|
210
|
+
colorscale='Blues',
|
|
211
|
+
opacity=1,
|
|
212
|
+
showscale=False,
|
|
213
|
+
hoverinfo='skip'
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
fig.add_trace(sphere_surface)
|
|
217
|
+
|
|
218
|
+
fig.update_layout(title="Interactive Graph", showlegend=False, hovermode="closest")
|
|
219
|
+
fig.show()
|
|
220
|
+
|
|
221
|
+
@staticmethod
|
|
222
|
+
def degreesToCartesian3D(coords):
|
|
223
|
+
try:
|
|
224
|
+
import torch
|
|
225
|
+
C = torch.tensor(coords)
|
|
226
|
+
if C.dim() == 1:
|
|
227
|
+
C = C.unsqueeze(0)
|
|
228
|
+
R = 6371.0
|
|
229
|
+
lat = torch.deg2rad(C[:, 0])
|
|
230
|
+
lng = torch.deg2rad(C[:, 1])
|
|
231
|
+
x = R * torch.cos(lat) * torch.cos(lng)
|
|
232
|
+
y = R * torch.cos(lat) * torch.sin(lng)
|
|
233
|
+
z = R * torch.sin(lat)
|
|
234
|
+
return list(torch.stack((x, y, z), dim=1).numpy())
|
|
235
|
+
except ImportError:
|
|
236
|
+
import math
|
|
237
|
+
R = 6371.0
|
|
238
|
+
output = []
|
|
239
|
+
for lat, lng in coords:
|
|
240
|
+
lat = math.radians(lat)
|
|
241
|
+
lng = math.radians(lng)
|
|
242
|
+
x = R * math.cos(lat) * math.cos(lng)
|
|
243
|
+
y = R * math.cos(lat) * math.sin(lng)
|
|
244
|
+
z = R * math.sin(lat)
|
|
245
|
+
output.append([x, y, z])
|
|
246
|
+
return output
|
|
247
|
+
|
|
248
|
+
@staticmethod
|
|
249
|
+
def curvedEdges(start, end, R=6371.0, H=0.05, n=20):
|
|
250
|
+
try:
|
|
251
|
+
# if torch and np are available calc vectorized graeter circle curves
|
|
252
|
+
import numpy as np
|
|
253
|
+
import torch
|
|
254
|
+
|
|
255
|
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
256
|
+
|
|
257
|
+
start_np = np.array(start, dtype=np.float32)
|
|
258
|
+
end_np = np.array(end, dtype=np.float32)
|
|
259
|
+
|
|
260
|
+
start = torch.tensor(start_np, device=device)
|
|
261
|
+
end = torch.tensor(end_np, device=device)
|
|
262
|
+
start = start.float()
|
|
263
|
+
end = end.float()
|
|
264
|
+
|
|
265
|
+
# normalize to sphere
|
|
266
|
+
start_norm = R * start / start.norm(dim=1, keepdim=True)
|
|
267
|
+
end_norm = R * end / end.norm(dim=1, keepdim=True)
|
|
268
|
+
|
|
269
|
+
# compute angle between vectors
|
|
270
|
+
dot = (start_norm * end_norm).sum(dim=1, keepdim=True) / (R**2)
|
|
271
|
+
dot = torch.clamp(dot, -1.0, 1.0)
|
|
272
|
+
theta = torch.acos(dot).unsqueeze(2) # shape: (num_edges,1,1)
|
|
273
|
+
|
|
274
|
+
# linear interpolation along great circle
|
|
275
|
+
t = torch.linspace(0, 1, n, device=device).view(1, n, 1)
|
|
276
|
+
one_minus_t = 1 - t
|
|
277
|
+
sin_theta = torch.sin(theta)
|
|
278
|
+
sin_theta[sin_theta == 0] = 1e-6
|
|
279
|
+
|
|
280
|
+
factor_start = torch.sin(one_minus_t * theta) / sin_theta
|
|
281
|
+
factor_end = torch.sin(t * theta) / sin_theta
|
|
282
|
+
|
|
283
|
+
curve = factor_start * start_norm.unsqueeze(1) + factor_end * end_norm.unsqueeze(1)
|
|
284
|
+
|
|
285
|
+
# normalize to radius
|
|
286
|
+
curve = R * curve / curve.norm(dim=2, keepdim=True)
|
|
287
|
+
|
|
288
|
+
# apply radial lift at curve center using sin weight
|
|
289
|
+
weight = torch.sin(torch.pi * t) # 0 at endpoints, 1 at center
|
|
290
|
+
curve = curve * (1 + H * weight)
|
|
291
|
+
|
|
292
|
+
return curve
|
|
293
|
+
except ImportError:
|
|
294
|
+
# fallback to calculating quadratic bezier curves with math
|
|
295
|
+
import math
|
|
296
|
+
curves_all = []
|
|
297
|
+
|
|
298
|
+
def multiply_vec(vec, factor):
|
|
299
|
+
return [factor * x for x in vec]
|
|
300
|
+
|
|
301
|
+
def add_vec(*vecs):
|
|
302
|
+
return [sum(items) for items in zip(*vecs)]
|
|
303
|
+
|
|
304
|
+
for startP, endP in zip(start, end):
|
|
305
|
+
mid = [(s + e) / 2 for s, e in zip(startP, endP)]
|
|
306
|
+
norm = math.sqrt(sum(c ** 2 for c in mid))
|
|
307
|
+
mid_proj = [R * c / norm for c in mid]
|
|
308
|
+
mid_arch = [c * (1 + H) for c in mid_proj]
|
|
309
|
+
|
|
310
|
+
curve = []
|
|
311
|
+
for i in range(n):
|
|
312
|
+
t_i = i / (n - 1)
|
|
313
|
+
one_minus_t = 1 - t_i
|
|
314
|
+
point = add_vec(
|
|
315
|
+
multiply_vec(startP, one_minus_t ** 2),
|
|
316
|
+
multiply_vec(mid_arch, 2 * one_minus_t * t_i),
|
|
317
|
+
multiply_vec(endP, t_i ** 2)
|
|
318
|
+
)
|
|
319
|
+
curve.append(point)
|
|
320
|
+
|
|
321
|
+
curves_all.append(curve)
|
|
322
|
+
|
|
323
|
+
return curves_all
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/docs/examples/flightRouter/data/fullDataset.csv
RENAMED
|
File without changes
|
{multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/docs/examples/mazePathfinder/data/maze.csv
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/src/multiModalRouter.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/src/multiModalRouter.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/src/multiModalRouter.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{multimodalrouter-0.1.4 → multimodalrouter-0.1.5}/src/multimodalrouter/utils/preprocessor.py
RENAMED
|
File without changes
|