Add the author name to RSS feeds.
[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       @degrees_per_pixel = 0.0000000001 if @degrees_per_pixel < 0.0000000001
26       @width = width
27       @height = height
28       @dlon = width / 2 * @degrees_per_pixel
29       @dlat = height / 2 * @degrees_per_pixel  * cos(@clat * PI / 180)
30
31       @tx = xsheet(@clon - @dlon)
32       @ty = ysheet(@clat - @dlat)
33
34       @bx = xsheet(@clon + @dlon)
35       @by = ysheet(@clat + @dlat)
36
37     end
38
39     #the following two functions will give you the x/y on the entire sheet
40
41     def kilometerinpixels
42       return 40008.0  / 360.0 * @degrees_per_pixel
43     end
44
45     def ysheet(lat)
46       log(tan(PI / 4 +  (lat  * PI / 180 / 2)))
47     end
48
49     def xsheet(lon)
50       lon
51     end
52
53     #and these two will give you the right points on your image. all the constants can be reduced to speed things up. FIXME
54
55     def y(lat)
56       return @height - ((ysheet(lat) - @ty) / (@by - @ty) * @height)
57     end
58
59     def x(lon)
60       return  ((xsheet(lon) - @tx) / (@bx - @tx) * @width)
61     end
62   end
63
64
65   class GPXImporter
66     # FIXME swap REXML for libXML
67     attr_reader :possible_points
68     attr_reader :actual_points
69     attr_reader :tracksegs
70
71     def initialize(filename)
72       @filename = filename
73     end
74
75     def points
76       @possible_points = 0
77       @actual_points = 0
78       @tracksegs = 0
79
80       lat = -1
81       lon = -1
82       ele = -1
83       date = DateTime.now();
84       gotlatlon = false
85       gotele = false
86       gotdate = false
87
88       parser = REXML::Parsers::SAX2Parser.new(File.new(@filename))
89
90       parser.listen( :start_element,  %w{ trkpt }) do |uri,localname,qname,attributes| 
91         lat = attributes['lat'].to_f
92         lon = attributes['lon'].to_f
93         gotlatlon = true
94         @possible_points += 1
95       end
96
97       parser.listen( :characters, %w{ ele } ) do |text|
98         ele = text
99         gotele = true
100       end
101
102       parser.listen( :characters, %w{ time } ) do |text|
103         if text && text != ''
104           begin
105             date = DateTime.parse(text)
106             gotdate = true
107           rescue
108           end
109         end
110       end
111
112       parser.listen( :end_element, %w{ trkseg } ) do |uri, localname, qname|
113         @tracksegs += 1
114       end
115
116       parser.listen( :end_element, %w{ trkpt } ) do |uri,localname,qname|
117         if gotlatlon && gotdate
118           ele = '0' unless gotele
119           if lat < 90 && lat > -90 && lon > -180 && lon < 180
120             @actual_points += 1
121             yield Hash['latitude' => lat, 'longitude' => lon, 'timestamp' => date, 'altitude' => ele, 'segment' => @tracksegs]
122           end
123         end
124         gotlatlon = false
125         gotele = false
126         gotdate = false
127       end
128
129       parser.parse
130     end
131
132     def get_picture(min_lat, min_lon, max_lat, max_lon, num_points)
133       #puts "getting picfor bbox #{min_lat},#{min_lon} - #{max_lat},#{max_lon}"
134       frames = 10
135       width = 250
136       height = 250
137       rat= Math.cos( ((max_lat + min_lat)/2.0) /  180.0 * 3.141592)
138       proj = OSM::Mercator.new((min_lat + max_lat) / 2, (max_lon + min_lon) / 2, (max_lat - min_lat) / width / rat, width, height)
139
140       images = []
141
142       frames.times do
143         gc =  Magick::Draw.new
144         gc.stroke_linejoin('miter')
145         gc.stroke('#FFFFFF')
146         gc.fill('#FFFFFF')
147         gc.rectangle(0,0,width,height)
148         gc.stroke_width(1)
149         images << gc
150       end
151
152       oldpx = 0.0
153       oldpy = 0.0
154
155       first = true
156
157       m = 0
158       mm = 0
159       points do |p|
160         px = proj.x(p['longitude'])
161         py = proj.y(p['latitude'])
162         frames.times do |n|
163           images[n].stroke_width(1)
164           images[n].stroke('#BBBBBB')
165           images[n].fill('#BBBBBB')
166         #  puts "A #{px},#{py} - #{oldpx},#{oldpy}"
167           images[n].line(px, py, oldpx, oldpy ) unless first
168         end
169         images[mm].stroke_width(3)
170         images[mm].stroke('#000000')
171         images[mm].fill('#000000')
172         images[mm].line(px, py, oldpx, oldpy ) unless first
173       #  puts "B #{px},#{py} - #{oldpx},#{oldpy}"
174         m +=1
175         if m > num_points.to_f / frames.to_f * (mm+1)
176           mm += 1
177         end
178         first = false
179         oldpy = py
180         oldpx = px
181       end
182
183       il = Magick::ImageList.new
184
185       frames.times do |n|
186         canvas = Magick::Image.new(width, height) {
187           self.background_color = 'white'
188         }
189         begin
190           images[n].draw(canvas)
191         rescue ArgumentError
192         end
193         canvas.format = 'GIF'
194         il << canvas
195       end
196
197       il.delay = 50
198       il.format = 'GIF'
199       return il.to_blob
200     end
201
202     def get_icon(min_lat, min_lon, max_lat, max_lon)
203       #puts "getting icon for bbox #{min_lat},#{min_lon} - #{max_lat},#{max_lon}"
204       width = 50
205       height = 50
206       rat= Math.cos( ((max_lat + min_lat)/2.0) /  180.0 * 3.141592)
207       proj = OSM::Mercator.new((min_lat + max_lat) / 2, (max_lon + min_lon) / 2, (max_lat - min_lat) / width / rat, width, height)
208
209       images = []
210
211       gc =  Magick::Draw.new
212       gc.stroke_linejoin('miter')
213
214       oldpx = 0.0
215       oldpy = 0.0
216
217       first = true
218
219       gc.stroke_width(1)
220       gc.stroke('#000000')
221       gc.fill('#000000')
222
223       points do |p|
224         px = proj.x(p['longitude'])
225         py = proj.y(p['latitude'])
226         gc.line(px, py, oldpx, oldpy ) unless first
227        # puts "C #{px},#{py} - #{oldpx},#{oldpy}"
228         first = false
229         oldpy = py
230         oldpx = px
231       end
232
233       canvas = Magick::Image.new(width, height) {
234         self.background_color = 'white'
235       }
236       begin
237         gc.draw(canvas)
238       rescue ArgumentError
239       end
240       canvas.format = 'GIF'
241       return canvas.to_blob
242     end
243
244   end
245
246   class GreatCircle
247     include Math
248
249     # initialise with a base position
250     def initialize(lat, lon)
251       @lat = lat * PI / 180
252       @lon = lon * PI / 180
253     end
254
255     # get the distance from the base position to a given position
256     def distance(lat, lon)
257       lat = lat * PI / 180
258       lon = lon * PI / 180
259       return 6372.795 * 2 * asin(sqrt(sin((lat - @lat) / 2) ** 2 + cos(@lat) * cos(lat) * sin((lon - @lon)/2) ** 2))
260     end
261
262     # get the worst case bounds for a given radius from the base position
263     def bounds(radius)
264       latradius = 2 * asin(sqrt(sin(radius / 6372.795 / 2) ** 2))
265       lonradius = 2 * asin(sqrt(sin(radius / 6372.795 / 2) ** 2 / cos(@lat) ** 2))
266       minlat = (@lat - latradius) * 180 / PI
267       maxlat = (@lat + latradius) * 180 / PI
268       minlon = (@lon - lonradius) * 180 / PI
269       maxlon = (@lon + lonradius) * 180 / PI
270       return { :minlat => minlat, :maxlat => maxlat, :minlon => minlon, :maxlon => maxlon }
271     end
272   end
273
274   class GeoRSS
275     def initialize(feed_title='OpenStreetMap GPS Traces', feed_description='OpenStreetMap GPS Traces', feed_url='http://www.openstreetmap.org/traces/')
276       @doc = XML::Document.new
277       @doc.encoding = 'UTF-8' 
278       
279       rss = XML::Node.new 'rss'
280       @doc.root = rss
281       rss['version'] = "2.0"
282       rss['xmlns:geo'] = "http://www.w3.org/2003/01/geo/wgs84_pos#"
283       @channel = XML::Node.new 'channel'
284       rss << @channel
285       title = XML::Node.new 'title'
286       title <<  feed_title
287       @channel << title
288       description_el = XML::Node.new 'description'
289       @channel << description_el
290
291       description_el << feed_description
292       link = XML::Node.new 'link'
293       link << feed_url
294       @channel << link
295       image = XML::Node.new 'image'
296       @channel << image
297       url = XML::Node.new 'url'
298       url << 'http://www.openstreetmap.org/feeds/mag_map-rss2.0.png'
299       image << url
300       title = XML::Node.new 'title'
301       title << "OpenStreetMap"
302       image << title
303       width = XML::Node.new 'width'
304       width << '100'
305       image << width
306       height = XML::Node.new 'height'
307       height << '100'
308       image << height
309       link = XML::Node.new 'link'
310       link << feed_url
311       image << link
312     end
313
314     def add(latitude=0, longitude=0, title_text='dummy title', author_text='anonymous', url='http://www.example.com/', description_text='dummy description', timestamp=DateTime.now)
315       item = XML::Node.new 'item'
316
317       title = XML::Node.new 'title'
318       item << title
319       title << title_text
320       link = XML::Node.new 'link'
321       link << url
322       item << link
323
324       guid = XML::Node.new 'guid'
325       guid << url
326       item << guid
327
328       description = XML::Node.new 'description'
329       description << description_text
330       item << description
331
332       author = XML::Node.new 'author'
333       author << author_text
334       item << author
335
336       pubDate = XML::Node.new 'pubDate'
337       pubDate << timestamp.to_s(:rfc822)
338       item << pubDate
339
340       if latitude
341         lat_el = XML::Node.new 'geo:lat'
342         lat_el << latitude.to_s
343         item << lat_el
344       end
345
346       if longitude
347         lon_el = XML::Node.new 'geo:long'
348         lon_el << longitude.to_s
349         item << lon_el
350       end
351
352       @channel << item
353     end
354
355     def to_s
356       return @doc.to_s
357     end
358   end
359
360   class API
361     def get_xml_doc
362       doc = XML::Document.new
363       doc.encoding = 'UTF-8' 
364       root = XML::Node.new 'osm'
365       root['version'] = API_VERSION
366       root['generator'] = 'OpenStreetMap server'
367       doc.root = root
368       return doc
369     end
370   end
371
372   def self.IPLocation(ip_address)
373     Timeout::timeout(4) do
374       Net::HTTP.start('api.hostip.info') do |http|
375         country = http.get("/country.php?ip=#{ip_address}").body
376         country = "GB" if country = "UK"
377         Net::HTTP.start('ws.geonames.org') do |http|
378           xml = REXML::Document.new(http.get("/countryInfo?country=#{country}").body)
379           xml.elements.each("geonames/country") do |ele|
380             minlon = ele.get_text("bBoxWest").to_s
381             minlat = ele.get_text("bBoxSouth").to_s
382             maxlon = ele.get_text("bBoxEast").to_s
383             maxlat = ele.get_text("bBoxNorth").to_s
384             return { :minlon => minlon, :minlat => minlat, :maxlon => maxlon, :maxlat => maxlat }
385           end
386         end
387       end
388     end
389
390     return nil
391   rescue Exception
392     return nil
393   end
394 end