Improve boundary algorithm
This commit is contained in:
@@ -25,6 +25,13 @@ def to_postgis_polygon(boundary: list[tuple[float, float]]) -> str:
|
||||
return f'polygon(({polygon[:-1]}))'
|
||||
|
||||
|
||||
def to_postgis_linestring(boundary: list[tuple[float, float]]) -> str:
|
||||
line = ''
|
||||
for pt in boundary:
|
||||
line += f'{pt[0]} {pt[1]},'
|
||||
return f'linestring({line[:-1]})'
|
||||
|
||||
|
||||
def get_nodes_in_boundary(name: str, boundary: list[tuple[float, float]]) -> list[str]:
|
||||
api = 'get_nodes_in_boundary'
|
||||
write(name, f"delete from temp_region where id = '{api}'")
|
||||
@@ -105,7 +112,7 @@ class Topology:
|
||||
if self._max_x_node == '' or self._nodes[node]['x'] > self._nodes[self._max_x_node]['x']:
|
||||
self._max_x_node = node
|
||||
|
||||
self._links = {}
|
||||
self._links: dict[str, Any] = {}
|
||||
self._link_list: list[str] = []
|
||||
for node in self._nodes:
|
||||
for link in get_node_links(db, node):
|
||||
@@ -140,25 +147,21 @@ class Topology:
|
||||
return self._link_list
|
||||
|
||||
|
||||
def calculate_boundary(name: str, nodes: list[str]) -> list[tuple[float, float]]:
|
||||
topology = Topology(name, nodes)
|
||||
t_nodes = topology.nodes()
|
||||
t_links = topology.links()
|
||||
|
||||
cursor = topology.max_x_node()
|
||||
def _calculate_boundary(name: str, cursor: str, t_nodes: dict[str, Any], t_links: dict[str, Any]) -> tuple[list[str], dict[str, list[str]], list[tuple[float, float]]]:
|
||||
in_angle = 0
|
||||
|
||||
paths: list[str] = []
|
||||
vertices: list[str] = []
|
||||
path: dict[str, list[str]] = {}
|
||||
while True:
|
||||
# prevent duplicated node
|
||||
if len(paths) > 0 and cursor == paths[-1]:
|
||||
if len(vertices) > 0 and cursor == vertices[-1]:
|
||||
break
|
||||
|
||||
# prevent duplicated path
|
||||
if len(paths) >= 3 and paths[0] == paths[-1] and paths[1] == cursor:
|
||||
if len(vertices) >= 3 and vertices[0] == vertices[-1] and vertices[1] == cursor:
|
||||
break
|
||||
|
||||
paths.append(cursor)
|
||||
vertices.append(cursor)
|
||||
|
||||
sorted_links = []
|
||||
overlapped_link = ''
|
||||
@@ -171,7 +174,8 @@ def calculate_boundary(name: str, nodes: list[str]) -> list[tuple[float, float]]
|
||||
|
||||
# work into a branch, return
|
||||
if len(sorted_links) == 0:
|
||||
cursor = paths[-2]
|
||||
path[overlapped_link] = []
|
||||
cursor = vertices[-2]
|
||||
in_angle = _angle_of_node_link(cursor, overlapped_link, t_nodes, t_links)
|
||||
continue
|
||||
|
||||
@@ -182,13 +186,166 @@ def calculate_boundary(name: str, nodes: list[str]) -> list[tuple[float, float]]
|
||||
out_link = link
|
||||
break
|
||||
|
||||
path[out_link] = []
|
||||
cursor = t_links[out_link]['node1'] if cursor == t_links[out_link]['node2'] else t_links[out_link]['node2']
|
||||
in_angle = _angle_of_node_link(cursor, out_link, t_nodes, t_links)
|
||||
|
||||
boundary: list[tuple[float, float]] = []
|
||||
for node in paths:
|
||||
for node in vertices:
|
||||
boundary.append((t_nodes[node]['x'], t_nodes[node]['y']))
|
||||
|
||||
return (vertices, path, boundary)
|
||||
|
||||
|
||||
def _collect_new_links(in_links: dict[str, list[str]], t_nodes: dict[str, Any], t_links: dict[str, Any], new_nodes: dict[str, Any], new_links: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
for link, pts in in_links.items():
|
||||
node1 = t_links[link]['node1']
|
||||
node2 = t_links[link]['node2']
|
||||
x1, x2 = t_nodes[node1]['x'], t_nodes[node2]['x']
|
||||
y1, y2 = t_nodes[node1]['y'], t_nodes[node2]['y']
|
||||
|
||||
if node1 not in new_nodes:
|
||||
new_nodes[node1] = { 'x': x1, 'y': y1, 'links': [] }
|
||||
if node2 not in new_nodes:
|
||||
new_nodes[node2] = { 'x': x2, 'y': y2, 'links': [] }
|
||||
|
||||
x_delta = x2 - x1
|
||||
y_delta = y2 - y1
|
||||
use_x = abs(x_delta) > abs(y_delta)
|
||||
|
||||
if len(pts) == 0:
|
||||
new_links[link] = t_links[link]
|
||||
else:
|
||||
sorted_nodes: list[tuple[float, str]] = []
|
||||
sorted_nodes.append((0.0, node1))
|
||||
sorted_nodes.append((1.0, node2))
|
||||
i = 0
|
||||
for pt in pts:
|
||||
x, y = new_nodes[pt]['x'], new_nodes[pt]['y']
|
||||
percent = ((x - x1) / x_delta) if use_x else ((y - y1) / y_delta)
|
||||
sorted_nodes.append((percent, pt))
|
||||
i += 1
|
||||
sorted_nodes = sorted(sorted_nodes, key=lambda s:s[0])
|
||||
|
||||
for i in range(1, len(sorted_nodes)):
|
||||
l = sorted_nodes[i - 1][1]
|
||||
r = sorted_nodes[i][1]
|
||||
new_link = f'LINK_[{l}]_[{r}]'
|
||||
new_links[new_link] = { 'node1': l, 'node2': r }
|
||||
|
||||
return (new_nodes, new_links)
|
||||
|
||||
|
||||
def calculate_boundary(name: str, nodes: list[str]) -> list[tuple[float, float]]:
|
||||
topology = Topology(name, nodes)
|
||||
t_nodes = topology.nodes()
|
||||
t_links = topology.links()
|
||||
|
||||
vertices, path, boundary = _calculate_boundary(name, topology.max_x_node(), t_nodes, t_links)
|
||||
|
||||
#return boundary
|
||||
|
||||
api = 'calculate_boundary'
|
||||
write(name, f"delete from temp_region where id = '{api}'")
|
||||
# use linestring instead of polygon to reduce strict limitation
|
||||
write(name, f"insert into temp_region (id, boundary) values ('{api}', '{to_postgis_linestring(boundary)}')")
|
||||
|
||||
write(name, f'delete from temp_node')
|
||||
for node in nodes:
|
||||
write(name, f"insert into temp_node values ('{node}')")
|
||||
|
||||
for row in read_all(name, f"select n.node from coordinates as c, temp_node as n, temp_region as r where c.node = n.node and ST_Intersects(c.coord, r.boundary) and r.id = '{api}'"):
|
||||
node = row['node']
|
||||
write(name, f"delete from temp_node where node = '{node}'")
|
||||
|
||||
outside_nodes: list[str] = []
|
||||
for row in read_all(name, "select node from temp_node"):
|
||||
outside_nodes.append(row['node'])
|
||||
|
||||
# no outside nodes, return
|
||||
if len(outside_nodes) == 0:
|
||||
write(name, f'delete from temp_node')
|
||||
write(name, f"delete from temp_region where id = '{api}'")
|
||||
return boundary
|
||||
|
||||
new_nodes: dict[str, Any] = {}
|
||||
new_links: dict[str, Any] = {}
|
||||
|
||||
boundary_links: dict[str, list[str]] = {}
|
||||
write(name, "delete from temp_link_2")
|
||||
for node in outside_nodes:
|
||||
for link in t_nodes[node]['links']:
|
||||
node1 = t_links[link]['node1']
|
||||
node2 = t_links[link]['node2']
|
||||
if node1 in outside_nodes and node2 not in outside_nodes and node2 not in vertices and link:
|
||||
if link not in boundary:
|
||||
boundary_links[link] = []
|
||||
line = f"LINESTRING({t_nodes[node1]['x']} {t_nodes[node1]['y']}, {t_nodes[node2]['x']} {t_nodes[node2]['y']})"
|
||||
write(name, f"insert into temp_link_2 values ('{link}', '{line}')")
|
||||
if node2 in outside_nodes and node1 not in outside_nodes and node1 not in vertices:
|
||||
if link not in boundary:
|
||||
boundary_links[link] = []
|
||||
line = f"LINESTRING({t_nodes[node1]['x']} {t_nodes[node1]['y']}, {t_nodes[node2]['x']} {t_nodes[node2]['y']})"
|
||||
write(name, f"insert into temp_link_2 values ('{link}', '{line}')")
|
||||
if node1 in outside_nodes and node2 in outside_nodes:
|
||||
x1, x2 = t_nodes[node1]['x'], t_nodes[node2]['x']
|
||||
y1, y2 = t_nodes[node1]['y'], t_nodes[node2]['y']
|
||||
if node1 not in new_nodes:
|
||||
new_nodes[node1] = { 'x': x1, 'y': y1, 'links': [] }
|
||||
if node2 not in new_nodes:
|
||||
new_nodes[node2] = { 'x': x2, 'y': y2, 'links': [] }
|
||||
if link not in new_links:
|
||||
new_links[link] = t_links[link]
|
||||
|
||||
# no boundary links, return
|
||||
if len(boundary_links) == 0:
|
||||
write(name, "delete from temp_link_2")
|
||||
write(name, f'delete from temp_node')
|
||||
write(name, f"delete from temp_region where id = '{api}'")
|
||||
return boundary
|
||||
|
||||
write(name, "delete from temp_link_1")
|
||||
for link, _ in path.items():
|
||||
node1 = t_links[link]['node1']
|
||||
node2 = t_links[link]['node2']
|
||||
line = f"LINESTRING({t_nodes[node1]['x']} {t_nodes[node1]['y']}, {t_nodes[node2]['x']} {t_nodes[node2]['y']})"
|
||||
write(name, f"insert into temp_link_1 (link, geom) values ('{link}', '{line}')")
|
||||
|
||||
has_intersection = False
|
||||
for row in read_all(name, f"select l1.link as l, l2.link as r, st_astext(st_intersection(l1.geom, l2.geom)) as p from temp_link_1 as l1, temp_link_2 as l2 where st_intersects(l1.geom, l2.geom)"):
|
||||
has_intersection = True
|
||||
|
||||
link1, link2, pt = str(row['l']), str(row['r']), str(row['p'])
|
||||
pts = pt.lower().removeprefix('point(').removesuffix(')').split(' ')
|
||||
xy = (float(pts[0]), float(pts[1]))
|
||||
|
||||
new_node = f'NODE_[{link1}]_[{link2}]'
|
||||
new_nodes[new_node] = { 'x': xy[0], 'y': xy[1], 'links': [] }
|
||||
|
||||
path[link1].append(new_node)
|
||||
boundary_links[link2].append(new_node)
|
||||
|
||||
if not has_intersection:
|
||||
write(name, "delete from temp_link_1")
|
||||
write(name, "delete from temp_link_2")
|
||||
write(name, 'delete from temp_node')
|
||||
write(name, f"delete from temp_region where id = '{api}'")
|
||||
return boundary
|
||||
|
||||
new_nodes, new_links = _collect_new_links(path, t_nodes, t_links, new_nodes, new_links)
|
||||
new_nodes, new_links = _collect_new_links(boundary_links, t_nodes, t_links, new_nodes, new_links)
|
||||
|
||||
for link, values in new_links.items():
|
||||
new_nodes[values['node1']]['links'].append(link)
|
||||
new_nodes[values['node2']]['links'].append(link)
|
||||
|
||||
_, _, boundary = _calculate_boundary(name, topology.max_x_node(), new_nodes, new_links)
|
||||
|
||||
write(name, "delete from temp_link_1")
|
||||
write(name, "delete from temp_link_2")
|
||||
write(name, 'delete from temp_node')
|
||||
write(name, f"delete from temp_region where id = '{api}'")
|
||||
|
||||
return boundary
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user