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