Source code for aequilibrae.project.network.connector_creation
from math import pi, sqrt
from sqlite3 import Connection
from typing import Optional
import numpy as np
from scipy.cluster.vq import kmeans2, whiten
from scipy.spatial.distance import cdist
import shapely.wkb
from shapely.geometry import LineString
from aequilibrae.utils.db_utils import commit_and_close
INFINITE_CAPACITY = 99999
[docs]
def connector_creation(
geo, zone_id: int, srid: int, mode_id: str, network, link_types="", connectors=1, conn_: Optional[Connection] = None
):
if len(mode_id) > 1:
raise Exception("We can only add centroid connectors for one mode at a time")
with conn_ or commit_and_close(network.project.connect()) as conn:
logger = network.project.logger
if conn.execute("select count(*) from nodes where node_id=?", [zone_id]).fetchone() is None:
logger.warning("This centroid does not exist. Please create it first")
return
proj_nodes = network.nodes
node = proj_nodes.get(zone_id)
sql = "select count(*) from links where a_node=? and instr(modes,?) > 0"
if conn.execute(sql, [zone_id, mode_id]).fetchone()[0] > 0:
logger.warning("Mode is already connected")
return
if len(link_types) > 0:
lt = f"*[{link_types}]*"
else:
lt = "".join([x[0] for x in conn.execute("Select link_type_id from link_types").fetchall()])
lt = f"*[{lt}]*"
sql = """select node_id, ST_asBinary(geometry), modes, link_types from nodes where ST_Within(geometry, GeomFromWKB(?, ?)) and
(nodes.rowid in (select rowid from SpatialIndex where f_table_name = 'nodes' and
search_frame = GeomFromWKB(?, ?)))
and link_types glob ? and instr(modes, ?)>0"""
# We expand the area by its average radius until it is 20 times
# beginning with a strict search within the zone
buffer = 0
increase = sqrt(geo.area / pi)
dt = []
while dt == [] and buffer <= increase * 10:
wkb = geo.buffer(buffer).wkb
dt = conn.execute(sql, [wkb, srid, wkb, srid, lt, mode_id]).fetchall()
buffer += increase
if buffer > increase:
msg = f"Could not find node inside zone {zone_id}. Search area was expanded until we found a suitable node"
logger.warning(msg)
if dt == []:
logger.warning(
f"FAILED! Could not find suitable nodes to connect within 5 times the diameter of zone {zone_id}."
)
return
coords = []
nodes = []
for node_id, wkb, modes, link_types in dt:
geo = shapely.wkb.loads(wkb)
coords.append([geo.x, geo.y])
nodes.append(node_id)
num_connectors = connectors
if len(nodes) == 0:
raise Exception("We could not find any candidate nodes that satisfied your criteria")
elif len(nodes) < connectors:
logger.warning(
f"We have fewer possible nodes than required connectors for zone {zone_id}. Will connect all of them."
)
num_connectors = len(nodes)
if num_connectors == len(coords):
all_nodes = nodes
else:
features = np.array(coords)
whitened = whiten(features)
centroids, allocation = kmeans2(whitened, num_connectors)
all_nodes = set()
for i in range(num_connectors):
nds = [x for x, y in zip(nodes, list(allocation)) if y == i]
centr = centroids[i]
positions = [x for x, y in zip(whitened, allocation) if y == i]
if positions:
dist = cdist(np.array([centr]), np.array(positions)).flatten()
node_to_connect = nds[dist.argmin()]
all_nodes.add(node_to_connect)
nds = list(all_nodes)
data = [zone_id] + nds
sql = f'select b_node from links where a_node=? and b_node in ({",".join(["?"] * len(nds))})'
data = [x[0] for x in conn.execute(sql, data).fetchall()]
if data:
qry = ",".join(["?"] * len(data))
dt = [mode_id, zone_id] + data
conn.execute(f"Update links set modes=modes || ? where a_node=? and b_node in ({qry})", dt)
nds = [x for x in nds if x not in data]
logger.warning(f"Mode {mode_id} added to {len(data)} existing centroid connectors for zone {zone_id}")
links = network.links
for node_to_connect in nds:
link = links.new()
node_to = proj_nodes.get(node_to_connect)
link.geometry = LineString([node.geometry, node_to.geometry])
link.modes = mode_id
link.direction = 0
link.link_type = "centroid_connector"
link.name = f"centroid connector zone {zone_id}"
link.capacity_ab = INFINITE_CAPACITY
link.capacity_ba = INFINITE_CAPACITY
link.save()
if nds:
logger.warning(f"{len(nds)} new centroid connectors for mode {mode_id} added for centroid {zone_id}")