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

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

Clear gotele and gotdate when we see the start of a trkpt element to
avoid inheriting previous data.

File size: 11.2 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        gotele = false
96        gotdate = false
97        @possible_points += 1
98      end
99
100      parser.listen( :characters, %w{ ele } ) do |text|
101        ele = text
102        gotele = true
103      end
104
105      parser.listen( :characters, %w{ time } ) do |text|
106        if text && text != ''
107          begin
108            date = DateTime.parse(text)
109            gotdate = true
110          rescue
111          end
112        end
113      end
114
115      parser.listen( :end_element, %w{ trkseg } ) do |uri, localname, qname|
116        @tracksegs += 1
117      end
118
119      parser.listen( :end_element, %w{ trkpt } ) do |uri,localname,qname|
120        if gotlatlon && gotdate
121          ele = '0' unless gotele
122          if lat < 90 && lat > -90 && lon > -180 && lon < 180
123            @actual_points += 1
124            yield Hash['latitude' => lat, 'longitude' => lon, 'timestamp' => date, 'altitude' => ele, 'segment' => @tracksegs]
125          end
126        end
127        gotlatlon = false
128        gotele = false
129        gotdate = false
130      end
131
132      parser.parse
133    end
134
135    def get_picture(min_lat, min_lon, max_lat, max_lon, num_points)
136      #puts "getting picfor bbox #{min_lat},#{min_lon} - #{max_lat},#{max_lon}"
137      frames = 10
138      width = 250
139      height = 250
140      rat= Math.cos( ((max_lat + min_lat)/2.0) /  180.0 * 3.141592)
141      proj = OSM::Mercator.new((min_lat + max_lat) / 2, (max_lon + min_lon) / 2, (max_lat - min_lat) / width / rat, width, height)
142
143      linegc = Magick::Draw.new
144      linegc.stroke_linejoin('miter')
145      linegc.stroke_width(1)
146      linegc.stroke('#BBBBBB')
147      linegc.fill('#BBBBBB')
148
149      highlightgc = Magick::Draw.new
150      highlightgc.stroke_linejoin('miter')
151      highlightgc.stroke_width(3)
152      highlightgc.stroke('#000000')
153      highlightgc.fill('#000000')
154
155      images = []
156
157      frames.times do
158        image = Magick::Image.new(width, height) do |image|
159          image.background_color = 'white'
160          image.format = 'GIF'
161        end
162
163        images << image
164      end
165
166      oldpx = 0.0
167      oldpy = 0.0
168
169      first = true
170
171      m = 0
172      mm = 0
173      points do |p|
174        px = proj.x(p['longitude'])
175        py = proj.y(p['latitude'])
176
177        if m > 0
178          frames.times do |n|
179            if n == mm
180              gc = highlightgc.dup
181            else
182              gc = linegc.dup
183            end
184
185            gc.line(px, py, oldpx, oldpy)
186
187            gc.draw(images[n])
188          end
189        end
190
191        m += 1
192        if m > num_points.to_f / frames.to_f * (mm+1)
193          mm += 1
194        end
195
196        oldpy = py
197        oldpx = px
198      end
199
200      il = Magick::ImageList.new
201
202      images.each do |f|
203        il << f
204      end
205
206      il.delay = 50
207      il.format = 'GIF'
208
209      return il.to_blob
210    end
211
212    def get_icon(min_lat, min_lon, max_lat, max_lon)
213      #puts "getting icon for bbox #{min_lat},#{min_lon} - #{max_lat},#{max_lon}"
214      width = 50
215      height = 50
216      rat= Math.cos( ((max_lat + min_lat)/2.0) /  180.0 * 3.141592)
217      proj = OSM::Mercator.new((min_lat + max_lat) / 2, (max_lon + min_lon) / 2, (max_lat - min_lat) / width / rat, width, height)
218
219      gc = Magick::Draw.new
220      gc.stroke_linejoin('miter')
221      gc.stroke_width(1)
222      gc.stroke('#000000')
223      gc.fill('#000000')
224
225      image = Magick::Image.new(width, height) do |image|
226        image.background_color = 'white'
227        image.format = 'GIF'
228      end
229
230      oldpx = 0.0
231      oldpy = 0.0
232
233      first = true
234
235      points do |p|
236        px = proj.x(p['longitude'])
237        py = proj.y(p['latitude'])
238
239        gc.dup.line(px, py, oldpx, oldpy).draw(image) unless first
240
241        first = false
242        oldpy = py
243        oldpx = px
244      end
245
246      return image.to_blob
247    end
248
249  end
250
251  class GreatCircle
252    include Math
253
254    # initialise with a base position
255    def initialize(lat, lon)
256      @lat = lat * PI / 180
257      @lon = lon * PI / 180
258    end
259
260    # get the distance from the base position to a given position
261    def distance(lat, lon)
262      lat = lat * PI / 180
263      lon = lon * PI / 180
264      return 6372.795 * 2 * asin(sqrt(sin((lat - @lat) / 2) ** 2 + cos(@lat) * cos(lat) * sin((lon - @lon)/2) ** 2))
265    end
266
267    # get the worst case bounds for a given radius from the base position
268    def bounds(radius)
269      latradius = 2 * asin(sqrt(sin(radius / 6372.795 / 2) ** 2))
270      lonradius = 2 * asin(sqrt(sin(radius / 6372.795 / 2) ** 2 / cos(@lat) ** 2))
271      minlat = (@lat - latradius) * 180 / PI
272      maxlat = (@lat + latradius) * 180 / PI
273      minlon = (@lon - lonradius) * 180 / PI
274      maxlon = (@lon + lonradius) * 180 / PI
275      return { :minlat => minlat, :maxlat => maxlat, :minlon => minlon, :maxlon => maxlon }
276    end
277  end
278
279  class GeoRSS
280    def initialize(feed_title='OpenStreetMap GPS Traces', feed_description='OpenStreetMap GPS Traces', feed_url='http://www.openstreetmap.org/traces/')
281      @doc = XML::Document.new
282      @doc.encoding = 'UTF-8' 
283     
284      rss = XML::Node.new 'rss'
285      @doc.root = rss
286      rss['version'] = "2.0"
287      rss['xmlns:geo'] = "http://www.w3.org/2003/01/geo/wgs84_pos#"
288      @channel = XML::Node.new 'channel'
289      rss << @channel
290      title = XML::Node.new 'title'
291      title <<  feed_title
292      @channel << title
293      description_el = XML::Node.new 'description'
294      @channel << description_el
295
296      description_el << feed_description
297      link = XML::Node.new 'link'
298      link << feed_url
299      @channel << link
300      image = XML::Node.new 'image'
301      @channel << image
302      url = XML::Node.new 'url'
303      url << 'http://www.openstreetmap.org/images/mag_map-rss2.0.png'
304      image << url
305      title = XML::Node.new 'title'
306      title << "OpenStreetMap"
307      image << title
308      width = XML::Node.new 'width'
309      width << '100'
310      image << width
311      height = XML::Node.new 'height'
312      height << '100'
313      image << height
314      link = XML::Node.new 'link'
315      link << feed_url
316      image << link
317    end
318
319    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)
320      item = XML::Node.new 'item'
321
322      title = XML::Node.new 'title'
323      item << title
324      title << title_text
325      link = XML::Node.new 'link'
326      link << url
327      item << link
328
329      guid = XML::Node.new 'guid'
330      guid << url
331      item << guid
332
333      description = XML::Node.new 'description'
334      description << description_text
335      item << description
336
337      author = XML::Node.new 'author'
338      author << author_text
339      item << author
340
341      pubDate = XML::Node.new 'pubDate'
342      pubDate << timestamp.to_s(:rfc822)
343      item << pubDate
344
345      if latitude
346        lat_el = XML::Node.new 'geo:lat'
347        lat_el << latitude.to_s
348        item << lat_el
349      end
350
351      if longitude
352        lon_el = XML::Node.new 'geo:long'
353        lon_el << longitude.to_s
354        item << lon_el
355      end
356
357      @channel << item
358    end
359
360    def to_s
361      return @doc.to_s
362    end
363  end
364
365  class API
366    def get_xml_doc
367      doc = XML::Document.new
368      doc.encoding = 'UTF-8' 
369      root = XML::Node.new 'osm'
370      root['version'] = API_VERSION
371      root['generator'] = 'OpenStreetMap server'
372      doc.root = root
373      return doc
374    end
375  end
376
377  def self.IPLocation(ip_address)
378    Timeout::timeout(4) do
379      Net::HTTP.start('api.hostip.info') do |http|
380        country = http.get("/country.php?ip=#{ip_address}").body
381        country = "GB" if country == "UK"
382        Net::HTTP.start('ws.geonames.org') do |http|
383          xml = REXML::Document.new(http.get("/countryInfo?country=#{country}").body)
384          xml.elements.each("geonames/country") do |ele|
385            minlon = ele.get_text("bBoxWest").to_s
386            minlat = ele.get_text("bBoxSouth").to_s
387            maxlon = ele.get_text("bBoxEast").to_s
388            maxlat = ele.get_text("bBoxNorth").to_s
389            return { :minlon => minlon, :minlat => minlat, :maxlon => maxlon, :maxlat => maxlat }
390          end
391        end
392      end
393    end
394
395    return nil
396  rescue Exception
397    return nil
398  end
399
400  # Construct a random token of a given length
401  def self.make_token(length = 30)
402    chars = 'abcdefghijklmnopqrtuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
403    token = ''
404
405    length.times do
406      token += chars[(rand * chars.length).to_i].chr
407    end
408
409    return token
410  end
411
412  # Return an encrypted version of a password
413  def self.encrypt_password(password, salt)
414    return Digest::MD5.hexdigest(password) if salt.nil?
415    return Digest::MD5.hexdigest(salt + password)
416  end
417
418  # Return an SQL fragment to select a given area of the globe
419  def self.sql_for_area(minlat, minlon, maxlat, maxlon, prefix = nil)
420    tilesql = QuadTile.sql_for_area(minlat, minlon, maxlat, maxlon, prefix)
421    minlat = (minlat * 10000000).round
422    minlon = (minlon * 10000000).round
423    maxlat = (maxlat * 10000000).round
424    maxlon = (maxlon * 10000000).round
425
426    return "#{tilesql} AND #{prefix}latitude BETWEEN #{minlat} AND #{maxlat} AND #{prefix}longitude BETWEEN #{minlon} AND #{maxlon}"
427  end
428end
Note: See TracBrowser for help on using the repository browser.