From 7ae74237114f9eaadc8bd737d7d41862f0f3aef3 Mon Sep 17 00:00:00 2001 From: Jarod Lam Date: Wed, 24 Aug 2022 16:23:05 +1000 Subject: [PATCH] Add `set_lightosm_defaults` function to edit fallback values --- Project.toml | 2 +- docs/src/defaults.md | 5 ++ docs/src/index.md | 1 + src/buildings.jl | 6 +-- src/constants.jl | 113 ++++++++++++++++++++++++++++++++++++------- src/graph.jl | 30 ++++++++---- src/parse.jl | 20 ++++---- test/constants.jl | 110 ++++++++++++++++++++++++++++++----------- test/runtests.jl | 4 +- test/traversal.jl | 2 +- 10 files changed, 221 insertions(+), 72 deletions(-) create mode 100644 docs/src/defaults.md diff --git a/Project.toml b/Project.toml index fda30be..ebe2584 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.4" +version = "0.2.5" [deps] DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" diff --git a/docs/src/defaults.md b/docs/src/defaults.md new file mode 100644 index 0000000..620ecd6 --- /dev/null +++ b/docs/src/defaults.md @@ -0,0 +1,5 @@ +# Default Values + +```@docs +LightOSM.set_defaults +``` diff --git a/docs/src/index.md b/docs/src/index.md index f727374..07dd314 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -15,6 +15,7 @@ Pages = [ "create_buildings.md", "geolocation.md" "graph_utilities.md" + "defaults.md" ] ``` diff --git a/src/buildings.jl b/src/buildings.jl index a0f2c98..6ea76df 100644 --- a/src/buildings.jl +++ b/src/buildings.jl @@ -176,12 +176,12 @@ function height(tags::Dict)::Number return height isa String ? max([remove_non_numeric(h) for h in split(height, r"[+^;,-]")]...) : height elseif levels !== nothing levels = levels isa String ? round(max([remove_non_numeric(l) for l in split(levels, r"[+^;,-]")]...)) : levels - levels = levels == 0 ? rand(1:DEFAULT_MAX_BUILDING_LEVELS) : levels + levels = levels == 0 ? rand(1:DEFAULT_MAX_BUILDING_LEVELS[]) : levels else - levels = rand(1:DEFAULT_MAX_BUILDING_LEVELS) + levels = rand(1:DEFAULT_MAX_BUILDING_LEVELS[]) end - return levels * DEFAULT_BUILDING_HEIGHT_PER_LEVEL + return levels * DEFAULT_BUILDING_HEIGHT_PER_LEVEL[] end function parse_osm_buildings_dict(osm_buildings_dict::AbstractDict)::Dict{Integer,Building} diff --git a/src/constants.jl b/src/constants.jl index 25d042c..5a1d1b5 100644 --- a/src/constants.jl +++ b/src/constants.jl @@ -1,3 +1,12 @@ +""" +Default data types used to construct OSMGraph object. +""" +const DEFAULT_OSM_ID_TYPE = Int64 +const DEFAULT_OSM_INDEX_TYPE = Int32 +const DEFAULT_OSM_EDGE_WEIGHT_TYPE = Float64 +const DEFAULT_OSM_MAXSPEED_TYPE = Int16 +const DEFAULT_OSM_LANES_TYPE = Int8 + """ Approximate radius of the Earth (km) used in geoemetry functions. """ @@ -133,7 +142,7 @@ const OSM_DOWNLOAD_FORMAT = Dict( """ Default maxspeed based on highway type. """ -const DEFAULT_MAXSPEEDS = Dict( +const DEFAULT_MAXSPEEDS = Ref(Dict{String,DEFAULT_OSM_MAXSPEED_TYPE}( "motorway" => 100, "trunk" => 100, "primary" => 100, @@ -142,12 +151,12 @@ const DEFAULT_MAXSPEEDS = Dict( "unclassified" => 50, "residential" => 50, "other" => 50 -) +)) """ Default number of lanes based on highway type. """ -const DEFAULT_LANES = Dict( +const DEFAULT_LANES = Ref(Dict{String,DEFAULT_OSM_LANES_TYPE}( "motorway" => 3, "trunk" => 3, "primary" => 2, @@ -156,7 +165,7 @@ const DEFAULT_LANES = Dict( "unclassified" => 1, "residential" => 1, "other" => 1 -) +)) """ Default oneway attribute based on highway type. @@ -186,33 +195,101 @@ const ONEWAY_FALSE = Set(["false", "no", "0", 0]) """ Default factor applied to maxspeed when the `lane_efficiency` weight is used to contruct OSMGraph object. """ -const LANE_EFFICIENCY = Dict( +const LANE_EFFICIENCY = Ref(Dict{DEFAULT_OSM_LANES_TYPE,DEFAULT_OSM_EDGE_WEIGHT_TYPE}( 1 => 0.7, 2 => 0.8, 3 => 0.9, 4 => 1.0 -) - -""" -Default data types used to construct OSMGraph object. -""" -const DEFAULT_OSM_ID_TYPE = Int64 -const DEFAULT_OSM_INDEX_TYPE = Int32 -const DEFAULT_OSM_EDGE_WEIGHT_TYPE = Float64 -const DEFAULT_OSM_MAXSPEED_TYPE = Int16 -const DEFAULT_OSM_LANES_TYPE = Int8 +)) """ Default height of buildings in metres. """ -const DEFAULT_BUILDING_HEIGHT_PER_LEVEL = 4 +const DEFAULT_BUILDING_HEIGHT_PER_LEVEL = Ref{Float64}(4) """ Default maximum levels of buildings. """ -const DEFAULT_MAX_BUILDING_LEVELS = 3 +const DEFAULT_MAX_BUILDING_LEVELS = Ref{Int}(3) """ Delimiters used to clean maxspeed and lanes data. """ -const COMMON_OSM_STRING_DELIMITERS = r"[+^:;,|-]" \ No newline at end of file +const COMMON_OSM_STRING_DELIMITERS = r"[+^:;,|-]" + +""" + LightOSM.set_defaults(kwargs...) + +Sets default values that LightOSM uses when generating the graph. All arguments are +optional. + +# Keyword Arguments +- `maxspeeds::AbstractDict{String,<:Real}`: If no `maxspeed` way tag is available, these + values are used instead based on the value of the `highway` way tag. If no + `highway` way tag is available, the value for `"other"` is used. Unit is km/h. + Default value: + ```julia + Dict( + "motorway" => 100, + "trunk" => 100, + "primary" => 100, + "secondary" => 100, + "tertiary" => 50, + "unclassified" => 50, + "residential" => 50, + "other" => 50 + ) + ``` +- `lanes::AbstractDict{String,<:Integer}`: If no `lanes` way tag is available, these + values are used instead based on the value of the `highway` way tag. If no + `highway` way tag is available, the value for `"other"` is used. + Default value: + ```julia + Dict( + "motorway" => 3, + "trunk" => 3, + "primary" => 2, + "secondary" => 2, + "tertiary" => 1, + "unclassified" => 1, + "residential" => 1, + "other" => 1 + ) + ``` +- `lane_efficiency::AbstractDict{<:Integer,<:Real}`: Gives the lane efficiency based on + number of lanes. `1.0` is used for any number of lanes not specified here. + Default value: + ```julia + LANE_EFFICIENCY = Dict( + 1 => 0.7, + 2 => 0.8, + 3 => 0.9, + 4 => 1.0 + ) + ``` +- `building_height_per_level::Integer`: If the `height` building tag is not available, + it is calculated by multiplying this value by the number of levels from the + `building:levels` tag. Unit is metres. Default value: + ```julia + 4 + ``` +- `max_building_levels::Integer`: If the `building:levels` tag is not available, a number + is randomly chosen between 1 and this value. Default value: + ```julia + 3 + ``` +""" +function set_defaults(; + maxspeeds::AbstractDict{String,<:Real}=DEFAULT_MAXSPEEDS[], + lanes::AbstractDict{String,<:Integer}=DEFAULT_LANES[], + lane_efficiency::AbstractDict{<:Integer,<:Real}=LANE_EFFICIENCY[], + building_height_per_level::Real=DEFAULT_BUILDING_HEIGHT_PER_LEVEL[], + max_building_levels::Integer=DEFAULT_MAX_BUILDING_LEVELS[] + ) + DEFAULT_MAXSPEEDS[] = maxspeeds + DEFAULT_LANES[] = lanes + LANE_EFFICIENCY[] = lane_efficiency + DEFAULT_BUILDING_HEIGHT_PER_LEVEL[] = building_height_per_level + DEFAULT_MAX_BUILDING_LEVELS[] = max_building_levels + return +end diff --git a/src/graph.jl b/src/graph.jl index 283ae39..ba3ec4c 100644 --- a/src/graph.jl +++ b/src/graph.jl @@ -7,18 +7,30 @@ largest_connected_component::Bool=true )::OSMGraph -Creates an `OSMGraph` object from download OpenStreetMap network data, use with `download_osm_network`. +Creates an `OSMGraph` object from download OpenStreetMap network data, use with +`download_osm_network`. # Arguments -- `osm_data_object::Symbol`: OpenStreetMap network data parsed as either XML or Dictionary object depending on the download method. -- `network_type::Symbol=:drive`: Network type filter, pick from `:drive`, `:drive_service`, `:walk`, `:bike`, `:all`, `:all_private`, `:none`, `:rail`, must match the network type used to download `osm_data_object`. -- `weight_type::Symbol=:time`: Weight type for graph edges, pick from `:distance` (km), `:time` (hours), `:lane_efficiency` (time scaled by number of lanes). -- `graph_type::Symbol=:static`: Type of `Graphs.AbstractGraph`, pick from `:static` (StaticDiGraph), `:light` (DiGraph), `:simple_weighted` (SimpleWeightedDiGraph), `:meta` (MetaDiGraph). -- `precompute_dijkstra_states::Bool=false`: Set true to precompute dijkstra parent states for every source node in the graph, *NOTE* this may take a while and may not be possible for graphs with large amount of nodes due to memory limits. -- `largest_connected_component::Bool=true`: Set true to keep only the largest connected components in the network. +- `osm_data_object::Union{XMLDocument,Dict}`: OpenStreetMap network data parsed as either + `XMLDocument` or `Dict` object depending on the download method. *NOTE* if you pass in + a `Dict`, the object will be modified to add missing tag information. +- `network_type::Symbol=:drive`: Network type filter, pick from `:drive`, `:drive_service`, + `:walk`, `:bike`, `:all`, `:all_private`, `:none`, `:rail`, must match the network type + used to download `osm_data_object`. +- `weight_type::Symbol=:time`: Weight type for graph edges, pick from `:distance` (km), + `:time` (hours), `:lane_efficiency` (time scaled by number of lanes). +- `graph_type::Symbol=:static`: Type of `Graphs.AbstractGraph`, pick from `:static` + (`StaticDiGraph`), `:light` (`DiGraph`), `:simple_weighted` (`SimpleWeightedDiGraph`), + `:meta` (`MetaDiGraph`). +- `precompute_dijkstra_states::Bool=false`: Set true to precompute Dijkstra parent states + for every source node in the graph, *NOTE* this may take a while and may not be + possible for graphs with large amount of nodes due to memory limits. +- `largest_connected_component::Bool=true`: Set true to keep only the largest connected + components in the network. # Return -- `OSMGraph`: Container for storing OpenStreetMap node, way, relation and graph related obejcts. +- `OSMGraph`: Container for storing OpenStreetMap node-, way-, relation- and graph-related + obejcts. """ function graph_from_object(osm_data_object::Union{XMLDocument,Dict}; network_type::Symbol=:drive, @@ -353,7 +365,7 @@ function add_weights!(g::OSMGraph, weight_type::Symbol=:distance) weight = dist / maxspeed else lanes = g.ways[highway].tags["lanes"]::DEFAULT_OSM_LANES_TYPE - lane_efficiency = get(LANE_EFFICIENCY, lanes, 1.0) + lane_efficiency = get(LANE_EFFICIENCY[], lanes, 1.0) weight = dist / (maxspeed * lane_efficiency) end else diff --git a/src/parse.jl b/src/parse.jl index 1707b0b..39a1c57 100644 --- a/src/parse.jl +++ b/src/parse.jl @@ -29,8 +29,8 @@ function maxspeed(tags::AbstractDict)::DEFAULT_OSM_MAXSPEED_TYPE end else highway_type = get(tags, "highway", "other") - key = getkey(DEFAULT_MAXSPEEDS, highway_type, "other") - return U(DEFAULT_MAXSPEEDS[key]) + key = getkey(DEFAULT_MAXSPEEDS[], highway_type, "other") + return U(DEFAULT_MAXSPEEDS[][key]) end end @@ -55,8 +55,8 @@ function lanes(tags::AbstractDict)::DEFAULT_OSM_LANES_TYPE end else highway_type = get(tags, "highway", "other") - key = getkey(DEFAULT_LANES, highway_type, "other") - return U(DEFAULT_LANES[key]) + key = getkey(DEFAULT_LANES[], highway_type, "other") + return U(DEFAULT_LANES[][key]) end end @@ -217,12 +217,12 @@ function parse_osm_network_dict(osm_network_dict::AbstractDict, network_type::Sy id = way["id"] ways[id] = Way(id, nds, tags) elseif is_railway(tags) && matches_network_type(tags, network_type) - tags["rail_type"] = haskey(tags,"railway") ? tags["railway"] : "unknown" - tags["electrified"] = haskey(tags,"electrified") ? tags["electrified"] : "unknown" - tags["gauge"] = haskey(tags,"gauge") ? tags["gauge"] : nothing - tags["usage"] = haskey(tags,"usage") ? tags["usage"] : "unknown" - tags["name"] = haskey(tags,"name") ? tags["name"] : "unknown" - tags["lanes"] = haskey(tags,"tracks") ? tags["tracks"] : 1 + tags["rail_type"] = get(tags, "railway", "unknown") + tags["electrified"] = get(tags, "electrified", "unknown") + tags["gauge"] = get(tags, "gauge", nothing) + tags["usage"] = get(tags, "usage", "unknown") + tags["name"] = get(tags, "name", "unknown") + tags["lanes"] = get(tags, "tracks", 1) tags["maxspeed"] = maxspeed(tags) tags["oneway"] = is_oneway(tags) tags["reverseway"] = is_reverseway(tags) diff --git a/test/constants.jl b/test/constants.jl index 8a2d5b1..65f95e6 100644 --- a/test/constants.jl +++ b/test/constants.jl @@ -1,29 +1,81 @@ -dict = Dict( - "first" => ["single"], - "second" => ["dual1", "dual2"] -) - -single_dict = Dict("first" => ["single"]) -single_str = LightOSM.concatenate_exclusions(single_dict) -@test startswith(single_str, '[') -@test endswith(single_str, ']') -args_only = chop(single_str, head=1, tail=1) -args = split(args_only, "!~") -@test args[1] == "\"first\"" -@test args[2] == "\"single\"" - -dual_dict = Dict("second" => ["dual1", "dual2"]) -dual_str = LightOSM.concatenate_exclusions(dual_dict) -@test startswith(dual_str, '[') -@test endswith(dual_str, ']') -args_only = chop(dual_str, head=1, tail=1) -args = split(args_only, "!~") -@test args[1] == "\"second\"" -@test args[2] == "\"dual1|dual2\"" - -combined_dict = dict = Dict( - "first" => ["single"], - "second" => ["dual1", "dual2"] -) -str = LightOSM.concatenate_exclusions(combined_dict) -@test str == single_str * dual_str || str == dual_str * single_str # either order \ No newline at end of file +@testset "concatenate_exclusions tests" begin + dict = Dict( + "first" => ["single"], + "second" => ["dual1", "dual2"] + ) + + single_dict = Dict("first" => ["single"]) + single_str = LightOSM.concatenate_exclusions(single_dict) + @test startswith(single_str, '[') + @test endswith(single_str, ']') + args_only = chop(single_str, head=1, tail=1) + args = split(args_only, "!~") + @test args[1] == "\"first\"" + @test args[2] == "\"single\"" + + dual_dict = Dict("second" => ["dual1", "dual2"]) + dual_str = LightOSM.concatenate_exclusions(dual_dict) + @test startswith(dual_str, '[') + @test endswith(dual_str, ']') + args_only = chop(dual_str, head=1, tail=1) + args = split(args_only, "!~") + @test args[1] == "\"second\"" + @test args[2] == "\"dual1|dual2\"" + + combined_dict = dict = Dict( + "first" => ["single"], + "second" => ["dual1", "dual2"] + ) + str = LightOSM.concatenate_exclusions(combined_dict) + @test str == single_str * dual_str || str == dual_str * single_str # either order +end + +@testset "set_defaults tests" begin + resp = HTTP.get(TEST_OSM_URL) + data = JSON.parse(String(resp.body)) + + # Get original defaults + original_maxspeeds = deepcopy(LightOSM.DEFAULT_MAXSPEEDS[]) + original_lanes = deepcopy(LightOSM.DEFAULT_LANES[]) + + # Create graph using originals + original_g = LightOSM.graph_from_object(deepcopy(data); graph_type=:static, weight_type=:lane_efficiency) + + # New defaults + new_maxspeeds = Dict( + "motorway" => 100, + "trunk" => 100, + "primary" => 60, + "secondary" => 60, + "tertiary" => 50, + "unclassified" => 50, + "residential" => 40, + "other" => 50 + ) + new_lanes = Dict( + "motorway" => 5, + "trunk" => 4, + "primary" => 3, + "secondary" => 1, + "tertiary" => 1, + "unclassified" => 1, + "residential" => 1, + "other" => 1 + ) + LightOSM.set_defaults( + maxspeeds=new_maxspeeds, + lanes=new_lanes + ) + + # Create graph using new values + new_g = LightOSM.graph_from_object(deepcopy(data); graph_type=:static, weight_type=:lane_efficiency) + + # Test way 217499573, Chapel St with tags: + # "highway": "secondary" + # "name": "Chapel Street" + # "surface": "asphalt" + @test original_g.ways[217499573].tags["maxspeed"] == original_maxspeeds["secondary"] + @test new_g.ways[217499573].tags["maxspeed"] == new_maxspeeds["secondary"] + @test original_g.ways[217499573].tags["lanes"] == original_lanes["secondary"] + @test new_g.ways[217499573].tags["lanes"] == new_lanes["secondary"] +end diff --git a/test/runtests.jl b/test/runtests.jl index aab147b..a891df9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,6 +8,8 @@ using Test include("stub.jl") +const TEST_OSM_URL = "https://raw.githubusercontent.com/captchanjack/LightOSMFiles.jl/main/maps/south-yarra.json" + @testset "LightOSM Tests" begin @testset "Constants" begin include("constants.jl") end @testset "Utilities" begin include("utilities.jl") end @@ -18,4 +20,4 @@ include("stub.jl") @testset "Graph" begin include("graph.jl") end @testset "Traversal" begin include("traversal.jl") end @testset "Subgraph" begin include("subgraph.jl") end -end \ No newline at end of file +end diff --git a/test/traversal.jl b/test/traversal.jl index b93d7fc..d33f226 100644 --- a/test/traversal.jl +++ b/test/traversal.jl @@ -66,7 +66,7 @@ for T in (AStar, AStarVector, AStarDict) end # download graph, pick random nodes and test dijkstra and astar equality -data = HTTP.get("https://raw.githubusercontent.com/captchanjack/LightOSMFiles.jl/main/maps/south-yarra.json") +data = HTTP.get(TEST_OSM_URL) data = JSON.parse(String(data.body)) # distance weights