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

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

QuadTile? infrastructure.

File size: 11.4 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/images/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
413
414  # Return an SQL fragment to select a given area of the globe
415  def self.sql_for_area(minlat, minlon, maxlat, maxlon)
416    tilesql = QuadTile.sql_for_area(minlat, minlon, maxlat, maxlon)
417    minlat = (minlat * 1000000).round
418    minlon = (minlon * 1000000).round
419    maxlat = (maxlat * 1000000).round
420    maxlon = (maxlon * 1000000).round
421
422    return "#{tilesql} AND latitude BETWEEN #{minlat} AND #{maxlat} AND longitude BETWEEN #{minlon} AND #{maxlon}"
423  end
424end
Note: See TracBrowser for help on using the repository browser.