mormon 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ .rvmrc
2
+ cache
3
+ lib/mormon/cache
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mormon.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ mormon (0.0.1)
5
+ nokogiri
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ columnize (0.3.6)
11
+ debugger (1.2.0)
12
+ columnize (>= 0.3.1)
13
+ debugger-linecache (~> 1.1.1)
14
+ debugger-ruby_core_source (~> 1.1.3)
15
+ debugger-linecache (1.1.2)
16
+ debugger-ruby_core_source (>= 1.1.1)
17
+ debugger-ruby_core_source (1.1.3)
18
+ diff-lcs (1.1.3)
19
+ nokogiri (1.5.5)
20
+ rspec (2.11.0)
21
+ rspec-core (~> 2.11.0)
22
+ rspec-expectations (~> 2.11.0)
23
+ rspec-mocks (~> 2.11.0)
24
+ rspec-core (2.11.1)
25
+ rspec-expectations (2.11.3)
26
+ diff-lcs (~> 1.1.3)
27
+ rspec-mocks (2.11.2)
28
+
29
+ PLATFORMS
30
+ ruby
31
+
32
+ DEPENDENCIES
33
+ debugger
34
+ mormon!
35
+ rspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Geronimo Diaz
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,20 @@
1
+ Mormon
2
+ ======
3
+
4
+ OSM Routing in Ruby, based on pyroutelib2
5
+
6
+ Usage
7
+ =====
8
+
9
+ osm_loader = Mormon::OSM::Loader.new "path/to/file.osm"
10
+ osm_router = Mormon::OSM::Router.new osm_loader
11
+ osm_router.find_route node_start, node_end, transport
12
+
13
+ - Notes:
14
+ - trasnports: must be one in [:cycle, :car, :train, :foot, :horse]
15
+ - node_start and node_end must be node ids
16
+
17
+ License
18
+ =======
19
+
20
+ - WTFYW License
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,229 @@
1
+ require 'nokogiri'
2
+
3
+ module Mormon
4
+ module OSM
5
+ class Loader
6
+ attr_reader :options, :routing, :nodes, :ways, :tiles, :routeable_nodes, :route_types
7
+
8
+ def initialize(filename, options = {})
9
+ @options = options
10
+
11
+ @tiles = {}
12
+ @nodes = {}
13
+ @ways = {}
14
+
15
+ @routing = {}
16
+ @routeable_nodes = {}
17
+ @route_types = [:cycle, :car, :train, :foot, :horse]
18
+
19
+ @route_types.each do |type|
20
+ @routing[type] = {}
21
+ @routeable_nodes[type] = {}
22
+ end
23
+
24
+ # @tilename = Mormon::Tile::Name.new
25
+ # @tiledata = Mormon::Tile::Data.new
26
+
27
+ parse filename
28
+ end
29
+
30
+ def parse(filename)
31
+ puts "Loading %s.." % filename
32
+
33
+ if !File.exists?(filename)
34
+ print "No such data file %s" % filename
35
+ return false
36
+ end
37
+
38
+ osm = Nokogiri::XML(File.open(filename))
39
+
40
+ load_nodes osm
41
+ load_ways osm
42
+ end
43
+
44
+ def report
45
+ report = "Loaded %d nodes,\n" % @nodes.keys.size
46
+ report += "%d ways, and...\n" % @ways.keys.size
47
+
48
+ @route_types.each do |type|
49
+ report += " %d %s routes\n" % [@routing[type].keys.size, type]
50
+ end
51
+
52
+ report
53
+ end
54
+
55
+ private
56
+
57
+ def load_nodes(nokosm)
58
+ nokosm.css('node').each do |node|
59
+ node_id = node[:id]
60
+
61
+ @nodes[node_id] = {
62
+ lat: node[:lat].to_f,
63
+ lon: node[:lon].to_f,
64
+ tags: {}
65
+ }
66
+
67
+ node.css('tag').each do |t|
68
+ k,v = t[:k].to_sym, t[:v]
69
+ @nodes[node_id][:tags][k] = v unless useless_tags.include?(k)
70
+ end
71
+ end
72
+ end
73
+
74
+ def load_ways(nokosm)
75
+ nokosm.css('way').each do |way|
76
+ way_id = way[:id]
77
+
78
+ @ways[way_id] = {
79
+ nodes: way.css('nd').map { |nd| nd[:ref] },
80
+ tags: {}
81
+ }
82
+
83
+ way.css('tag').each do |t|
84
+ k,v = t[:k].to_sym, t[:v]
85
+ @ways[way_id][:tags][k] = v unless useless_tags.include?(k)
86
+ end
87
+
88
+ store_way @ways[way_id]
89
+ end
90
+ end
91
+
92
+ def useless_tags
93
+ [:created_by]
94
+ end
95
+
96
+ def way_access(highway, railway)
97
+ access = {}
98
+ access[:cycle] = [:primary, :secondary, :tertiary, :unclassified, :minor, :cycleway,
99
+ :residential, :track, :service].include? highway.to_sym
100
+
101
+ access[:car] = [:motorway, :trunk, :primary, :secondary, :tertiary,
102
+ :unclassified, :minor, :residential, :service].include? highway.to_sym
103
+
104
+ access[:train] = [:rail, :light_rail, :subway].include? railway.to_sym
105
+ access[:foot] = access[:cycle] || [:footway, :steps].include?(highway.to_sym)
106
+ access[:horse] = [:track, :unclassified, :bridleway].include? highway.to_sym
107
+ access
108
+ end
109
+
110
+ def store_way(way)
111
+ tags = way[:tags]
112
+
113
+ highway = equivalent tags.fetch(:highway, "")
114
+ railway = equivalent tags.fetch(:railway, "")
115
+ oneway = tags.fetch(:oneway, "")
116
+ reversible = !['yes','true','1'].include?(oneway)
117
+
118
+ access = way_access highway, railway
119
+
120
+ # Store routing information
121
+ last = -1
122
+ way[:nodes].each do |node|
123
+ if last != -1
124
+ @route_types.each do |route_type|
125
+ if access[route_type]
126
+ weight = Mormon::Weight.get route_type, highway.to_sym
127
+ add_link(last, node, route_type, weight)
128
+ add_link(node, last, route_type, weight) if reversible || route_type == :foot
129
+ end
130
+ end
131
+ end
132
+ last = node
133
+ end
134
+ end
135
+
136
+ def routeable_from(node, route_type)
137
+ @routeable_nodes[route_type][node] = 1
138
+ end
139
+
140
+ def add_link(fr, to, route_type, weight = 1)
141
+ routeable_from fr, route_type
142
+ return if @routing[route_type][fr].keys.include?(to)
143
+ @routing[route_type][fr][to] = weight
144
+ rescue
145
+ @routing[route_type][fr] = { to => weight }
146
+ end
147
+
148
+ def way_type(tags)
149
+ # Look for a variety of tags (priority order - first one found is used)
150
+ [:highway, :railway, :waterway, :natural].each do |type|
151
+ value = tags.fetch(type, '')
152
+ return equivalent(value) if value
153
+ end
154
+ nil
155
+ end
156
+
157
+ def equivalent(tag)
158
+ {
159
+ primary_link: "primary",
160
+ trunk: "primary",
161
+ trunk_link: "primary",
162
+ secondary_link: "secondary",
163
+ tertiary: "secondary",
164
+ tertiary_link: "secondary",
165
+ residential: "unclassified",
166
+ minor: "unclassified",
167
+ steps: "footway",
168
+ driveway: "service",
169
+ pedestrian: "footway",
170
+ bridleway: "cycleway",
171
+ track: "cycleway",
172
+ arcade: "footway",
173
+ canal: "river",
174
+ riverbank: "river",
175
+ lake: "river",
176
+ light_rail: "railway"
177
+ }[tag.to_sym] || tag
178
+ end
179
+
180
+ # def find_node(lat, lon, route_type)
181
+ # # find the nearest node that can be the start of a route
182
+ # load_area(lat, lon)
183
+
184
+ # max_dist = 1E+20
185
+ # node_found = nil
186
+ # pos_found = nil
187
+
188
+ # rnodes.values.each do |node_id, pos|
189
+ # dy = pos[0] - lat
190
+ # dx = pos[1] - lon
191
+ # dist = dx * dx + dy * dy
192
+
193
+ # if dist < maxDist
194
+ # max_dist = dist
195
+ # node_found = node_id
196
+ # pos_found = pos
197
+ # end
198
+
199
+ # node_found
200
+ # end
201
+ # end
202
+
203
+ # @unused: load the specific area instead an osm file
204
+ # def load_area(lat, lon)
205
+ # if options[:file]
206
+ # puts "The %s file was already loaded" % options[:file]
207
+ # return
208
+ # end
209
+
210
+ # # Download data in the vicinity of a lat/long
211
+ # z = Mormon::Tile::Data.download_level
212
+ # (x,y) = @tilename.xy(lat, lon, z)
213
+
214
+ # tile_id = '%d,%d' % [x, y]
215
+
216
+ # return if @tiles[tile_id]
217
+
218
+ # @tiles[tile_id] = true
219
+
220
+ # filename = @tiledata.get_osm(z, x, y)
221
+ # # print "Loading %d,%d at z%d from %s" % (x,y,z,filename)
222
+
223
+ # parse filename
224
+ # end
225
+
226
+ end
227
+ end
228
+ end
229
+
@@ -0,0 +1,107 @@
1
+ module Mormon
2
+ module OSM
3
+ class Router
4
+ # attr_reader :data
5
+
6
+ def initialize(loader)
7
+ @loader = loader
8
+ end
9
+
10
+ # Calculate distance between two nodes via pitagoras c**2 = a**2 + b**2
11
+ def distance(n1, n2)
12
+ node1, node2 = @loader.nodes[n1.to_s], @loader.nodes[n2.to_s]
13
+
14
+ lat1, lon1 = node1[:lat], node1[:lon]
15
+ lat2, lon2 = node2[:lat], node2[:lon]
16
+
17
+ # TODO: projection issues
18
+ dlat = lat2 - lat1
19
+ dlon = lon2 - lon1
20
+
21
+ Math.sqrt dlat**2 + dlon**2
22
+ end
23
+
24
+ def find_route(node_start, node_end, transport)
25
+ result, nodes = route(node_start, node_end, transport)
26
+
27
+ return [result,[]] if result != 'success'
28
+
29
+ nodes.map! do |node|
30
+ data = @loader.nodes[node.to_s]
31
+ [data[:lat], data[:lon]]
32
+ end
33
+
34
+ [result, nodes]
35
+ end
36
+
37
+ private
38
+
39
+ def route(node_start, node_end, transport)
40
+ return "no_such_transport" unless @loader.routing[transport]
41
+ return "no_such_node" unless @loader.routing[transport][node_start.to_s]
42
+
43
+ @_end = node_end
44
+ @queue = []
45
+
46
+ # Start by queueing all outbound links from the start node
47
+ @loader.routing[transport][node_start.to_s].each do |neighbor, weight|
48
+ add_to_queue(node_start, neighbor, { node_end: nil, distance: 0, nodes: [node_start] }, weight)
49
+ end
50
+
51
+ closed = [node_start]
52
+
53
+ # Limit for how long it will search
54
+ (0..10000).each do
55
+ next_item = @queue.shift
56
+ return "no_route" unless next_item
57
+
58
+ x = next_item[:node_end]
59
+ next if closed.include?(x)
60
+
61
+ # Found the end node - success
62
+ return ['success', next_item[:nodes]] if x == node_end
63
+
64
+ closed << x
65
+
66
+ @loader.routing[transport][x.to_s].each do |node, weight|
67
+ add_to_queue(x, node, next_item, weight) unless closed.include?(node)
68
+ end if @loader.routing[transport][x.to_s]
69
+ end
70
+
71
+ 'gave_up'
72
+ end
73
+
74
+ def add_to_queue(node_start, node_end, current_queue, weight = 1)
75
+ # Add another potential route to the queue
76
+
77
+ node_start = node_start.to_i
78
+ node_end = node_end.to_i
79
+
80
+ # if already in queue
81
+ @queue.each { |other| return if other[:node_end] == node_end }
82
+
83
+ distance = distance(node_start, node_end)
84
+
85
+ return if weight.zero?
86
+ distance = distance / weight
87
+
88
+ # Create a hash for all the route's attributes
89
+ current_distance = current_queue[:distance]
90
+ nodes = current_queue[:nodes].dup
91
+ nodes << node_end
92
+
93
+ queue_item = {
94
+ distance: current_distance + distance,
95
+ max_distance: current_distance + distance(node_end, @_end),
96
+ nodes: nodes,
97
+ node_end: node_end
98
+ }
99
+
100
+ # Try to insert, keeping the queue ordered by decreasing worst-case distance
101
+ found = @queue.find { |other| other[:max_distance] > queue_item[:max_distance] }
102
+ found ? @queue.insert(@queue.index(found), queue_item) : @queue << queue_item
103
+ end
104
+
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,67 @@
1
+ require 'open-uri'
2
+ require 'fileutils'
3
+
4
+ module Mormon
5
+ module Tile
6
+ class Data
7
+ TILE_DIR = 'cache/%d/%d/%d'
8
+ TILE_URL = 'http://dev.openstreetmap.org/~ojw/api/?/map/%d/%d/%d'
9
+
10
+ attr_reader :options
11
+
12
+ def initialize(options = {})
13
+ @options = options
14
+ end
15
+
16
+ def self.download_level
17
+ # All primary downloads are done at a particular zoom level
18
+ 15
19
+ end
20
+
21
+ def get_osm(z, x, y)
22
+ # Download OSM data for the region covering a slippy-map tile
23
+ if x < 0 or y < 0 or z < 0 or z > 25
24
+ puts "Disallowed %d,%d at %d" % [x, y, z]
25
+ return
26
+ end
27
+
28
+ directory = TILE_DIR % [z,x,y]
29
+ FileUtils.mkdir_p(directory) unless Dir.exists?(directory)
30
+
31
+ if z == Data.download_level
32
+ url = TILE_URL % [z,x,y]
33
+ filename = '%s/data.osm' % directory
34
+
35
+ # puts "URL: %s" % url
36
+ # puts "filename: %s" % filename
37
+
38
+ # download the data
39
+ if options[:reset_cache] || !File.exists?(filename)
40
+ begin
41
+ open(url) do |content|
42
+ File.new(filename) { |f| f.write content }
43
+ end
44
+ filename
45
+ rescue OpenURI::HTTPError
46
+ "Tile not found in #{url}"
47
+ end
48
+ end
49
+
50
+ elsif z > Data.download_level
51
+ # use larger tile
52
+ while z > Data.download_level
53
+ z = z - 1
54
+ x = (x / 2).to_i
55
+ y = (y / 2).to_i
56
+ end
57
+ get_osm z, x, y
58
+ end
59
+
60
+ end
61
+
62
+ end
63
+ end
64
+ end
65
+
66
+ # tile = Mormon::Tile::Data.new :reset_cache => true
67
+ # puts tile.get_osm(15, 16218, 10741)
@@ -0,0 +1,112 @@
1
+ module Mormon
2
+ module Tile
3
+ class Name
4
+
5
+ # "http://cassini.toolserver.org:8080/"
6
+ # "http://a.tile.openstreetmap.org/"
7
+ # "http://toolserver.org/~cmarqu/hill/",
8
+ # "http://tah.openstreetmap.org/Tiles/tile/"
9
+ LAYERS_URL = {
10
+ tah: "http://a.tile.openstreetmap.org/",
11
+ oam: "http://oam1.hypercube.telascience.org/tiles/1.0.0/openaerialmap-900913/",
12
+ mapnik: "http://tile.openstreetmap.org/mapnik/"
13
+ }
14
+
15
+ def edges(x, y, z)
16
+ lat1, lat2 = lat_edges(y, z)
17
+ lon1, lon2 = lon_edges(x, z)
18
+ [lat2, lon1, lat1, lon2] # S,W,N,E
19
+ end
20
+
21
+ def px_size
22
+ 256
23
+ end
24
+
25
+ def layer_ext(layer)
26
+ layer == 'oam' ? 'jpg' : 'png'
27
+ end
28
+
29
+ def layer_base(layer)
30
+ LAYERS_URL[layer]
31
+ end
32
+
33
+ def url(x, y, z, layer = :mapnik)
34
+ "%s%d/%d/%d.%s" % [layer_base(layer), z, x, y, layer_ext(layer)]
35
+ end
36
+
37
+ def xy(lat, lon, z)
38
+ x, y = latlon_2_xy(lat, lon, z)
39
+ [x.to_i, y.to_i]
40
+ end
41
+
42
+ def xy_2_latlon(x, y, z)
43
+ n = num_tiles(z)
44
+ rel_y = y / n
45
+ lat = mercator_to_lat(Math::PI * (1 - 2 * rel_y))
46
+ lon = -180.0 + 360.0 * x / n
47
+ [lat, lon]
48
+ end
49
+
50
+
51
+ private
52
+
53
+ def num_tiles(z)
54
+ 2 ** z.to_f
55
+ end
56
+
57
+ def sec(x)
58
+ 1 / Math.cos(x)
59
+ end
60
+
61
+ def latlon_2_relative_xy(lat,lon)
62
+ x = (lon + 180) / 360
63
+ y = (1 - Math.log(Math.tan(to_radians(lat)) + sec(to_radians(lat))) / Math::PI) / 2
64
+ [x,y]
65
+ end
66
+
67
+ def to_radians(degrees)
68
+ degrees * Math::PI / 180
69
+ end
70
+
71
+ def to_degrees(radians)
72
+ radians * 180 / Math::PI
73
+ end
74
+
75
+ def latlon_2_xy(lat, lon, z)
76
+ n = num_tiles(z)
77
+ x, y = latlon_2_relative_xy(lat, lon)
78
+ [n * x, n * y]
79
+ end
80
+
81
+ def mercator_to_lat(mercator_y)
82
+ to_degrees Math.atan(Math.sinh(mercator_y))
83
+ end
84
+
85
+ def lat_edges(y, z)
86
+ n = num_tiles(z)
87
+ unit = 1 / n
88
+ rel_y1 = y * unit
89
+ rel_y2 = rel_y1 + unit
90
+ lat1 = mercator_to_lat(Math::PI * (1 - 2 * rel_y1))
91
+ lat2 = mercator_to_lat(Math::PI * (1 - 2 * rel_y2))
92
+ [lat1, lat2]
93
+ end
94
+
95
+ def lon_edges(x, z)
96
+ n = num_tiles(z)
97
+ unit = 360 / n
98
+ lon1 = -180 + x * unit
99
+ lon2 = lon1 + unit
100
+ [lon1, lon2]
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ # tile = Mormon::Tile::Name.new
107
+
108
+ # (0..16).each do |z|
109
+ # x, y = tile.xy 51.50610, -0.119888, z
110
+ # s, w, n, e = tile.edges(x, y, z)
111
+ # # puts tile.url(x,y,z, :tah)
112
+ # end
@@ -0,0 +1,3 @@
1
+ module Mormon
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,32 @@
1
+ module Mormon
2
+ class Weight
3
+ # Represents an hipotetical velocity of the transport in the way type
4
+ @weightings = {
5
+ motorway: { car: 10 },
6
+ trunk: { car: 10, cycle: 0.05 },
7
+ primary: { cycle: 0.3, car: 2, foot: 1, horse: 0.1 },
8
+ secondary: { cycle: 1, car: 1.5, foot: 1, horse: 0.2 },
9
+ tertiary: { cycle: 1, car: 1, foot: 1, horse: 0.3 },
10
+ unclassified: { cycle: 1, car: 1, foot: 1, horse: 1 },
11
+ minor: { cycle: 1, car: 1, foot: 1, horse: 1 },
12
+ cycleway: { cycle: 3, foot: 0.2 },
13
+ residential: { cycle: 3, car: 0.7, foot: 1, horse: 1 },
14
+ track: { cycle: 1, car: 1, foot: 1, horse: 1, mtb: 3 },
15
+ service: { cycle: 1, car: 1, foot: 1, horse: 1 },
16
+ bridleway: { cycle: 0.8, foot: 1, horse: 10, mtb: 3 },
17
+ footway: { cycle: 0.2, foot: 1 },
18
+ steps: { foot: 1, cycle: 0.3 },
19
+ rail: { train: 1 },
20
+ light_rail: { train: 1 },
21
+ subway: { train: 1 }
22
+ }
23
+
24
+ def self.weightings
25
+ @weightings
26
+ end
27
+
28
+ def self.get(transport, way_type)
29
+ @weightings[way_type] && @weightings[way_type][transport]
30
+ end
31
+ end
32
+ end
data/lib/mormon.rb ADDED
@@ -0,0 +1,7 @@
1
+ require "mormon/version"
2
+ require "mormon/weight"
3
+ require "mormon/tile_name"
4
+ require "mormon/tile_data"
5
+ require "mormon/osm_loader"
6
+ require "mormon/osm_router"
7
+
data/mormon.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mormon/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "mormon"
8
+ gem.version = Mormon::VERSION
9
+ gem.authors = ["Geronimo Diaz"]
10
+ gem.email = ["geronimod@gmail.com"]
11
+ gem.description = %q{ OSM Router }
12
+ gem.summary = %q{ OSM Routing with some extra features: reset tiles cache and random routes. It's based on Pyroute library. }
13
+ gem.homepage = "https://github.com/geronimod/mormon"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.rubyforge_project = "mormon"
21
+
22
+ gem.add_dependency "nokogiri"
23
+ gem.add_development_dependency "rspec"
24
+ gem.add_development_dependency "debugger"
25
+ end