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.
- checksums.yaml +7 -0
- data/lib/overpass_graph.rb +27 -0
- data/lib/overpass_graph/create_adjacency_hash.rb +82 -0
- data/lib/overpass_graph/create_vertex_set.rb +46 -0
- data/lib/overpass_graph/get_roads.rb +66 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -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: []
|