Update to use libxml 1.0.0 gem.
[rails.git] / lib / osm.rb
1 # The OSM module provides support functions for OSM.
2 module OSM
3
4   require 'time'
5   require 'rexml/parsers/sax2parser'
6   require 'rexml/text'
7   require 'xml/libxml'
8   require 'digest/md5'
9   require 'RMagick'
10
11   # The base class for API Errors.
12   class APIError < RuntimeError
13   end
14
15   # Raised when an API object is not found.
16   class APINotFoundError < APIError
17   end
18
19   # Raised when a precondition to an API action fails sanity check.
20   class APIPreconditionFailedError < APIError
21   end
22
23   # Raised when to delete an already-deleted object.
24   class APIAlreadyDeletedError < APIError
25   end
26
27   # Helper methods for going to/from mercator and lat/lng.
28   class Mercator
29     include Math
30
31     #init me with your bounding box and the size of your image
32     def initialize(min_lat, min_lon, max_lat, max_lon, width, height)
33       xsize = xsheet(max_lon) - xsheet(min_lon)
34       ysize = ysheet(max_lat) - ysheet(min_lat)
35       xscale = xsize / width
36       yscale = ysize / height
37       scale = [xscale, yscale].max
38
39       xpad = width * scale - xsize
40       ypad = height * scale - ysize
41
42       @width = width
43       @height = height
44
45       @tx = xsheet(min_lon) - xpad / 2
46       @ty = ysheet(min_lat) - ypad / 2
47
48       @bx = xsheet(max_lon) + xpad / 2
49       @by = ysheet(max_lat) + ypad / 2
50     end
51
52     #the following two functions will give you the x/y on the entire sheet
53
54     def ysheet(lat)
55       log(tan(PI / 4 + (lat * PI / 180 / 2))) / (PI / 180)
56     end
57
58     def xsheet(lon)
59       lon
60     end
61
62     #and these two will give you the right points on your image. all the constants can be reduced to speed things up. FIXME
63
64     def y(lat)
65       return @height - ((ysheet(lat) - @ty) / (@by - @ty) * @height)
66     end
67
68     def x(lon)
69       return  ((xsheet(lon) - @tx) / (@bx - @tx) * @width)
70     end
71   end
72
73   class GreatCircle
74     include Math
75
76     # initialise with a base position
77     def initialize(lat, lon)
78       @lat = lat * PI / 180
79       @lon = lon * PI / 180
80     end
81
82     # get the distance from the base position to a given position
83     def distance(lat, lon)
84       lat = lat * PI / 180
85       lon = lon * PI / 180
86       return 6372.795 * 2 * asin(sqrt(sin((lat - @lat) / 2) ** 2 + cos(@lat) * cos(lat) * sin((lon - @lon)/2) ** 2))
87     end
88
89     # get the worst case bounds for a given radius from the base position
90     def bounds(radius)
91       latradius = 2 * asin(sqrt(sin(radius / 6372.795 / 2) ** 2))
92       lonradius = 2 * asin(sqrt(sin(radius / 6372.795 / 2) ** 2 / cos(@lat) ** 2))
93       minlat = (@lat - latradius) * 180 / PI
94       maxlat = (@lat + latradius) * 180 / PI
95       minlon = (@lon - lonradius) * 180 / PI
96       maxlon = (@lon + lonradius) * 180 / PI
97       return { :minlat => minlat, :maxlat => maxlat, :minlon => minlon, :maxlon => maxlon }
98     end
99   end
100
101   class GeoRSS
102     def initialize(feed_title='OpenStreetMap GPS Traces', feed_description='OpenStreetMap GPS Traces', feed_url='http://www.openstreetmap.org/traces/')
103       @doc = XML::Document.new
104       @doc.encoding = XML::Encoding::UTF_8
105
106       rss = XML::Node.new 'rss'
107       @doc.root = rss
108       rss['version'] = "2.0"
109       rss['xmlns:geo'] = "http://www.w3.org/2003/01/geo/wgs84_pos#"
110       @channel = XML::Node.new 'channel'
111       rss << @channel
112       title = XML::Node.new 'title'
113       title <<  feed_title
114       @channel << title
115       description_el = XML::Node.new 'description'
116       @channel << description_el
117
118       description_el << feed_description
119       link = XML::Node.new 'link'
120       link << feed_url
121       @channel << link
122       image = XML::Node.new 'image'
123       @channel << image
124       url = XML::Node.new 'url'
125       url << 'http://www.openstreetmap.org/images/mag_map-rss2.0.png'
126       image << url
127       title = XML::Node.new 'title'
128       title << "OpenStreetMap"
129       image << title
130       width = XML::Node.new 'width'
131       width << '100'
132       image << width
133       height = XML::Node.new 'height'
134       height << '100'
135       image << height
136       link = XML::Node.new 'link'
137       link << feed_url
138       image << link
139     end
140
141     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)
142       item = XML::Node.new 'item'
143
144       title = XML::Node.new 'title'
145       item << title
146       title << title_text
147       link = XML::Node.new 'link'
148       link << url
149       item << link
150
151       guid = XML::Node.new 'guid'
152       guid << url
153       item << guid
154
155       description = XML::Node.new 'description'
156       description << description_text
157       item << description
158
159       author = XML::Node.new 'author'
160       author << author_text
161       item << author
162
163       pubDate = XML::Node.new 'pubDate'
164       pubDate << timestamp.to_s(:rfc822)
165       item << pubDate
166
167       if latitude
168         lat_el = XML::Node.new 'geo:lat'
169         lat_el << latitude.to_s
170         item << lat_el
171       end
172
173       if longitude
174         lon_el = XML::Node.new 'geo:long'
175         lon_el << longitude.to_s
176         item << lon_el
177       end
178
179       @channel << item
180     end
181
182     def to_s
183       return @doc.to_s
184     end
185   end
186
187   class API
188     def get_xml_doc
189       doc = XML::Document.new
190       doc.encoding = XML::Encoding::UTF_8
191       root = XML::Node.new 'osm'
192       root['version'] = API_VERSION
193       root['generator'] = 'OpenStreetMap server'
194       doc.root = root
195       return doc
196     end
197   end
198
199   def self.IPLocation(ip_address)
200     Timeout::timeout(4) do
201       Net::HTTP.start('api.hostip.info') do |http|
202         country = http.get("/country.php?ip=#{ip_address}").body
203         country = "GB" if country == "UK"
204         Net::HTTP.start('ws.geonames.org') do |http|
205           xml = REXML::Document.new(http.get("/countryInfo?country=#{country}").body)
206           xml.elements.each("geonames/country") do |ele|
207             minlon = ele.get_text("bBoxWest").to_s
208             minlat = ele.get_text("bBoxSouth").to_s
209             maxlon = ele.get_text("bBoxEast").to_s
210             maxlat = ele.get_text("bBoxNorth").to_s
211             return { :minlon => minlon, :minlat => minlat, :maxlon => maxlon, :maxlat => maxlat }
212           end
213         end
214       end
215     end
216
217     return nil
218   rescue Exception
219     return nil
220   end
221
222   # Construct a random token of a given length
223   def self.make_token(length = 30)
224     chars = 'abcdefghijklmnopqrtuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
225     token = ''
226
227     length.times do
228       token += chars[(rand * chars.length).to_i].chr
229     end
230
231     return token
232   end
233
234   # Return an encrypted version of a password
235   def self.encrypt_password(password, salt)
236     return Digest::MD5.hexdigest(password) if salt.nil?
237     return Digest::MD5.hexdigest(salt + password)
238   end
239
240   # Return an SQL fragment to select a given area of the globe
241   def self.sql_for_area(minlat, minlon, maxlat, maxlon, prefix = nil)
242     tilesql = QuadTile.sql_for_area(minlat, minlon, maxlat, maxlon, prefix)
243     minlat = (minlat * 10000000).round
244     minlon = (minlon * 10000000).round
245     maxlat = (maxlat * 10000000).round
246     maxlon = (maxlon * 10000000).round
247
248     return "#{tilesql} AND #{prefix}latitude BETWEEN #{minlat} AND #{maxlat} AND #{prefix}longitude BETWEEN #{minlon} AND #{maxlon}"
249   end
250
251
252 end