various gpx bits
[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 'xml/libxml'
15   require 'RMagick'
16
17   class Mercator
18     include Math
19
20     def initialize(lat, lon, degrees_per_pixel, width, height)
21       #init me with your centre lat/lon, the number of degrees per pixel and the size of your image
22       @clat = lat
23       @clon = lon
24       @degrees_per_pixel = degrees_per_pixel
25       @width = width
26       @height = height
27       @dlon = width / 2 * degrees_per_pixel
28       @dlat = height / 2 * degrees_per_pixel  * cos(@clat * PI / 180)
29
30       @tx = xsheet(@clon - @dlon)
31       @ty = ysheet(@clat - @dlat)
32
33       @bx = xsheet(@clon + @dlon)
34       @by = ysheet(@clat + @dlat)
35
36     end
37
38     #the following two functions will give you the x/y on the entire sheet
39
40     def kilometerinpixels
41       return 40008.0  / 360.0 * @degrees_per_pixel
42     end
43
44     def ysheet(lat)
45       log(tan(PI / 4 +  (lat  * PI / 180 / 2)))
46     end
47
48     def xsheet(lon)
49       lon
50     end
51
52     #and these two will give you the right points on your image. all the constants can be reduced to speed things up. FIXME
53
54     def y(lat)
55       return @height - ((ysheet(lat) - @ty) / (@by - @ty) * @height)
56     end
57
58     def x(lon)
59       return  ((xsheet(lon) - @tx) / (@bx - @tx) * @width)
60     end
61   end
62
63
64   class GPXImporter
65     # FIXME swap REXML for libXML
66     attr_reader :possible_points
67     attr_reader :actual_points
68     attr_reader :tracksegs
69
70     def initialize(filename)
71       @filename = filename
72       @possible_points = 0
73       @actual_points = 0
74       @tracksegs = 0
75     end
76
77     def points
78       file = File.new(@filename)
79       parser = REXML::Parsers::SAX2Parser.new( file )
80
81       lat = -1
82       lon = -1
83       ele = -1
84       date = Time.now();
85       gotlatlon = false
86       gotele = false
87       gotdate = false
88
89       parser.listen( :start_element,  %w{ trkpt }) do |uri,localname,qname,attributes| 
90         lat = attributes['lat'].to_f
91         lon = attributes['lon'].to_f
92         gotlatlon = true
93         @possible_points += 1
94       end
95
96       parser.listen( :characters, %w{ ele } ) do |text|
97         ele = text
98         gotele = true
99       end
100
101       parser.listen( :characters, %w{ time } ) do |text|
102         if text && text != ''
103           date = Time.parse(text)
104           gotdate = true
105         end
106       end
107
108       parser.listen( :end_element, %w{ trkseg } ) do |uri, localname, qname|
109         @tracksegs += 1
110       end
111
112       parser.listen( :end_element, %w{ trkpt } ) do |uri,localname,qname|
113         if gotlatlon && gotdate
114           ele = '0' unless gotele
115           if lat < 90 && lat > -90 && lon > -180 && lon < 180
116             @actual_points += 1
117             yield Hash['latitude' => lat,'longitude' => lon,'timestamp' => date,'altitude' => ele,'segment' => @tracksegs]
118           end
119         end
120         gotlatlon = false
121         gotele = false
122         gotdate = false
123       end
124       parser.parse
125     end
126
127     def get_picture(min_lat, min_lon, max_lat, max_lon, num_points)
128       #puts "getting picfor bbox #{min_lat},#{min_lon} - #{max_lat},#{max_lon}"
129       frames = 10
130       width = 250
131       height = 250
132       rat= Math.cos( ((max_lat + min_lat)/2.0) /  180.0 * 3.141592)
133       proj = OSM::Mercator.new((min_lat + max_lat) / 2, (max_lon + min_lon) / 2, (max_lat - min_lat) / width / rat, width, height)
134
135       images = []
136
137       frames.times do
138         gc =  Magick::Draw.new
139         gc.stroke_linejoin('miter')
140         gc.stroke('#FFFFFF')
141         gc.fill('#FFFFFF')
142         gc.rectangle(0,0,width,height)
143         gc.stroke_width(1)
144         images << gc
145       end
146
147       oldpx = 0.0
148       oldpy = 0.0
149
150       first = true
151
152       m = 0
153       mm = 0
154       points do |p|
155         px = proj.x(p['longitude'])
156         py = proj.y(p['latitude'])
157         frames.times do |n|
158           images[n].stroke_width(1)
159           images[n].stroke('#BBBBBB')
160           images[n].fill('#BBBBBB')
161         #  puts "A #{px},#{py} - #{oldpx},#{oldpy}"
162           images[n].line(px, py, oldpx, oldpy ) unless first
163         end
164         images[mm].stroke_width(3)
165         images[mm].stroke('#000000')
166         images[mm].fill('#000000')
167         images[mm].line(px, py, oldpx, oldpy ) unless first
168       #  puts "B #{px},#{py} - #{oldpx},#{oldpy}"
169         m +=1
170         if m > num_points.to_f / frames.to_f * (mm+1)
171           mm += 1
172         end
173         first = false
174         oldpy = py
175         oldpx = px
176       end
177
178       il = Magick::ImageList.new
179
180       frames.times do |n|
181         canvas = Magick::Image.new(width, height) {
182           self.background_color = 'white'
183         }
184         begin
185           images[n].draw(canvas)
186         rescue ArgumentError
187         end
188         canvas.format = 'GIF'
189         il << canvas
190       end
191
192       il.delay = 50
193       il.format = 'GIF'
194       return il.to_blob
195     end
196
197     def get_icon(min_lat, min_lon, max_lat, max_lon)
198       puts "getting icon for bbox #{min_lat},#{min_lon} - #{max_lat},#{max_lon}"
199       width = 50
200       height = 50
201       rat= Math.cos( ((max_lat + min_lat)/2.0) /  180.0 * 3.141592)
202       proj = OSM::Mercator.new((min_lat + max_lat) / 2, (max_lon + min_lon) / 2, (max_lat - min_lat) / width / rat, width, height)
203
204       images = []
205
206       gc =  Magick::Draw.new
207       gc.stroke_linejoin('miter')
208
209       oldpx = 0.0
210       oldpy = 0.0
211
212       first = true
213
214       gc.stroke_width(1)
215       gc.stroke('#000000')
216       gc.fill('#000000')
217
218       points do |p|
219         px = proj.x(p['longitude'])
220         py = proj.y(p['latitude'])
221         gc.line(px, py, oldpx, oldpy ) unless first
222        # puts "C #{px},#{py} - #{oldpx},#{oldpy}"
223         first = false
224         oldpy = py
225         oldpx = px
226       end
227
228       canvas = Magick::Image.new(width, height) {
229         self.background_color = 'white'
230       }
231       begin
232         gc.draw(canvas)
233       rescue ArgumentError
234       end
235       canvas.format = 'GIF'
236       return canvas.to_blob
237     end
238
239   end
240
241   class GeoRSS
242     def initialize(description='OpenStreetMap GPS Traces')
243       @doc = XML::Document.new
244       @doc.encoding = 'UTF-8' 
245       
246       rss = XML::Node.new 'rss'
247       @doc.root = rss
248       rss['version'] = "2.0"
249       rss['xmlns:geo'] = "http://www.w3.org/2003/01/geo/wgs84_pos#"
250       @channel = XML::Node.new 'channel'
251       rss << @channel
252       title = XML::Node.new 'title'
253       title <<  'OpenStreetMap GPS Traces'
254       @channel << title
255       description_el = XML::Node.new 'description'
256       @channel << description_el
257
258       description_el << description
259       link = XML::Node.new 'link'
260       link << 'http://www.openstreetmap.org/traces/'
261       @channel << link
262       image = XML::Node.new 'image'
263       @channel << image
264       url = XML::Node.new 'url'
265       url << 'http://www.openstreetmap.org/feeds/mag_map-rss2.0.png'
266       image << url
267       title = XML::Node.new 'title'
268       title << "OpenStreetMap"
269       image << title
270       width = XML::Node.new 'width'
271       width << '100'
272       image << width
273       height = XML::Node.new 'height'
274       height << '100'
275       image << height
276       link = XML::Node.new 'link'
277       link << 'http://www.openstreetmap.org/traces/'
278       image << link
279     end
280
281     def add(latitude=0, longitude=0, title_text='dummy title', url='http://www.example.com/', description_text='dummy description', timestamp=Time.now)
282       item = XML::Node.new 'item'
283
284       title = XML::Node.new 'title'
285       item << title
286       title << title_text
287       link = XML::Node.new 'link'
288       link << url
289       item << link
290
291       description = XML::Node.new 'description'
292       description << description_text
293       item << description
294
295       pubDate = XML::Node.new 'pubDate'
296       pubDate << timestamp.xmlschema
297       item << pubDate
298
299       lat_el = XML::Node.new 'geo:lat'
300       lat_el << latitude.to_s
301       item << lat_el
302
303       lon_el = XML::Node.new 'geo:lon'
304       lon_el << longitude.to_s
305       item << lon_el
306
307       @channel << item
308     end
309
310     def to_s
311       return @doc.to_s
312     end
313   end
314
315   class API
316     def get_xml_doc
317       doc = XML::Document.new
318       doc.encoding = 'UTF-8' 
319       root = XML::Node.new 'osm'
320       root['version'] = API_VERSION
321       root['generator'] = 'OpenStreetMap server'
322       doc.root = root
323       return doc
324     end
325   end
326 end