source: subversion/sites/rails_port/lib/osm.rb @ 4141

Last change on this file since 4141 was 4141, checked in by tomhughes, 12 years ago

Salt passwords so that two users with the same password will have
different password hashes in the database.

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