-
-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes centroid connector creation (#584)
* Fixes centroid connector creation * Fixes centroid connector creation * Fixes centroid connector creation * Update connector_creation.py --------- Co-authored-by: pveigadecamargo <[email protected]> Co-authored-by: Renata Imai <[email protected]>
- Loading branch information
1 parent
692579c
commit 523611d
Showing
5 changed files
with
57 additions
and
104 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,122 +1,67 @@ | ||
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 shapely.geometry import LineString, Polygon | ||
from aequilibrae.utils.db_utils import commit_and_close | ||
|
||
INFINITE_CAPACITY = 99999 | ||
|
||
|
||
def connector_creation( | ||
geo, zone_id: int, srid: int, mode_id: str, network, link_types="", connectors=1, conn_: Optional[Connection] = None | ||
zone_id: int, | ||
mode_id: str, | ||
network, | ||
link_types="", | ||
connectors=1, | ||
conn_: Optional[Connection] = None, | ||
delimiting_area: Polygon = 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: | ||
with conn_ or commit_and_close(network.project.path_to_file) as conn: | ||
logger = network.project.logger | ||
if conn.execute("select count(*) from nodes where node_id=?", [zone_id]).fetchone() is None: | ||
if sum(conn.execute("select count(*) from nodes where node_id=?", [zone_id]).fetchone()) == 0: | ||
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}]*" | ||
proj_nodes = network.nodes.data | ||
|
||
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""" | ||
centroid = proj_nodes.query("node_id == @zone_id") # type: gpd.GeoDataFrame | ||
centroid = centroid.rename(columns={"node_id": "zone_id"})[["zone_id", "geometry"]] | ||
|
||
# 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 | ||
nodes = proj_nodes.query("is_centroid != 1 and modes.str.contains(@mode_id)", engine="python") | ||
|
||
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) | ||
if len(link_types) > 0: | ||
nodes = nodes[nodes.link_types.str.contains("|".join(list(link_types)))] | ||
|
||
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) | ||
if delimiting_area is not None: | ||
nodes = nodes[nodes.geometry.within(delimiting_area)] | ||
|
||
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 nodes.empty: | ||
zone_id = centroid["zone_id"].values[0] | ||
logger.warning(f"No nodes found for centroid {zone_id} (mode {mode_id} and link types {link_types})") | ||
return | ||
|
||
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}") | ||
joined = nodes[["node_id", "geometry"]].sjoin_nearest(centroid, distance_col="distance_connector") | ||
joined = joined.nsmallest(connectors, "distance_connector") | ||
|
||
centr_geo = centroid.geometry.values[0] | ||
links = network.links | ||
for node_to_connect in nds: | ||
for _, rec in joined.iterrows(): | ||
link = links.new() | ||
node_to = proj_nodes.get(node_to_connect) | ||
link.geometry = LineString([node.geometry, node_to.geometry]) | ||
link.geometry = LineString([centr_geo, rec.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}") | ||
if not joined.empty: | ||
logger.warning(f"{joined.shape[0]} new centroid connectors for mode {mode_id} added for centroid {zone_id}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters