Skip to content

Commit b3dca29

Browse files
committed
simplify_graph() - remove interstitial nodes from graph
1 parent 4b2d85d commit b3dca29

File tree

3 files changed

+104
-2
lines changed

3 files changed

+104
-2
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ authors = ["Jack Chan <[email protected]>"]
44
version = "0.1.19"
55

66
[deps]
7+
ArchGDAL = "c9ce4bd3-c3d5-55b8-8973-c0e20141b8c3"
8+
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
79
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
810
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
911
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"

src/LightOSM.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ using Parameters
44
using DataStructures: DefaultDict, OrderedDict, MutableLinkedList, PriorityQueue, dequeue!, dequeue_pair!
55
using Statistics: mean
66
using SparseArrays: SparseMatrixCSC, sparse
7-
using Graphs: AbstractGraph, DiGraph, nv, outneighbors, weakly_connected_components, vertices
7+
using Graphs: AbstractGraph, DiGraph, nv, outneighbors, weakly_connected_components, vertices, all_neighbors, indegree, outdegree, add_edge!
88
using StaticGraphs: StaticDiGraph
99
using SimpleWeightedGraphs: SimpleWeightedDiGraph
1010
using MetaGraphs: MetaDiGraph
1111
using NearestNeighbors: KDTree, knn
12+
using ArchGDAL: IGeometry, createlinestring, createpoint
1213
using HTTP
1314
using JSON
1415
using LightXML
16+
using DataFrames
1517

1618
export GeoLocation,
1719
OSMGraph,
@@ -33,7 +35,8 @@ export GeoLocation,
3335
download_osm_buildings,
3436
buildings_from_object,
3537
buildings_from_download,
36-
buildings_from_file
38+
buildings_from_file,
39+
simplify_graph
3740

3841
include("types.jl")
3942
include("constants.jl")
@@ -46,5 +49,6 @@ include("traversal.jl")
4649
include("shortest_path.jl")
4750
include("nearest_node.jl")
4851
include("buildings.jl")
52+
include("simplification.jl")
4953

5054
end # module

src/simplification.jl

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
2+
#adapted from osmnx: https://github.com/gboeing/osmnx/blob/main/osmnx/simplification.py
3+
4+
"""
5+
Predicate wether v is an edge endpoint in the simplified version of g
6+
"""
7+
function is_endpoint(g::AbstractGraph, v)
8+
neighbors = all_neighbors(g, v)
9+
if v in neighbors # has self loop
10+
return true
11+
elseif outdegree(g, v) == 0 || indegree(g, v) == 0 # sink or source
12+
return true
13+
elseif length(neighbors) != 2 || indegree(g, v) != outdegree(g, v) # change to one way
14+
return true
15+
end
16+
return false
17+
end
18+
19+
"""
20+
iterator over all endpoints in g
21+
"""
22+
endpoints(g::AbstractGraph) = (v for v in vertices(g) if is_endpoint(g, v))
23+
24+
"""
25+
iterator over all paths in g which can be contracted
26+
"""
27+
function paths_to_reduce(g::AbstractGraph)
28+
(path_to_endpoint(g, (u, v)) for u in endpoints(g) for v in outneighbors(g, u))
29+
end
30+
31+
"""
32+
path to the next endpoint starting in edge (ep, ep_succ)
33+
"""
34+
function path_to_endpoint(g::AbstractGraph, (ep, ep_succ)::Tuple{T,T}) where {T<:Integer}
35+
path = [ep, ep_succ]
36+
head = ep_succ
37+
# ep_succ not in endpoints -> has 2 neighbors and degree 2 or 4
38+
while !is_endpoint(g, head)
39+
neighbors = [n for n in outneighbors(g, head) if n != path[end-1]]
40+
@assert length(neighbors) == 1 "found unmarked endpoint!"
41+
head, = neighbors
42+
push!(path, head)
43+
(head == ep) && return path # self loop
44+
end
45+
return path
46+
end
47+
48+
"""
49+
Build a new graph which simplifies the topology of osmg.graph.
50+
The resulting graph only contains intersections and dead ends from the original graph.
51+
The geometry of the contracted nodes is kept in the edge_gdf DataFrame
52+
"""
53+
function simplify_graph(osmg::OSMGraph)
54+
g = osmg.graph
55+
relevant_nodes = collect(endpoints(g))
56+
n = length(relevant_nodes)
57+
(n == nv(g)) && return g # nothing to simplify here
58+
59+
60+
G_simplified = DiGraph(n)
61+
weights = similar(osmg.weights, (n, n))
62+
edge_gdf = DataFrame(
63+
u = Int[],
64+
v = Int[],
65+
key = Int[],
66+
weight = Vector{eltype(osmg.weights)}(),
67+
geom = IGeometry[],
68+
)
69+
node_gdf = DataFrame(id = Int[], geom = IGeometry[])
70+
71+
72+
index_mapping = Dict{Int,Int}()
73+
for (new_i, old_i) in enumerate(relevant_nodes)
74+
index_mapping[old_i] = new_i
75+
geo = createpoint(osmg.node_coordinates[old_i])
76+
push!(node_gdf, (new_i, geo))
77+
end
78+
79+
for path in paths_to_reduce(g)
80+
u = index_mapping[first(path)]
81+
v = index_mapping[last(path)]
82+
edge_weight = sum((osmg.weights[i, i+1] for i in 1:length(path)-1))
83+
geo = createlinestring(osmg.node_coordinates[path])
84+
85+
if add_edge!(G_simplified, (u, v))
86+
key = 0
87+
weights[u, v] = edge_weight
88+
else # parallel edge
89+
key = sum((edge_gdf.u .== u) .& (edge_gdf.v .== v))
90+
weights[u, v] = min(edge_weight, weights[u, v])
91+
end
92+
push!(edge_gdf, (u, v, key, edge_weight, geo))
93+
end
94+
95+
return G_simplified, weights, node_gdf, edge_gdf
96+
end

0 commit comments

Comments
 (0)