RampantTrackGeneration 0.0.1.0.0__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.
- rampanttrackgeneration-0.0.1.0.0/PKG-INFO +18 -0
- rampanttrackgeneration-0.0.1.0.0/README.md +1 -0
- rampanttrackgeneration-0.0.1.0.0/TrackGenerationDemo.py +57 -0
- rampanttrackgeneration-0.0.1.0.0/pyproject.toml +29 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/before_preprocessing.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/intersection_0.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/intersection_1.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/intersection_2.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/intersection_3.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/intersection_4.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/post_postprocessing0.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/post_postprocessing1.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/post_postprocessing2.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/post_postprocessing3.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/pre_postprocessing2.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/pre_postprocessing3.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/pre_postprocessing_0.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/pre_postprocessing_1.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/reconnection_of_edges_0.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/reconnection_of_edges_1.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/reconnection_of_edges_2.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/reconnection_of_edges_3.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/track.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/track_2.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/track_2_actual.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/track_3.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/track_4.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/voronoi_diagram.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/voronoi_diagram2.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/voronoi_diagram3.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/screenshots_for_writeup/voronoi_diagram4.png +0 -0
- rampanttrackgeneration-0.0.1.0.0/src/RampantTrackGeneration/Track.py +13 -0
- rampanttrackgeneration-0.0.1.0.0/src/RampantTrackGeneration/TrackGenerator.py +326 -0
- rampanttrackgeneration-0.0.1.0.0/src/RampantTrackGeneration/__init__.py +2 -0
- rampanttrackgeneration-0.0.1.0.0/src/RampantTrackGeneration/edges/Ops.py +87 -0
- rampanttrackgeneration-0.0.1.0.0/src/RampantTrackGeneration/edges/__init__.py +1 -0
- rampanttrackgeneration-0.0.1.0.0/src/RampantTrackGeneration/edges/data/EdgeIntersectionInfo.py +10 -0
- rampanttrackgeneration-0.0.1.0.0/src/RampantTrackGeneration/edges/data/EdgeVertexInfo.py +10 -0
- rampanttrackgeneration-0.0.1.0.0/src/RampantTrackGeneration/edges/data/EdgesMakingAngle.py +8 -0
- rampanttrackgeneration-0.0.1.0.0/src/RampantTrackGeneration/edges/data/__init__.py +5 -0
- rampanttrackgeneration-0.0.1.0.0/src/RampantTrackGeneration/graphs/Ops.py +63 -0
- rampanttrackgeneration-0.0.1.0.0/src/RampantTrackGeneration/graphs/__init__.py +1 -0
- rampanttrackgeneration-0.0.1.0.0/src/RampantTrackGeneration/json/flask/JSON.py +24 -0
- rampanttrackgeneration-0.0.1.0.0/src/RampantTrackGeneration/json/flask/__init__.py +1 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: RampantTrackGeneration
|
|
3
|
+
Version: 0.0.1.0.0
|
|
4
|
+
Summary: Rampant on the Tracks's Track generation logic, leveraging Voronout and optimized for a web service.
|
|
5
|
+
Project-URL: Homepage, https://github.com/jpshankar/RampantTrackGeneration
|
|
6
|
+
Project-URL: Issues, https://github.com/jpshankar/RampantTrackGeneration/issues
|
|
7
|
+
Author-email: Javas Shankar <javasshankar@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Requires-Python: >=3.13
|
|
12
|
+
Requires-Dist: flask
|
|
13
|
+
Requires-Dist: networkx
|
|
14
|
+
Requires-Dist: numpy
|
|
15
|
+
Requires-Dist: voronout
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
RampantTrackGeneration
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
RampantTrackGeneration
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from math import floor
|
|
2
|
+
|
|
3
|
+
from matplotlib import pyplot, patches
|
|
4
|
+
|
|
5
|
+
from RampantTrackGeneration import TrackGenerator
|
|
6
|
+
|
|
7
|
+
baseWidth = 600
|
|
8
|
+
baseHeight = 600
|
|
9
|
+
|
|
10
|
+
numWalkers = 3
|
|
11
|
+
numDestinations = 3
|
|
12
|
+
|
|
13
|
+
numRegions = (numWalkers * numDestinations) * 2
|
|
14
|
+
#numRegions = 10
|
|
15
|
+
|
|
16
|
+
minWinSteps = floor(numRegions / 4)
|
|
17
|
+
#print(f"minWinSteps {minWinSteps}")
|
|
18
|
+
|
|
19
|
+
percentToProcess = 0.50
|
|
20
|
+
|
|
21
|
+
newConnectionAngleMinQuantile = 0.45
|
|
22
|
+
lonelyConnectionMinLengthQuantile = 0.25
|
|
23
|
+
|
|
24
|
+
connectionEdgeVertexPadding = 0.20
|
|
25
|
+
connectionEdgeNodeBuffer = 0.10
|
|
26
|
+
|
|
27
|
+
track = TrackGenerator.generateTrack(
|
|
28
|
+
diagramWidth = baseWidth,
|
|
29
|
+
diagramHeight = baseHeight,
|
|
30
|
+
numWalkersOnTrack = numWalkers,
|
|
31
|
+
numDestinationsOnTrack = numDestinations,
|
|
32
|
+
diagramEdgePercentageToProcess = percentToProcess,
|
|
33
|
+
newConnectionAngleMinQuantile = newConnectionAngleMinQuantile,
|
|
34
|
+
lonelyConnectionMinLengthQuantile = lonelyConnectionMinLengthQuantile,
|
|
35
|
+
connectionLengthVertexPadding = connectionEdgeVertexPadding,
|
|
36
|
+
connectionLengthNodeBuffer = connectionEdgeNodeBuffer
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
pyplot.figure(0)
|
|
40
|
+
(_, ax) = pyplot.subplots()
|
|
41
|
+
|
|
42
|
+
for (edge, nodes) in track.stops.items():
|
|
43
|
+
node0Id = edge.vertex0Id
|
|
44
|
+
node1Id = edge.vertex1Id
|
|
45
|
+
|
|
46
|
+
node0 = track.nodes[node0Id]
|
|
47
|
+
node1 = track.nodes[node1Id]
|
|
48
|
+
|
|
49
|
+
#print(f"drawing figure {edgesNum} {vertex0Id}, {vertex1Id}: {vertex0}, {vertex1}")
|
|
50
|
+
|
|
51
|
+
pyplot.plot([node0.x, node1.x], [node0.y, node1.y])
|
|
52
|
+
|
|
53
|
+
for node in nodes:
|
|
54
|
+
nodeCircle = patches.Circle(xy = (node.x, node.y), radius = 1.5, color = 'black')
|
|
55
|
+
ax.add_patch(nodeCircle)
|
|
56
|
+
|
|
57
|
+
pyplot.show()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling >= 1.26"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "RampantTrackGeneration"
|
|
7
|
+
version = "0.0.1.0.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Javas Shankar", email="javasshankar@gmail.com" },
|
|
10
|
+
]
|
|
11
|
+
description = "Rampant on the Tracks's Track generation logic, leveraging Voronout and optimized for a web service."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.13"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
]
|
|
18
|
+
license = "MIT"
|
|
19
|
+
license-files = ["LICEN[CS]E*"]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"flask",
|
|
22
|
+
"networkx",
|
|
23
|
+
"numpy",
|
|
24
|
+
"Voronout",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/jpshankar/RampantTrackGeneration"
|
|
29
|
+
Issues = "https://github.com/jpshankar/RampantTrackGeneration/issues"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from .edges.data import EdgeVertexInfo
|
|
4
|
+
|
|
5
|
+
from uuid import uuid4
|
|
6
|
+
|
|
7
|
+
from voronout.Point import Point
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class Track:
|
|
11
|
+
nodes: dict[uuid4, Point]
|
|
12
|
+
stops: dict[EdgeVertexInfo, tuple[Point]]
|
|
13
|
+
edges: tuple[EdgeVertexInfo]
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
from .edges.data import EdgesMakingAngle, EdgeVertexInfo
|
|
2
|
+
|
|
3
|
+
from .edges.Ops import Ops as EdgesOps
|
|
4
|
+
from .graphs.Ops import Ops as GraphOps
|
|
5
|
+
|
|
6
|
+
from math import floor
|
|
7
|
+
|
|
8
|
+
from networkx import Graph, all_node_cuts as networkXAllNodeCuts
|
|
9
|
+
|
|
10
|
+
from voronout.Point import Point
|
|
11
|
+
from voronout.VoronoiDiagram import VoronoiDiagram
|
|
12
|
+
|
|
13
|
+
from .Track import Track
|
|
14
|
+
|
|
15
|
+
import numpy
|
|
16
|
+
import random
|
|
17
|
+
|
|
18
|
+
from uuid import uuid4
|
|
19
|
+
|
|
20
|
+
class TrackGenerator:
|
|
21
|
+
@staticmethod
|
|
22
|
+
def _edgeAngleViability(angle: float, minAcceptableAngle: float) -> bool:
|
|
23
|
+
# neither angle nor its complement should be < minAcceptableAngle
|
|
24
|
+
return angle >= minAcceptableAngle and (180 - angle) >= minAcceptableAngle
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def _calculateEdgeAnglesWithGraphVertexEdges(graph: Graph, edge: EdgeVertexInfo, vertexId: uuid4) -> dict[EdgesMakingAngle, float]:
|
|
28
|
+
vertexNeighborIds = graph.neighbors(n = vertexId)
|
|
29
|
+
vertexEdgesMakingAngles = []
|
|
30
|
+
for vertexNeighborId in vertexNeighborIds:
|
|
31
|
+
otherEdge = EdgeVertexInfo(vertex0Id = vertexId, vertex1Id = vertexNeighborId)
|
|
32
|
+
edgesMakingAngle = EdgesMakingAngle(edge0 = edge, edge1 = otherEdge)
|
|
33
|
+
vertexEdgesMakingAngles.append(edgesMakingAngle)
|
|
34
|
+
|
|
35
|
+
return EdgesOps.calculateEdgeAnglesWithExtantVertexEdges(edgeGraph = graph, edgesMakingAngles = tuple(vertexEdgesMakingAngles))
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def _findPossibleViableConnection(graph: Graph, possibleConnections: tuple[EdgeVertexInfo], minAcceptableAngle: float) -> EdgeVertexInfo:
|
|
39
|
+
possibleViableConnections = []
|
|
40
|
+
for possibleConnection in possibleConnections:
|
|
41
|
+
zeroConnections = TrackGenerator._calculateEdgeAnglesWithGraphVertexEdges(graph = graph, edge = possibleConnection, vertexId = possibleConnection.vertex0Id)
|
|
42
|
+
oneConnections = TrackGenerator._calculateEdgeAnglesWithGraphVertexEdges(graph = graph, edge = possibleConnection, vertexId = possibleConnection.vertex1Id)
|
|
43
|
+
|
|
44
|
+
allZeroConnectionsValid = all((TrackGenerator._edgeAngleViability(angle = angle, minAcceptableAngle = minAcceptableAngle) for angle in zeroConnections.values()))
|
|
45
|
+
allOneConnectionsValid = all((TrackGenerator._edgeAngleViability(angle = angle, minAcceptableAngle = minAcceptableAngle) for angle in oneConnections.values()))
|
|
46
|
+
|
|
47
|
+
if allZeroConnectionsValid and allOneConnectionsValid:
|
|
48
|
+
possibleViableConnections.append(possibleConnection)
|
|
49
|
+
|
|
50
|
+
return random.choice(possibleViableConnections) if possibleViableConnections else None
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def _updateVertexPotentialConnections(existingConnectionsGraph: Graph, potentialConnectionsGraph: Graph, vertexToUpdateId: uuid4):
|
|
54
|
+
verticesConnectedTo = existingConnectionsGraph.neighbors(n = vertexToUpdateId)
|
|
55
|
+
potentialConnectingVertexIds = tuple((potentialConnectingVertexId for potentialConnectingVertexId in existingConnectionsGraph.nodes if potentialConnectingVertexId not in verticesConnectedTo and potentialConnectingVertexId != vertexToUpdateId))
|
|
56
|
+
|
|
57
|
+
vertexToUpdate = existingConnectionsGraph.nodes[vertexToUpdateId]
|
|
58
|
+
GraphOps.addVertexToGraph(graph = potentialConnectionsGraph, vertexId = vertexToUpdateId, vertexX = vertexToUpdate["x"], vertexY = vertexToUpdate["y"])
|
|
59
|
+
|
|
60
|
+
for potentialConnectingVertexId in potentialConnectingVertexIds:
|
|
61
|
+
potentialConnectionVertex = existingConnectionsGraph.nodes[potentialConnectingVertexId]
|
|
62
|
+
GraphOps.addVertexToGraph(graph = existingConnectionsGraph, vertexId = potentialConnectingVertexId, vertexX = potentialConnectionVertex["x"], vertexY = potentialConnectionVertex["y"])
|
|
63
|
+
|
|
64
|
+
potentialConnection = EdgeVertexInfo(vertex0Id = vertexToUpdateId, vertex1Id = potentialConnectingVertexId)
|
|
65
|
+
|
|
66
|
+
potentialConnectionP1 = Point(x = vertexToUpdate["x"], y = vertexToUpdate["y"])
|
|
67
|
+
potentialConnectionP2 = Point(x = potentialConnectionVertex["x"], y = potentialConnectionVertex["y"])
|
|
68
|
+
|
|
69
|
+
potentialConnectionDistance = GraphOps.scaledGraphPointDistance(graph = potentialConnectionsGraph, p1 = potentialConnectionP1, p2 = potentialConnectionP2)
|
|
70
|
+
|
|
71
|
+
GraphOps.addConnectionToGraph(graph = potentialConnectionsGraph, connection = potentialConnection, vertexDistance = potentialConnectionDistance)
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def _pruneIntersectionEdgesFromExistingConnectionsGraph(existingConnectionsGraph: Graph, intersectionEdges: tuple[EdgeVertexInfo]):
|
|
75
|
+
existingConnectionsNodeCutSets = networkXAllNodeCuts(G = existingConnectionsGraph)
|
|
76
|
+
nodesThatWouldDisconnect = set()
|
|
77
|
+
|
|
78
|
+
for existingConnectionNodeCutSet in existingConnectionsNodeCutSets:
|
|
79
|
+
for existingConnectionNodeCut in existingConnectionNodeCutSet:
|
|
80
|
+
nodesThatWouldDisconnect.add(existingConnectionNodeCut)
|
|
81
|
+
|
|
82
|
+
safeToRemoveEdges = tuple((intersectionEdge for intersectionEdge in intersectionEdges if intersectionEdge.vertex0Id not in nodesThatWouldDisconnect and intersectionEdge.vertex1Id not in nodesThatWouldDisconnect))
|
|
83
|
+
|
|
84
|
+
for safeToRemoveEdge in safeToRemoveEdges:
|
|
85
|
+
safeToRemoveEdgeLength = GraphOps.graphEdgeLength(graph = existingConnectionsGraph, edge = safeToRemoveEdge)
|
|
86
|
+
(disconnectedVertex0, disconnectedVertex1) = GraphOps.removeEdgeAndReturnDisconnected(edgeGraph = existingConnectionsGraph, existingEdge = safeToRemoveEdge)
|
|
87
|
+
if len(disconnectedVertex0) > 0 or len(disconnectedVertex1) > 0:
|
|
88
|
+
# Need to disconnect/reconnect like this because refreshing existingConnectionsNodeCutSets after every removal is too expensive
|
|
89
|
+
existingConnectionsGraph.add_edge(u_of_edge = safeToRemoveEdge.vertex0Id, v_of_edge = safeToRemoveEdge.vertex1Id, edgeLength = safeToRemoveEdgeLength)
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def _handleLonelyExistingConnections(existingConnectionsGraph: Graph, lonelyConnectionMinLengthQuantile: float):
|
|
93
|
+
existingConnectionsEdgeLengthMapping = {}
|
|
94
|
+
for (ev0Id, ev1Id) in existingConnectionsGraph.edges:
|
|
95
|
+
existingConnectionEdgeInfo = EdgeVertexInfo(vertex0Id = ev0Id, vertex1Id = ev1Id)
|
|
96
|
+
existingConnectionsEdgeLengthMapping[existingConnectionEdgeInfo] = GraphOps.graphEdgeLength(graph = existingConnectionsGraph, edge = existingConnectionEdgeInfo)
|
|
97
|
+
|
|
98
|
+
existingConnectionEdgeMinQuantile = numpy.quantile(tuple(existingConnectionsEdgeLengthMapping.values()), lonelyConnectionMinLengthQuantile)
|
|
99
|
+
|
|
100
|
+
for (existingConnectionEdgeInfo, existingConnectionEdgeLength) in existingConnectionsEdgeLengthMapping.items():
|
|
101
|
+
existingEdgeVertex0Id = existingConnectionEdgeInfo.vertex0Id
|
|
102
|
+
existingEdgeVertex1Id = existingConnectionEdgeInfo.vertex1Id
|
|
103
|
+
|
|
104
|
+
zeroNeighbors = tuple(existingConnectionsGraph.neighbors(existingEdgeVertex0Id))
|
|
105
|
+
oneNeighbors = tuple(existingConnectionsGraph.neighbors(existingEdgeVertex1Id))
|
|
106
|
+
|
|
107
|
+
if not len(zeroNeighbors) > 1 or not len(oneNeighbors) > 1:
|
|
108
|
+
if existingConnectionEdgeLength < existingConnectionEdgeMinQuantile:
|
|
109
|
+
existingConnectionsGraph.remove_edge(u = existingEdgeVertex0Id, v = existingEdgeVertex1Id)
|
|
110
|
+
|
|
111
|
+
# https://math.stackexchange.com/questions/134112/find-a-point-on-a-line-segment-located-at-a-distance-d-from-one-endpoint
|
|
112
|
+
@staticmethod
|
|
113
|
+
def _generateStopsOnConnection(connectionGraph: Graph, connection: EdgeVertexInfo, connectionLengthVertexPadding: float, connectionLengthNodeBuffer: float) -> tuple[Point]:
|
|
114
|
+
connectionVertexZeroId = connection.vertex0Id
|
|
115
|
+
connectionVertexZero = connectionGraph.nodes[connectionVertexZeroId]
|
|
116
|
+
|
|
117
|
+
connectionVertexOneId = connection.vertex1Id
|
|
118
|
+
connectionVertexOne = connectionGraph.nodes[connectionVertexOneId]
|
|
119
|
+
|
|
120
|
+
zeroIsFirstVertex = connectionVertexZero["x"] < connectionVertexOne["x"]
|
|
121
|
+
|
|
122
|
+
firstVertex = connectionVertexZero if zeroIsFirstVertex else connectionVertexOne
|
|
123
|
+
secondVertex = connectionVertexZero if not zeroIsFirstVertex else connectionVertexOne
|
|
124
|
+
|
|
125
|
+
connectionLength = GraphOps.graphEdgeLength(graph = connectionGraph, edge = connection)
|
|
126
|
+
|
|
127
|
+
connectionXRangeMinInterval = (connectionLengthVertexPadding + connectionLengthNodeBuffer) * connectionLength
|
|
128
|
+
connectionXRangeMaxInterval = connectionLength - connectionXRangeMinInterval
|
|
129
|
+
|
|
130
|
+
connectionNodes = []
|
|
131
|
+
|
|
132
|
+
while connectionXRangeMinInterval < connectionXRangeMaxInterval:
|
|
133
|
+
d = random.uniform(connectionXRangeMinInterval, connectionXRangeMaxInterval)
|
|
134
|
+
td = d / connectionLength
|
|
135
|
+
|
|
136
|
+
nextX = firstVertex["x"] + ((secondVertex["x"] - firstVertex["x"]) * td)
|
|
137
|
+
nextY = firstVertex["y"] + ((secondVertex["y"] - firstVertex["y"]) * td)
|
|
138
|
+
|
|
139
|
+
nextPoint = Point(x = nextX, y = nextY)
|
|
140
|
+
connectionNodes.append(nextPoint)
|
|
141
|
+
|
|
142
|
+
connectionXRangeMinInterval = d + (connectionLengthNodeBuffer * connectionLength)
|
|
143
|
+
|
|
144
|
+
return tuple(connectionNodes)
|
|
145
|
+
|
|
146
|
+
@staticmethod
|
|
147
|
+
def generateTrack(
|
|
148
|
+
diagramWidth: int,
|
|
149
|
+
diagramHeight: int,
|
|
150
|
+
numWalkersOnTrack: int,
|
|
151
|
+
numDestinationsOnTrack: int,
|
|
152
|
+
diagramEdgePercentageToProcess: float,
|
|
153
|
+
newConnectionAngleMinQuantile: float,
|
|
154
|
+
lonelyConnectionMinLengthQuantile: float,
|
|
155
|
+
connectionLengthVertexPadding: float,
|
|
156
|
+
connectionLengthNodeBuffer: float
|
|
157
|
+
) -> Track:
|
|
158
|
+
numVoronoiDiagramRegions = (numWalkersOnTrack * numDestinationsOnTrack) * 2
|
|
159
|
+
|
|
160
|
+
diagramRegionSites = tuple((Point(x = random.random(), y = random.random()) for _ in range(numVoronoiDiagramRegions)))
|
|
161
|
+
|
|
162
|
+
voronoiDiagram = VoronoiDiagram(basePoints = diagramRegionSites, planeWidth = diagramWidth, planeHeight = diagramHeight)
|
|
163
|
+
|
|
164
|
+
voronoiDiagramEdgesToProcess = []
|
|
165
|
+
for voronoiDiagramEdge in voronoiDiagram.diagramEdges:
|
|
166
|
+
edge = EdgeVertexInfo(vertex0Id = voronoiDiagramEdge.vertex0Id, vertex1Id = voronoiDiagramEdge.vertex1Id)
|
|
167
|
+
if edge not in voronoiDiagramEdgesToProcess:
|
|
168
|
+
voronoiDiagramEdgesToProcess.append(edge)
|
|
169
|
+
|
|
170
|
+
voronoiDiagramEdges = tuple(voronoiDiagramEdgesToProcess)
|
|
171
|
+
|
|
172
|
+
voronoiVertices = voronoiDiagram.vertices
|
|
173
|
+
|
|
174
|
+
existingConnections = Graph(width = diagramWidth, height = diagramHeight)
|
|
175
|
+
potentialConnections = Graph(width = diagramWidth, height = diagramHeight)
|
|
176
|
+
|
|
177
|
+
for vertexId in voronoiVertices.keys():
|
|
178
|
+
edgesWithVertex = set((maybeEdgeWithPoint for maybeEdgeWithPoint in voronoiDiagramEdges if maybeEdgeWithPoint.vertex0Id == vertexId or maybeEdgeWithPoint.vertex1Id == vertexId))
|
|
179
|
+
|
|
180
|
+
if not len(edgesWithVertex) > 0:
|
|
181
|
+
print(f"discarding {vertexId}")
|
|
182
|
+
pass
|
|
183
|
+
|
|
184
|
+
for edgeWithPoint in edgesWithVertex:
|
|
185
|
+
pointEdgeVertex0Id = edgeWithPoint.vertex0Id
|
|
186
|
+
pointEdgeVertex0 = voronoiVertices[pointEdgeVertex0Id]
|
|
187
|
+
|
|
188
|
+
pointEdgeVertex1Id = edgeWithPoint.vertex1Id
|
|
189
|
+
pointEdgeVertex1 = voronoiVertices[pointEdgeVertex1Id]
|
|
190
|
+
|
|
191
|
+
GraphOps.addVertexToGraph(graph = existingConnections, vertexId = pointEdgeVertex0Id, vertexX = pointEdgeVertex0.x, vertexY = pointEdgeVertex0.y)
|
|
192
|
+
GraphOps.addVertexToGraph(graph = existingConnections, vertexId = pointEdgeVertex1Id, vertexX = pointEdgeVertex1.x, vertexY = pointEdgeVertex1.y)
|
|
193
|
+
|
|
194
|
+
pointEdgeVertexDistance = GraphOps.scaledGraphPointDistance(graph = existingConnections, p1 = pointEdgeVertex0, p2 = pointEdgeVertex1)
|
|
195
|
+
GraphOps.addConnectionToGraph(graph = existingConnections, connection = edgeWithPoint, vertexDistance = pointEdgeVertexDistance)
|
|
196
|
+
|
|
197
|
+
existingEdgeVertexAngles = {}
|
|
198
|
+
|
|
199
|
+
for (vertex0Id, vertex1Id) in existingConnections.edges:
|
|
200
|
+
existingEdge = EdgeVertexInfo(vertex0Id = vertex0Id, vertex1Id = vertex1Id)
|
|
201
|
+
|
|
202
|
+
vertex0EdgeAngles = TrackGenerator._calculateEdgeAnglesWithGraphVertexEdges(graph = existingConnections, edge = existingEdge, vertexId = vertex0Id)
|
|
203
|
+
for vertex0AngleEdges in vertex0EdgeAngles:
|
|
204
|
+
if vertex0AngleEdges not in existingEdgeVertexAngles:
|
|
205
|
+
existingEdgeVertexAngles[vertex0AngleEdges] = vertex0EdgeAngles[vertex0AngleEdges]
|
|
206
|
+
|
|
207
|
+
vertex1EdgeAngles = TrackGenerator._calculateEdgeAnglesWithGraphVertexEdges(graph = existingConnections, edge = existingEdge, vertexId = vertex1Id)
|
|
208
|
+
for vertex1AngleEdges in vertex1EdgeAngles:
|
|
209
|
+
if vertex1AngleEdges not in existingEdgeVertexAngles:
|
|
210
|
+
existingEdgeVertexAngles[vertex1AngleEdges] = vertex1EdgeAngles[vertex1AngleEdges]
|
|
211
|
+
|
|
212
|
+
initialDiagramEdgeAngles = tuple(existingEdgeVertexAngles.values())
|
|
213
|
+
initialDiagramMinAcceptableAngle = numpy.quantile(a = initialDiagramEdgeAngles, q = newConnectionAngleMinQuantile)
|
|
214
|
+
|
|
215
|
+
for vertexId in existingConnections.nodes:
|
|
216
|
+
TrackGenerator._updateVertexPotentialConnections(existingConnectionsGraph = existingConnections, potentialConnectionsGraph = potentialConnections, vertexToUpdateId = vertexId)
|
|
217
|
+
|
|
218
|
+
diagramEdgesSorted = sorted(voronoiDiagramEdges, key = lambda vde: GraphOps.graphEdgeLength(graph = existingConnections, edge = vde))
|
|
219
|
+
numDiagramEdgesToProcess = floor(len(diagramEdgesSorted) * diagramEdgePercentageToProcess)
|
|
220
|
+
|
|
221
|
+
diagramEdgesToProcess = diagramEdgesSorted[:numDiagramEdgesToProcess]
|
|
222
|
+
|
|
223
|
+
intersectionEdges = []
|
|
224
|
+
|
|
225
|
+
for diagramEdgeToProcess in diagramEdgesToProcess:
|
|
226
|
+
edgeVertex0Id = diagramEdgeToProcess.vertex0Id
|
|
227
|
+
edgeVertex1Id = diagramEdgeToProcess.vertex1Id
|
|
228
|
+
|
|
229
|
+
if existingConnections.has_edge(u = edgeVertex0Id, v = edgeVertex1Id):
|
|
230
|
+
edgeToProcessLength = GraphOps.graphEdgeLength(graph = existingConnections, edge = diagramEdgeToProcess)
|
|
231
|
+
|
|
232
|
+
(disconnectedByDeletionVertex0, disconnectedByDeletionVertex1) = GraphOps.removeEdgeAndReturnDisconnected(edgeGraph = existingConnections, existingEdge = diagramEdgeToProcess)
|
|
233
|
+
possibleConnections = []
|
|
234
|
+
|
|
235
|
+
for vertexDisconnected0 in disconnectedByDeletionVertex0:
|
|
236
|
+
for vertexDisconnected1 in disconnectedByDeletionVertex1:
|
|
237
|
+
connectionAlreadyAdded = existingConnections.has_edge(u = vertexDisconnected0, v = vertexDisconnected1)
|
|
238
|
+
|
|
239
|
+
if not connectionAlreadyAdded:
|
|
240
|
+
potentialConnection = EdgeVertexInfo(vertex0Id = vertexDisconnected0, vertex1Id = vertexDisconnected1)
|
|
241
|
+
|
|
242
|
+
if GraphOps.graphEdgeLength(graph = potentialConnections, edge = potentialConnection) > edgeToProcessLength:
|
|
243
|
+
possibleConnections.append(potentialConnection)
|
|
244
|
+
|
|
245
|
+
maybeViableConnection = TrackGenerator._findPossibleViableConnection(graph = existingConnections, possibleConnections = tuple(possibleConnections), minAcceptableAngle = initialDiagramMinAcceptableAngle)
|
|
246
|
+
|
|
247
|
+
if maybeViableConnection:
|
|
248
|
+
print(f"reconnecting to {maybeViableConnection}")
|
|
249
|
+
maybeViableVertexLength = GraphOps.graphEdgeLength(graph = potentialConnections, edge = maybeViableConnection)
|
|
250
|
+
|
|
251
|
+
maybeViableVertex0Id = maybeViableConnection.vertex0Id
|
|
252
|
+
maybeViableVertex1Id = maybeViableConnection.vertex1Id
|
|
253
|
+
|
|
254
|
+
potentialConnections.remove_edge(u = maybeViableVertex0Id, v = maybeViableVertex1Id)
|
|
255
|
+
|
|
256
|
+
# calculate intersections
|
|
257
|
+
potentialIntersections = tuple((EdgeVertexInfo(vertex0Id = vertex0Id, vertex1Id = vertex1Id) for (vertex0Id, vertex1Id) in existingConnections.edges if (vertex0Id != maybeViableVertex0Id and vertex0Id != maybeViableVertex1Id) and (vertex1Id != maybeViableVertex0Id and vertex1Id != maybeViableVertex1Id)))
|
|
258
|
+
edgeIntersectionsInfo = EdgesOps.calculateEdgesIntersectionInfo(edgeGraph = existingConnections, intersectingEdge = maybeViableConnection, otherEdges = potentialIntersections)
|
|
259
|
+
|
|
260
|
+
if edgeIntersectionsInfo:
|
|
261
|
+
# We expect this to be sorted along the v0 -> v1 path.
|
|
262
|
+
precedingVertexId = maybeViableVertex0Id
|
|
263
|
+
precedingVertex = GraphOps.graphVertex(graph = existingConnections, vertexId = maybeViableVertex0Id)
|
|
264
|
+
|
|
265
|
+
for edgeIntersectionInfo in edgeIntersectionsInfo:
|
|
266
|
+
edgeIntersectionPoint = edgeIntersectionInfo.intersectionPoint
|
|
267
|
+
distanceToIntersection = GraphOps.scaledGraphPointDistance(graph = existingConnections, p1 = precedingVertex, p2 = edgeIntersectionPoint)
|
|
268
|
+
|
|
269
|
+
edgeIntersectionPointId = uuid4()
|
|
270
|
+
print(f"creating intersection point {edgeIntersectionPointId}")
|
|
271
|
+
|
|
272
|
+
existingConnections.add_node(node_for_adding = edgeIntersectionPointId, x = edgeIntersectionPoint.x, y = edgeIntersectionPoint.y)
|
|
273
|
+
|
|
274
|
+
existingConnections.add_edge(u_of_edge = precedingVertexId, v_of_edge = edgeIntersectionPointId, edgeLength = distanceToIntersection)
|
|
275
|
+
intersectionEdges.append(EdgeVertexInfo(vertex0Id = precedingVertexId, vertex1Id = edgeIntersectionPointId))
|
|
276
|
+
|
|
277
|
+
edgeIntersected = edgeIntersectionInfo.intersectedEdge
|
|
278
|
+
|
|
279
|
+
edgeIntersectedVertex0Id = edgeIntersected.vertex0Id
|
|
280
|
+
edgeIntersectedVertex1Id = edgeIntersected.vertex1Id
|
|
281
|
+
|
|
282
|
+
print(f"handling intersected edge {edgeIntersectedVertex0Id}, {edgeIntersectedVertex1Id}")
|
|
283
|
+
|
|
284
|
+
edgeIntersectedVertex0 = GraphOps.graphVertex(graph = existingConnections, vertexId = edgeIntersectedVertex0Id)
|
|
285
|
+
edgeIntersectedVertex1 = GraphOps.graphVertex(graph = existingConnections, vertexId = edgeIntersectedVertex1Id)
|
|
286
|
+
|
|
287
|
+
iv0DistanceToIntersection = GraphOps.scaledGraphPointDistance(graph = existingConnections, p1 = edgeIntersectedVertex0, p2 = edgeIntersectionPoint)
|
|
288
|
+
iv1DistanceToIntersection = GraphOps.scaledGraphPointDistance(graph = existingConnections, p1 = edgeIntersectionPoint, p2 = edgeIntersectedVertex1)
|
|
289
|
+
|
|
290
|
+
existingConnections.remove_edge(u = edgeIntersectedVertex0Id, v = edgeIntersectedVertex1Id)
|
|
291
|
+
if edgeIntersected in intersectionEdges:
|
|
292
|
+
intersectionEdges.remove(edgeIntersected)
|
|
293
|
+
|
|
294
|
+
existingConnections.add_edge(u_of_edge = edgeIntersectedVertex0Id, v_of_edge = edgeIntersectionPointId, edgeLength = iv0DistanceToIntersection)
|
|
295
|
+
intersectionEdges.append(EdgeVertexInfo(vertex0Id = edgeIntersectedVertex0Id, vertex1Id = edgeIntersectionPointId))
|
|
296
|
+
|
|
297
|
+
existingConnections.add_edge(u_of_edge = edgeIntersectionPointId, v_of_edge = edgeIntersectedVertex1Id, edgeLength = iv1DistanceToIntersection)
|
|
298
|
+
intersectionEdges.append(EdgeVertexInfo(vertex0Id = edgeIntersectionPointId, vertex1Id = edgeIntersectedVertex1Id))
|
|
299
|
+
|
|
300
|
+
TrackGenerator._updateVertexPotentialConnections(existingConnectionsGraph = existingConnections, potentialConnectionsGraph = potentialConnections, vertexToUpdateId = edgeIntersectionPointId)
|
|
301
|
+
|
|
302
|
+
precedingVertexId = edgeIntersectionPointId
|
|
303
|
+
precedingVertex = edgeIntersectionPoint
|
|
304
|
+
|
|
305
|
+
# finish up with precedingVertex (last intersection point) -> vertex1
|
|
306
|
+
maybeViableVertex1 = GraphOps.graphVertex(graph = existingConnections, vertexId = maybeViableVertex1Id)
|
|
307
|
+
|
|
308
|
+
distanceToEnd = GraphOps.scaledGraphPointDistance(graph = existingConnections, p1 = precedingVertex, p2 = maybeViableVertex1)
|
|
309
|
+
|
|
310
|
+
existingConnections.add_edge(u_of_edge = precedingVertexId, v_of_edge = maybeViableVertex1Id, edgeLength = distanceToEnd)
|
|
311
|
+
intersectionEdges.append(EdgeVertexInfo(vertex0Id = precedingVertexId, vertex1Id = maybeViableVertex1Id))
|
|
312
|
+
else:
|
|
313
|
+
existingConnections.add_edge(u_of_edge = maybeViableVertex0Id, v_of_edge = maybeViableVertex1Id, edgeLength = maybeViableVertexLength)
|
|
314
|
+
else:
|
|
315
|
+
print(f"skipping edge {diagramEdgesToProcess}")
|
|
316
|
+
|
|
317
|
+
TrackGenerator._pruneIntersectionEdgesFromExistingConnectionsGraph(existingConnectionsGraph = existingConnections, intersectionEdges = intersectionEdges)
|
|
318
|
+
TrackGenerator._handleLonelyExistingConnections(existingConnectionsGraph = existingConnections, lonelyConnectionMinLengthQuantile = lonelyConnectionMinLengthQuantile)
|
|
319
|
+
|
|
320
|
+
edges = tuple((EdgeVertexInfo(vertex0Id = vertex0Id, vertex1Id = vertex1Id) for (vertex0Id, vertex1Id) in existingConnections.edges))
|
|
321
|
+
|
|
322
|
+
stops = { edge: TrackGenerator._generateStopsOnConnection(connectionGraph = existingConnections, connection = edge, connectionLengthVertexPadding = connectionLengthVertexPadding, connectionLengthNodeBuffer = connectionLengthNodeBuffer) for edge in edges}
|
|
323
|
+
|
|
324
|
+
nodes = { nodeId: GraphOps.graphVertex(graph = existingConnections, vertexId = nodeId) for nodeId in existingConnections.nodes}
|
|
325
|
+
|
|
326
|
+
return Track(nodes = nodes, stops = stops, edges = edges)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from .data import EdgesMakingAngle, EdgeIntersectionInfo, EdgeVertexInfo
|
|
2
|
+
|
|
3
|
+
from ..graphs.Ops import Ops as GraphOps
|
|
4
|
+
|
|
5
|
+
from math import atan, degrees
|
|
6
|
+
|
|
7
|
+
from networkx import Graph
|
|
8
|
+
|
|
9
|
+
from voronout.Point import Point
|
|
10
|
+
|
|
11
|
+
class Ops:
|
|
12
|
+
@staticmethod
|
|
13
|
+
def _edgeSlope(edgePoint1: Point, edgePoint2: Point) -> float:
|
|
14
|
+
if edgePoint2.x == edgePoint1.x:
|
|
15
|
+
return 0.0
|
|
16
|
+
else:
|
|
17
|
+
return (edgePoint2.y - edgePoint1.y)/(edgePoint2.x - edgePoint1.x)
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def _angleBetweenEdges(edgeGraph: Graph, edge0: EdgeVertexInfo, edge1: EdgeVertexInfo) -> float:
|
|
21
|
+
edge0Slope = Ops._edgeSlope(edgePoint1 = GraphOps.graphVertex(graph = edgeGraph, vertexId = edge0.vertex0Id), edgePoint2 = GraphOps.graphVertex(graph = edgeGraph, vertexId = edge0.vertex1Id))
|
|
22
|
+
edge1Slope = Ops._edgeSlope(edgePoint1 = GraphOps.graphVertex(graph = edgeGraph, vertexId = edge1.vertex0Id), edgePoint2 = GraphOps.graphVertex(graph = edgeGraph, vertexId = edge1.vertex1Id))
|
|
23
|
+
|
|
24
|
+
slopeNumerator = edge0Slope - edge1Slope
|
|
25
|
+
slopeDenominator = 1 + (edge0Slope * edge1Slope)
|
|
26
|
+
|
|
27
|
+
return degrees(atan(abs(slopeNumerator / slopeDenominator)))
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def _calculateEdgeAngles(edgeGraph: Graph, angleEdges: EdgesMakingAngle) -> float:
|
|
31
|
+
return Ops._angleBetweenEdges(edgeGraph = edgeGraph, edge0 = angleEdges.edge0, edge1 = angleEdges.edge1)
|
|
32
|
+
|
|
33
|
+
def calculateEdgeAnglesWithExtantVertexEdges(edgeGraph: Graph, edgesMakingAngles: tuple[EdgesMakingAngle]) -> dict[EdgesMakingAngle, float]:
|
|
34
|
+
return { edgesMakingAngle: Ops._calculateEdgeAngles(edgeGraph = edgeGraph, angleEdges = edgesMakingAngle) for edgesMakingAngle in edgesMakingAngles }
|
|
35
|
+
|
|
36
|
+
# https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line
|
|
37
|
+
@staticmethod
|
|
38
|
+
def _calculateEdgeIntersectionInfo(edgeGraph: Graph, intersectingEdge: EdgeVertexInfo, intersectedEdge: EdgeVertexInfo) -> EdgeIntersectionInfo:
|
|
39
|
+
intersectingVertex0 = GraphOps.graphVertex(graph = edgeGraph, vertexId = intersectingEdge.vertex0Id)
|
|
40
|
+
intersectingVertex1 = GraphOps.graphVertex(graph = edgeGraph, vertexId = intersectingEdge.vertex1Id)
|
|
41
|
+
|
|
42
|
+
intersectedVertex0 = GraphOps.graphVertex(graph = edgeGraph, vertexId = intersectedEdge.vertex0Id)
|
|
43
|
+
intersectedVertex1 = GraphOps.graphVertex(graph = edgeGraph, vertexId = intersectedEdge.vertex1Id)
|
|
44
|
+
|
|
45
|
+
x1dx3 = intersectingVertex0.x - intersectedVertex0.x
|
|
46
|
+
y3dy4 = intersectedVertex0.y - intersectedVertex1.y
|
|
47
|
+
|
|
48
|
+
y1dy3 = intersectingVertex0.y - intersectedVertex0.y
|
|
49
|
+
x3dx4 = intersectedVertex0.x - intersectedVertex1.x
|
|
50
|
+
|
|
51
|
+
x1dx2 = intersectingVertex0.x - intersectingVertex1.x
|
|
52
|
+
y1dy2 = intersectingVertex0.y - intersectingVertex1.y
|
|
53
|
+
|
|
54
|
+
denominator = (x1dx2 * y3dy4) - (y1dy2 * x3dx4)
|
|
55
|
+
|
|
56
|
+
if denominator != 0.0:
|
|
57
|
+
tNumerator = (x1dx3 * y3dy4) - (y1dy3 * x3dx4)
|
|
58
|
+
t = tNumerator / denominator
|
|
59
|
+
|
|
60
|
+
uNumerator = (x1dx2 * y1dy3) - (y1dy2 * x1dx3)
|
|
61
|
+
u = -(uNumerator / denominator)
|
|
62
|
+
|
|
63
|
+
if 0 <= t <= 1 and 0 <= u <= 1:
|
|
64
|
+
intersectionX = intersectingVertex0.x + (t * -x1dx2)
|
|
65
|
+
intersectionY = intersectingVertex0.y + (t * -y1dy2)
|
|
66
|
+
|
|
67
|
+
intersectionPoint = Point(x = intersectionX, y = intersectionY)
|
|
68
|
+
return EdgeIntersectionInfo(intersectionPoint = intersectionPoint, intersectedEdge = intersectedEdge)
|
|
69
|
+
else:
|
|
70
|
+
return None
|
|
71
|
+
else:
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def calculateEdgesIntersectionInfo(edgeGraph: Graph, intersectingEdge: EdgeVertexInfo, otherEdges: tuple[EdgeVertexInfo]) -> tuple[EdgeIntersectionInfo]:
|
|
76
|
+
edgeIntersectionData = []
|
|
77
|
+
for otherEdge in otherEdges:
|
|
78
|
+
edgeIntersectionInfo = Ops._calculateEdgeIntersectionInfo(edgeGraph = edgeGraph, intersectingEdge = intersectingEdge, intersectedEdge = otherEdge)
|
|
79
|
+
if edgeIntersectionInfo:
|
|
80
|
+
edgeIntersectionData.append(edgeIntersectionInfo)
|
|
81
|
+
|
|
82
|
+
intersectingVertex0Id = intersectingEdge.vertex0Id
|
|
83
|
+
intersectingVertex0 = GraphOps.graphVertex(graph = edgeGraph, vertexId = intersectingVertex0Id)
|
|
84
|
+
|
|
85
|
+
edgeIntersectionData.sort(key = lambda eii: GraphOps.scaledGraphPointDistance(graph = edgeGraph, p1 = intersectingVertex0, p2 = eii.intersectionPoint))
|
|
86
|
+
|
|
87
|
+
return edgeIntersectionData
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .Ops import Ops
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from uuid import uuid4
|
|
3
|
+
|
|
4
|
+
@dataclass(frozen=True)
|
|
5
|
+
class EdgeVertexInfo:
|
|
6
|
+
vertex0Id: uuid4
|
|
7
|
+
vertex1Id: uuid4
|
|
8
|
+
|
|
9
|
+
def __eq__(self, other) -> bool:
|
|
10
|
+
return (self.vertex0Id == other.vertex0Id and self.vertex1Id == other.vertex1Id) or (self.vertex0Id == other.vertex1Id and self.vertex1Id == other.vertex0Id)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from ..edges.data import EdgeVertexInfo
|
|
2
|
+
|
|
3
|
+
from networkx import Graph
|
|
4
|
+
from networkx.algorithms import approximation as nxApproximation
|
|
5
|
+
|
|
6
|
+
from uuid import uuid4
|
|
7
|
+
|
|
8
|
+
from voronout.Point import Point
|
|
9
|
+
|
|
10
|
+
class Ops:
|
|
11
|
+
@staticmethod
|
|
12
|
+
def graphVertex(graph: Graph, vertexId: uuid4) -> Point:
|
|
13
|
+
if graph.has_node(n = vertexId):
|
|
14
|
+
vertex = graph.nodes[vertexId]
|
|
15
|
+
return Point(x = vertex["x"], y = vertex["y"])
|
|
16
|
+
else:
|
|
17
|
+
return None
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def graphEdgeLength(graph: Graph, edge: EdgeVertexInfo) -> float:
|
|
21
|
+
return graph.edges[edge.vertex0Id, edge.vertex1Id]["edgeLength"]
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def scaledGraphPointDistance(graph: Graph, p1: Point, p2: Point):
|
|
25
|
+
graphWidthScalar = 1 / graph.graph["width"]
|
|
26
|
+
graphHeightScalar = 1 / graph.graph["height"]
|
|
27
|
+
|
|
28
|
+
scaledP1 = p1.scale(widthScalar = graphWidthScalar, heightScalar = graphHeightScalar)
|
|
29
|
+
scaledP2 = p2.scale(widthScalar = graphWidthScalar, heightScalar = graphHeightScalar)
|
|
30
|
+
|
|
31
|
+
return Point.distance(p1 = scaledP1, p2 = scaledP2)
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def addVertexToGraph(graph: Graph, vertexId: uuid4, vertexX: float, vertexY: float):
|
|
35
|
+
if not graph.has_node(n = vertexId):
|
|
36
|
+
graph.add_node(node_for_adding = vertexId, x = vertexX, y = vertexY)
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def addConnectionToGraph(graph: Graph, connection: EdgeVertexInfo, vertexDistance: float):
|
|
40
|
+
vertex0Id = connection.vertex0Id
|
|
41
|
+
vertex1Id = connection.vertex1Id
|
|
42
|
+
|
|
43
|
+
if not graph.has_edge(u = vertex0Id, v = vertex1Id):
|
|
44
|
+
graph.add_edge(u_of_edge = vertex0Id, v_of_edge = vertex1Id, edgeLength = vertexDistance)
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def removeEdgeAndReturnDisconnected(edgeGraph: Graph, existingEdge: EdgeVertexInfo) -> tuple[tuple[EdgeVertexInfo]]:
|
|
48
|
+
existingEdgeVertex0Id = existingEdge.vertex0Id
|
|
49
|
+
existingEdgeVertex1Id = existingEdge.vertex1Id
|
|
50
|
+
|
|
51
|
+
beforeExistingConnectivities = nxApproximation.all_pairs_node_connectivity(G = edgeGraph)
|
|
52
|
+
|
|
53
|
+
vertex0ConnectivityBeforeDeletion = beforeExistingConnectivities[existingEdgeVertex0Id]
|
|
54
|
+
vertex1ConnectivityBeforeDeletion = beforeExistingConnectivities[existingEdgeVertex1Id]
|
|
55
|
+
|
|
56
|
+
edgeGraph.remove_edge(u = existingEdgeVertex0Id, v = existingEdgeVertex1Id)
|
|
57
|
+
afterExistingConnectivities = nxApproximation.all_pairs_node_connectivity(G = edgeGraph)
|
|
58
|
+
|
|
59
|
+
disconnectedByDeletionVertex0 = tuple((maybeDisconnectedVertex for (maybeDisconnectedVertex, numConnectionsLeft) in afterExistingConnectivities[existingEdgeVertex0Id].items() if numConnectionsLeft == 0 and vertex0ConnectivityBeforeDeletion[maybeDisconnectedVertex] > 0))
|
|
60
|
+
disconnectedByDeletionVertex1 = tuple((maybeDisconnectedVertex for (maybeDisconnectedVertex, numConnectionsLeft) in afterExistingConnectivities[existingEdgeVertex1Id].items() if numConnectionsLeft == 0 and vertex1ConnectivityBeforeDeletion[maybeDisconnectedVertex] > 0))
|
|
61
|
+
|
|
62
|
+
return (disconnectedByDeletionVertex0, disconnectedByDeletionVertex1)
|
|
63
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .Ops import Ops
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from flask.json.provider import DefaultJSONProvider
|
|
2
|
+
|
|
3
|
+
from ... import Track
|
|
4
|
+
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from voronout.Point import Point
|
|
8
|
+
|
|
9
|
+
class FlaskRampantTrackGenerationJSONProvider(DefaultJSONProvider):
|
|
10
|
+
def _handlePointDict(self, pointDict: dict[UUID, Point]) -> dict[str, dict[str, float]]:
|
|
11
|
+
return {str(key): self.loads(repr(value)) for (key, value) in pointDict.items()}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def dumps(self, obj, **kw):
|
|
15
|
+
if isinstance(obj, Track):
|
|
16
|
+
stopObj = {self.loads(repr(stopLine)): list((self.loads(repr(lineStop)) for lineStop in lineStops)) for (stopLine, lineStops) in obj.stops.items()}
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
'nodes': self._handlePointDict(obj.nodes),
|
|
20
|
+
'stops': stopObj,
|
|
21
|
+
'edges': list(stopObj.values())
|
|
22
|
+
}
|
|
23
|
+
else:
|
|
24
|
+
return super().dumps(obj, **kw)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .JSON import FlaskRampantTrackGenerationJSONProvider
|