overpass_graph 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []