lots of rails gpx stuff
[rails.git] / lib / osm.rb
1 module OSM
2
3   # This piece of magic reads a GPX with SAX and spits out
4   # lat/lng and stuff
5   #
6   # This would print every latitude value:
7   #
8   # gpx = OSM:GPXImporter.new('somefile.gpx')
9   # gpx.points {|p| puts p['latitude']}
10
11   require 'time'
12   require 'rexml/parsers/sax2parser'
13   require 'rexml/text'
14   require 'RMagick'
15
16   class Mercator
17     include Math
18
19     def initialize(lat, lon, degrees_per_pixel, width, height)
20       #init me with your centre lat/lon, the number of degrees per pixel and the size of your image
21       @clat = lat
22       @clon = lon
23       @degrees_per_pixel = degrees_per_pixel
24       @width = width
25       @height = height
26       @dlon = width / 2 * degrees_per_pixel
27       @dlat = height / 2 * degrees_per_pixel  * cos(@clat * PI / 180)
28
29       @tx = xsheet(@clon - @dlon)
30       @ty = ysheet(@clat - @dlat)
31
32       @bx = xsheet(@clon + @dlon)
33       @by = ysheet(@clat + @dlat)
34
35     end
36
37     #the following two functions will give you the x/y on the entire sheet
38
39     def kilometerinpixels
40       return 40008.0  / 360.0 * @degrees_per_pixel
41     end
42
43     def ysheet(lat)
44       log(tan(PI / 4 +  (lat  * PI / 180 / 2)))
45     end
46
47     def xsheet(lon)
48       lon
49     end
50
51     #and these two will give you the right points on your image. all the constants can be reduced to speed things up. FIXME
52
53     def y(lat)
54       return @height - ((ysheet(lat) - @ty) / (@by - @ty) * @height)
55     end
56
57     def x(lon)
58       return  ((xsheet(lon) - @tx) / (@bx - @tx) * @width)
59     end
60   end
61
62
63   class GPXImporter
64     attr_reader :possible_points
65     attr_reader :actual_points
66     attr_reader :tracksegs
67
68     def initialize(filename)
69       @filename = filename
70       @possible_points = 0
71       @actual_points = 0
72       @tracksegs = 0
73     end
74
75     def points
76       file = File.new(@filename)
77       parser = REXML::Parsers::SAX2Parser.new( file )
78
79       lat = -1
80       lon = -1
81       ele = -1
82       date = Time.now();
83       gotlatlon = false
84       gotele = false
85       gotdate = false
86
87       parser.listen( :start_element,  %w{ trkpt }) do |uri,localname,qname,attributes| 
88         lat = attributes['lat'].to_f
89         lon = attributes['lon'].to_f
90         gotlatlon = true
91         @possible_points += 1
92       end
93
94       parser.listen( :characters, %w{ ele } ) do |text|
95         ele = text
96         gotele = true
97       end
98
99       parser.listen( :characters, %w{ time } ) do |text|
100         if text && text != ''
101           date = Time.parse(text)
102           gotdate = true
103         end
104       end
105
106       parser.listen( :end_element, %w{ trkseg } ) do |uri, localname, qname|
107         @tracksegs += 1
108       end
109
110       parser.listen( :end_element, %w{ trkpt } ) do |uri,localname,qname|
111         if gotlatlon && gotdate
112           ele = '0' unless gotele
113           if lat < 90 && lat > -90 && lon > -180 && lon < 180
114             @actual_points += 1
115             yield Hash['latitude' => lat,'longitude' => lon,'timestamp' => date,'altitude' => ele,'segment' => @tracksegs]
116           end
117         end
118         gotlatlon = false
119         gotele = false
120         gotdate = false
121       end
122       parser.parse
123     end
124
125     def get_picture(min_lat, min_lon, max_lat, max_lon, num_points)
126       frames = 10
127       width = 250
128       height = 250
129       rat= Math.cos( ((max_lat + min_lat)/2.0) /  180.0 * 3.141592)
130       proj = OSM::Mercator.new((min_lat + max_lat) / 2, (max_lon + min_lon) / 2, (max_lat - min_lat) / width / rat, width, height)
131
132       images = []
133
134       frames.times do
135         gc =  Magick::Draw.new
136         gc.stroke_linejoin('miter')
137         gc.stroke('#FFFFFF')
138         gc.fill('#FFFFFF')
139         gc.rectangle(0,0,width,height)
140         gc.stroke_width(1)
141         images << gc
142       end
143
144       oldpx = 0.0
145       oldpy = 0.0
146
147       first = true
148
149       m = 0
150       mm = 0
151       points do |p|
152         px = proj.x(p['longitude'])
153         py = proj.y(p['latitude'])
154         frames.times do |n|
155           images[n].stroke_width(1)
156           images[n].stroke('#BBBBBB')
157           images[n].fill('#BBBBBB')
158           images[n].line(px, py, oldpx, oldpy ) unless first
159         end
160         images[mm].stroke_width(3)
161         images[mm].stroke('#000000')
162         images[mm].fill('#000000')
163         images[mm].line(px, py, oldpx, oldpy ) unless first
164
165         m +=1
166         if m > num_points.to_f / frames.to_f * (mm+1)
167           mm += 1
168         end
169         first = false
170         oldpy = py
171         oldpx = px
172       end
173
174       il = Magick::ImageList.new
175
176       frames.times do |n|
177         canvas = Magick::Image.new(width, height) {
178           self.background_color = 'white'
179         }
180         begin
181           images[n].draw(canvas)
182         rescue ArgumentError
183         end
184         canvas.format = 'GIF'
185         il << canvas
186       end
187
188       il.delay = 50
189       il.format = 'GIF'
190       return il.to_blob
191     end
192
193     def get_icon(min_lat, min_lon, max_lat, max_lon)
194       width = 50
195       height = 50
196       rat= Math.cos( ((max_lat + min_lat)/2.0) /  180.0 * 3.141592)
197       proj = OSM::Mercator.new((min_lat + max_lat) / 2, (max_lon + min_lon) / 2, (max_lat - min_lat) / width / rat, width, height)
198
199       images = []
200
201       gc =  Magick::Draw.new
202       gc.stroke_linejoin('miter')
203
204       oldpx = 0.0
205       oldpy = 0.0
206
207       first = true
208
209       gc.stroke_width(1)
210       gc.stroke('#000000')
211       gc.fill('#000000')
212
213       points do |p|
214         px = proj.x(p['longitude'])
215         py = proj.y(p['latitude'])
216         gc.line(px, py, oldpx, oldpy ) unless first
217         first = false
218         oldpy = py
219         oldpx = px
220       end
221
222       canvas = Magick::Image.new(width, height) {
223         self.background_color = 'white'
224       }
225       begin
226         gc.draw(canvas)
227       rescue ArgumentError
228       end
229       canvas.format = 'GIF'
230       return canvas.to_blob
231     end
232
233   end
234 end