gpx rss and gpx cleanups
[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       frames = 10
129       width = 250
130       height = 250
131       rat= Math.cos( ((max_lat + min_lat)/2.0) /  180.0 * 3.141592)
132       proj = OSM::Mercator.new((min_lat + max_lat) / 2, (max_lon + min_lon) / 2, (max_lat - min_lat) / width / rat, width, height)
133
134       images = []
135
136       frames.times do
137         gc =  Magick::Draw.new
138         gc.stroke_linejoin('miter')
139         gc.stroke('#FFFFFF')
140         gc.fill('#FFFFFF')
141         gc.rectangle(0,0,width,height)
142         gc.stroke_width(1)
143         images << gc
144       end
145
146       oldpx = 0.0
147       oldpy = 0.0
148
149       first = true
150
151       m = 0
152       mm = 0
153       points do |p|
154         px = proj.x(p['longitude'])
155         py = proj.y(p['latitude'])
156         frames.times do |n|
157           images[n].stroke_width(1)
158           images[n].stroke('#BBBBBB')
159           images[n].fill('#BBBBBB')
160           images[n].line(px, py, oldpx, oldpy ) unless first
161         end
162         images[mm].stroke_width(3)
163         images[mm].stroke('#000000')
164         images[mm].fill('#000000')
165         images[mm].line(px, py, oldpx, oldpy ) unless first
166
167         m +=1
168         if m > num_points.to_f / frames.to_f * (mm+1)
169           mm += 1
170         end
171         first = false
172         oldpy = py
173         oldpx = px
174       end
175
176       il = Magick::ImageList.new
177
178       frames.times do |n|
179         canvas = Magick::Image.new(width, height) {
180           self.background_color = 'white'
181         }
182         begin
183           images[n].draw(canvas)
184         rescue ArgumentError
185         end
186         canvas.format = 'GIF'
187         il << canvas
188       end
189
190       il.delay = 50
191       il.format = 'GIF'
192       return il.to_blob
193     end
194
195     def get_icon(min_lat, min_lon, max_lat, max_lon)
196       width = 50
197       height = 50
198       rat= Math.cos( ((max_lat + min_lat)/2.0) /  180.0 * 3.141592)
199       proj = OSM::Mercator.new((min_lat + max_lat) / 2, (max_lon + min_lon) / 2, (max_lat - min_lat) / width / rat, width, height)
200
201       images = []
202
203       gc =  Magick::Draw.new
204       gc.stroke_linejoin('miter')
205
206       oldpx = 0.0
207       oldpy = 0.0
208
209       first = true
210
211       gc.stroke_width(1)
212       gc.stroke('#000000')
213       gc.fill('#000000')
214
215       points do |p|
216         px = proj.x(p['longitude'])
217         py = proj.y(p['latitude'])
218         gc.line(px, py, oldpx, oldpy ) unless first
219         first = false
220         oldpy = py
221         oldpx = px
222       end
223
224       canvas = Magick::Image.new(width, height) {
225         self.background_color = 'white'
226       }
227       begin
228         gc.draw(canvas)
229       rescue ArgumentError
230       end
231       canvas.format = 'GIF'
232       return canvas.to_blob
233     end
234
235   end
236
237   class GeoRSS
238     def initialize(description='OpenStreetMap GPS Traces')
239       @doc = XML::Document.new
240       @doc.encoding = 'UTF-8' 
241       
242       rss = XML::Node.new 'rss'
243       @doc.root = rss
244       rss['version'] = "2.0"
245       rss['xmlns:geo'] = "http://www.w3.org/2003/01/geo/wgs84_pos#"
246       @channel = XML::Node.new 'channel'
247       rss << @channel
248       title = XML::Node.new 'title'
249       title <<  'OpenStreetMap GPS Traces'
250       @channel << title
251       description_el = XML::Node.new 'description'
252       @channel << description_el
253
254       description_el << description
255       link = XML::Node.new 'link'
256       link << 'http://www.openstreetmap.org/traces/'
257       @channel << link
258       image = XML::Node.new 'image'
259       @channel << image
260       url = XML::Node.new 'url'
261       url << 'http://www.openstreetmap.org/feeds/mag_map-rss2.0.png'
262       image << url
263       title = XML::Node.new 'title'
264       title << "OpenStreetMap"
265       image << title
266       width = XML::Node.new 'width'
267       width << '100'
268       image << width
269       height = XML::Node.new 'height'
270       height << '100'
271       image << height
272       link = XML::Node.new 'link'
273       link << 'http://www.openstreetmap.org/traces/'
274       image << link
275     end
276
277     def add(latitude=0, longitude=0, title_text='dummy title', url='http://www.example.com/', description_text='dummy description', timestamp=Time.now)
278       item = XML::Node.new 'item'
279
280       title = XML::Node.new 'title'
281       item << title
282       title << title_text
283       link = XML::Node.new 'link'
284       link << url
285       item << link
286
287       description = XML::Node.new 'description'
288       description << description_text
289       item << description
290
291       pubDate = XML::Node.new 'pubDate'
292       pubDate << timestamp.xmlschema
293       item << pubDate
294
295       lat_el = XML::Node.new 'geo:lat'
296       lat_el << latitude.to_s
297       item << lat_el
298
299       lon_el = XML::Node.new 'geo:lon'
300       lon_el << longitude.to_s
301       item << lon_el
302
303       @channel << item
304     end
305
306     def to_s
307       return @doc.to_s
308     end
309   end
310 end