-
Notifications
You must be signed in to change notification settings - Fork 5
/
gates.py
95 lines (80 loc) · 4.01 KB
/
gates.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import logging
import os
import random
import math
import sys
import numpy as np
import xml.etree.ElementTree as ET
if 'SUMO_HOME' in os.environ:
tools = os.path.join(os.environ['SUMO_HOME'], 'tools')
sys.path.append(tools)
else:
sys.exit("Please declare environment variable 'SUMO_HOME' to use sumolib")
import sumolib
def setup_city_gates(net: sumolib.net.Net, stats: ET.ElementTree, gate_count: str, city_radius: float):
"""
Generate the requested amount of city gates based on the network and insert them into stats.
"""
gate_count = find_gate_count_auto(city_radius) if gate_count == "auto" else int(gate_count)
assert gate_count >= 0, "Number of city gates cannot be negative"
# Find existing gates to determine how many we need to insert
xml_gates = stats.find("cityGates")
if xml_gates is None:
xml_gates = ET.SubElement(stats.getroot(), "cityGates")
xml_entrances = xml_gates.findall("entrance")
n = gate_count - len(xml_entrances)
if n < 0:
logging.debug(
f"[gates] {gate_count} city gate were requested, but there are already {len(xml_entrances)} defined")
if n <= 0:
return
# Find all nodes that are dead ends, i.e. nodes that only have one neighbouring node
# and at least one of the connecting edges is a road (as opposed to path) and allows private vehicles
dead_ends = [node for node in net.getNodes() if len(node.getNeighboringNodes()) == 1
and any([any([lane.allows("private") for lane in edge.getLanes()]) for edge in
node.getIncoming() + node.getOutgoing()])]
# The user cannot get more gates than there are dead ends
n = min(n, len(dead_ends))
logging.debug(f"[gates] Identified {len(dead_ends)} dead ends")
logging.debug(f"[gates] Inserting {n} new city gates")
# Find n unit vectors pointing in different directions
# If n = 4 and base_rad = 0 we get the cardinal directions:
# N
# |
# W<---o--->E
# |
# S
base_rad = random.random() * math.tau
rads = [(base_rad + i * math.tau / n) % math.tau for i in range(0, n)]
directions = [(math.cos(rad), math.sin(rad)) for rad in rads]
for direction in directions:
# Find the dead ends furthest in each direction using the dot product and argmax. Those nodes will be our gates.
# Dead ends are removed from the list to avoid duplicates.
gate_index = int(np.argmax([np.dot(node.getCoord(), direction) for node in dead_ends]))
gate = dead_ends[gate_index]
dead_ends.remove(gate)
# Decide proportion of the incoming and outgoing vehicles coming through this gate
# These numbers are relatively to the values of the other gates
# The number is proportional to the number of lanes allowing private vehicles
incoming_lanes = sum(
[len([lane for lane in edge.getLanes() if lane.allows("private")]) for edge in gate.getIncoming()])
outgoing_lanes = sum(
[len([lane for lane in edge.getLanes() if lane.allows("private")]) for edge in gate.getOutgoing()])
incoming_traffic = (1 + random.random()) * outgoing_lanes
outgoing_traffic = (1 + random.random()) * incoming_lanes
# Add entrance to stats file
edge, pos = (gate.getOutgoing()[0], 0) if len(gate.getOutgoing()) > 0 \
else (gate.getIncoming()[0], gate.getIncoming()[0].getLength())
logging.debug(
f"[gates] Adding entrance to statistics, edge: {edge.getID()}, incoming traffic: {incoming_traffic}, outgoing "
f"traffic: {outgoing_traffic}")
ET.SubElement(xml_gates, "entrance", attrib={
"edge": edge.getID(),
"incoming": str(incoming_traffic),
"outgoing": str(outgoing_traffic),
"pos": str(pos)
})
def find_gate_count_auto(city_radius: float) -> int:
# Assume there's a gate very 1750 meters around the circumference
circumference = 2 * city_radius * math.pi
return int(circumference / 1750)