overpass_graph 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 57ae587cbde9c338d0e5b26af537aa39d6d1c38f41cf08560820cc1452b9ac90
4
+ data.tar.gz: ea7273b08f016c333f20a403949dc6a0d79d2fbacadce001afba8a7edcb58e36
5
+ SHA512:
6
+ metadata.gz: bc5f627688aebbd9fe4b6350d9af28aa2445dc25c40a6f700e2df823201ae0064ed7961163aa626da19346bdfdb22a2eecbcbf302c8ac5301ffd9194144a01ed
7
+ data.tar.gz: a22a1c1c5b5973e971fe1d6076e784511229cb02aba97a2e4e520ebe5fbcd50263efc201eb934b5d4e3aa453725da3f3bae13887b7c65a0e343d6e830f12c17a
@@ -0,0 +1,27 @@
1
+ require_relative './overpass_graph/get_roads'
2
+ require_relative './overpass_graph/create_vertex_set'
3
+ require_relative './overpass_graph/create_adjacency_hash'
4
+
5
+ module OverpassGraph
6
+
7
+ def self.graph(north, east, south, west, directed: true, filter_by_allowing: true, filtered_values:[], metric: false)
8
+ '''
9
+ Primary function for the library. The user may specify whether to filter by allowing or not, in addition to a few other options.
10
+ If they choose to filter by allowing certain road types, the get roads function will be passed an empty array for disallowed values.
11
+ If they choose to filter by disallowing certain road types, the get roads function will be passed an empty array for allowed values.
12
+ :return: adjacency map representation of a graph
13
+ '''
14
+ allowed_values = []
15
+ disallowed_values = []
16
+
17
+ filter_by_allowing ? allowed_values = filtered_values : disallowed_values = filtered_values
18
+
19
+ roads = get_roads(north, east, south, west, allowed_values, disallowed_values)
20
+
21
+ vertices = create_vertex_set(roads)
22
+
23
+ return create_adjacency_hash(roads, vertices, directed, metric)
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,82 @@
1
+ require 'haversine'
2
+
3
+ module OverpassGraph
4
+
5
+ def self.create_adjacency_hash(roads, vertices, directed, metric)
6
+ """
7
+ Creates an adjacency hash from a list of roads and set of vertices.
8
+
9
+ The graph is represented as a hash of hashes, where the keys are coordinate pairs [lat, lon] (as a list),
10
+ and the values are hashes that contain neighbor coordinate pairs as keys and edge lengths as values.
11
+ If an edge exists from a coordinate pair, x, to coordinate pair, y, of length z, then adj[x][y] will equal z.
12
+
13
+ Here's an example: { [lat1, lon1] => { [lat2, lon2] => distance1, [lat3, lon3] => distance2 } }
14
+ In this example, there is an edge from [lat1, lon1] to [lat2, lon2] of length distance1 and an edge from [lat1, lon1] to [lat3, lon3] of length distance2.
15
+
16
+ :return: adjacency hash representation of a graph
17
+ """
18
+ adj = {}
19
+
20
+ roads.each do |road|
21
+
22
+ road_nodes = road[:geometry]
23
+
24
+ start_vertex = [road_nodes[0][:lat], road_nodes[0][:lon]]
25
+ if !adj.has_key?(start_vertex)
26
+ adj[start_vertex] = {}
27
+ end
28
+
29
+ # Need to keep track of the distance travelled along road, the previous node (to increment distance)
30
+ # and the previous vertex. Upon discovering a new vertex in the iteration, an edge is created from
31
+ # the previous vertex to the discovered vertex (and if the road is two-way, from the discovered
32
+ # vertex back to the previous vertex)
33
+ distance = 0
34
+ prev_node = { coords: start_vertex, distance: distance }
35
+ prev_vertex = { coords: start_vertex, distance: distance }
36
+
37
+ (1..road_nodes.length - 1).each do |i|
38
+
39
+ current = [road_nodes[i][:lat], road_nodes[i][:lon]]
40
+
41
+ # updating distance with previous node and current node
42
+ if !metric
43
+ distance += Haversine.distance(prev_node[:coords][0], prev_node[:coords][1], current[0], current[1]).to_miles
44
+ else
45
+ distance += Haversine.distance(prev_node[:coords][0], prev_node[:coords][1], current[0], current[1]).to_km
46
+ end
47
+
48
+ # if the current node is in the set of vertices, calculate the distance between it and the previous vertex.
49
+ # Then add an edge of that length from the previous vertex to the current node. If the road is two-way, also add an edge
50
+ # from the current node back to the previous vertex.
51
+ if vertices === current
52
+
53
+ distance_between = distance - prev_vertex[:distance]
54
+
55
+ if road[:tags][:oneway] != 'yes' || !directed
56
+ if adj.has_key?(current)
57
+ adj[current][prev_vertex[:coords]] = distance_between
58
+ else
59
+ adj[current] = { prev_vertex[:coords] => distance_between }
60
+ end
61
+ end
62
+
63
+ if adj.has_key?(prev_vertex[:coords])
64
+ adj[prev_vertex[:coords]][current] = distance_between
65
+ else
66
+ adj[prev_vertex[:coords]] = { current => distance_between }
67
+ end
68
+
69
+ prev_vertex = {coords: current, distance: distance}
70
+ end
71
+
72
+ # previous node kept track of to calculate distance along the road
73
+ prev_node = {coords: current, distance: distance}
74
+ end
75
+
76
+ end
77
+
78
+ return adj
79
+
80
+ end
81
+
82
+ end
@@ -0,0 +1,46 @@
1
+ require 'set'
2
+
3
+ module OverpassGraph
4
+
5
+ def self.create_vertex_set(roads)
6
+ '''
7
+ Function to process a list of road hashes and return the set of vertex coordinates from that list.
8
+ Vertices are any coordinates that either: a) begin or end a road or b) are found within at least two roads (i.e. intersections)
9
+ :return: a set of vertices
10
+ '''
11
+
12
+ # frequency map of times a given node appears in all roads
13
+ node_count = {}
14
+
15
+ roads.each do |road|
16
+
17
+ road_nodes = road[:geometry]
18
+ start_vertex = [road_nodes[0][:lat], road_nodes[0][:lon]]
19
+ end_vertex = [road_nodes[-1][:lat], road_nodes[-1][:lon]]
20
+
21
+ # this ensures that each start and end vertex will be included in the set of returned
22
+ # vertices, because post iteration they'll both have at least 2 in node_count
23
+ if !node_count.has_key?(start_vertex)
24
+ node_count[start_vertex] = 1
25
+ end
26
+ if !node_count.has_key?(end_vertex)
27
+ node_count[end_vertex] = 1
28
+ end
29
+
30
+ # this will pick up any nodes that form intersections (i.e. are nodes in multiple roads),
31
+ # but aren't the starting or ending nodes of any road
32
+ road_nodes.each do |node|
33
+ current = [node[:lat], node[:lon]]
34
+ if !node_count.has_key?(current)
35
+ node_count[current] = 1
36
+ else
37
+ node_count[current] += 1
38
+ end
39
+ end
40
+ end
41
+
42
+ return Set.new( node_count.filter{ |node, num| num > 1 }.keys )
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,66 @@
1
+ require 'overpass_api_ruby'
2
+
3
+ module OverpassGraph
4
+
5
+ TIMEOUT = 900 # in seconds (15m)
6
+ MAXSIZE = 1_073_741_824 # about 1 GB (server may abort for queries near the uppper end of this range, especially at peak hours)
7
+
8
+ def self.get_roads(north, east, south, west, allowed_values, disallowed_values)
9
+ '''
10
+ Gets roads by querying the Overpass API.
11
+ :return: a list of hashes with information about all the roads in the bounding box
12
+ '''
13
+
14
+ if 90 < north || 90 < south || north < -90 || south < -90
15
+ raise "Latitudes in bounding boxes must be between -90.0 and 90.0"
16
+ end
17
+
18
+ if 180 < east || 180 < west || east < -180 || west < -180
19
+ raise "Longitudes in bounding boxes must be between -180.0 and 180.0"
20
+ end
21
+
22
+ if north < south
23
+ raise "Northern latitude is less than southern latitude.\nDid you mean 'overpass_graph(#{south}, #{east}, #{north}, #{west}...)"
24
+ end
25
+
26
+ if east < west
27
+ puts "OVERPASS_GRAPH WARNING: Eastern longitude is less than western longitude.\n"\
28
+ "In most cases this is not intended by the developer.\n"\
29
+ "Perhaps you meant 'overpass_graph(#{north}, #{west}, #{south}, #{east}...)'?\n"\
30
+ "Find out more here: https://dev.overpass-api.de/overpass-doc/en/full_data/bbox.html"
31
+ end
32
+
33
+ options = {
34
+ bbox: {
35
+ s: south,
36
+ n: north,
37
+ w: west,
38
+ e: east
39
+ },
40
+ timeout: TIMEOUT,
41
+ maxsize: MAXSIZE
42
+ }
43
+
44
+ allowed_string = allowed_values.map{|allowed_value| "[highway=#{allowed_value}]" }.join()
45
+ disallowed_string = disallowed_values.map{|disallowed_value| "[highway!=#{disallowed_value}]" }.join()
46
+
47
+ # query for all highways within the bounding box
48
+ overpass = OverpassAPI::QL.new(options)
49
+ query = "way[highway]#{allowed_string}#{disallowed_string};(._;>;);out geom;"
50
+
51
+ begin
52
+ response = overpass.query(query)
53
+ rescue
54
+
55
+ # try again if first request is denied, if second request is denied, throw the exception
56
+ response = overpass.query(query)
57
+
58
+ end
59
+
60
+ # filter out all partial roads and return the filtered hash
61
+ elements = response[:elements]
62
+ return elements.filter { |element| element[:type] == "way" }
63
+
64
+ end
65
+
66
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: overpass_graph
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sam Lawhon
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-10-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: overpass-api-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.3.1
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: 1.0.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 0.3.1
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: 1.0.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: haversine
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 0.3.2
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: 1.0.0
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 0.3.2
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: 1.0.0
53
+ - !ruby/object:Gem::Dependency
54
+ name: rspec
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '3.9'
60
+ type: :development
61
+ prerelease: false
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '3.9'
67
+ description: Lightweight gem to create weighted, directed graphs of streets in a given
68
+ bounding box. The library builds graphs using Open Street Map data queried through
69
+ the Overpass API.
70
+ email: samlawhon1@gmail.com
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - lib/overpass_graph.rb
76
+ - lib/overpass_graph/create_adjacency_hash.rb
77
+ - lib/overpass_graph/create_vertex_set.rb
78
+ - lib/overpass_graph/get_roads.rb
79
+ homepage: https://github.com/samlawhon/overpass-graph
80
+ licenses:
81
+ - MIT
82
+ metadata: {}
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubygems_version: 3.0.3
99
+ signing_key:
100
+ specification_version: 4
101
+ summary: Creates a weighted, directed graph from Open Street Map data
102
+ test_files: []