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
RevLine 
[1709]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  #
[4008]8  # gpx = OSM::GPXImporter.new('somefile.gpx')
[1709]9  # gpx.points {|p| puts p['latitude']}
[1711]10
[1709]11  require 'time'
12  require 'rexml/parsers/sax2parser'
13  require 'rexml/text'
[1771]14  require 'xml/libxml'
[4141]15  require 'digest/md5'
[1711]16  require 'RMagick'
[1709]17
[1711]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
[3265]26      @degrees_per_pixel = 0.0000000001 if @degrees_per_pixel < 0.0000000001
[1711]27      @width = width
28      @height = height
[3265]29      @dlon = width / 2 * @degrees_per_pixel
30      @dlat = height / 2 * @degrees_per_pixel  * cos(@clat * PI / 180)
[1711]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
[1709]66  class GPXImporter
[1771]67    # FIXME swap REXML for libXML
[1709]68    attr_reader :possible_points
[1710]69    attr_reader :actual_points
[1709]70    attr_reader :tracksegs
71
72    def initialize(filename)
[3318]73      @filename = filename
74    end
75
76    def points
[1709]77      @possible_points = 0
[1710]78      @actual_points = 0
[1709]79      @tracksegs = 0
80
81      lat = -1
82      lon = -1
83      ele = -1
[3263]84      date = DateTime.now();
[1709]85      gotlatlon = false
86      gotele = false
87      gotdate = false
88
[3318]89      parser = REXML::Parsers::SAX2Parser.new(File.new(@filename))
90
[1709]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
[5765]95        gotele = false
96        gotdate = false
[1709]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 != ''
[3363]107          begin
108            date = DateTime.parse(text)
109            gotdate = true
110          rescue
111          end
[1709]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
[1710]123            @actual_points += 1
[3318]124            yield Hash['latitude' => lat, 'longitude' => lon, 'timestamp' => date, 'altitude' => ele, 'segment' => @tracksegs]
[1709]125          end
126        end
127        gotlatlon = false
128        gotele = false
129        gotdate = false
130      end
[3318]131
[1709]132      parser.parse
133    end
[1711]134
135    def get_picture(min_lat, min_lon, max_lat, max_lon, num_points)
[2776]136      #puts "getting picfor bbox #{min_lat},#{min_lon} - #{max_lat},#{max_lon}"
[1711]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
[5257]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
[1711]155      images = []
156
157      frames.times do
[5257]158        image = Magick::Image.new(width, height) do |image|
159          image.background_color = 'white'
160          image.format = 'GIF'
161        end
162
163        images << image
[1711]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'])
[5257]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
[1711]189        end
[5257]190
191        m += 1
[1711]192        if m > num_points.to_f / frames.to_f * (mm+1)
193          mm += 1
194        end
[5257]195
[1711]196        oldpy = py
197        oldpx = px
198      end
199
200      il = Magick::ImageList.new
201
[5257]202      images.each do |f|
203        il << f
[1711]204      end
205
206      il.delay = 50
207      il.format = 'GIF'
[5257]208
[1711]209      return il.to_blob
210    end
211
212    def get_icon(min_lat, min_lon, max_lat, max_lon)
[3265]213      #puts "getting icon for bbox #{min_lat},#{min_lon} - #{max_lat},#{max_lon}"
[1711]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
[5257]219      gc = Magick::Draw.new
[1711]220      gc.stroke_linejoin('miter')
[5257]221      gc.stroke_width(1)
222      gc.stroke('#000000')
223      gc.fill('#000000')
[1711]224
[5257]225      image = Magick::Image.new(width, height) do |image|
226        image.background_color = 'white'
227        image.format = 'GIF'
228      end
229
[1711]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'])
[5257]238
239        gc.dup.line(px, py, oldpx, oldpy).draw(image) unless first
240
[1711]241        first = false
242        oldpy = py
243        oldpx = px
244      end
245
[5257]246      return image.to_blob
[1711]247    end
248
[1709]249  end
[1771]250
[3279]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
[1771]279  class GeoRSS
[3169]280    def initialize(feed_title='OpenStreetMap GPS Traces', feed_description='OpenStreetMap GPS Traces', feed_url='http://www.openstreetmap.org/traces/')
[1771]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'
[3169]291      title <<  feed_title
[1771]292      @channel << title
293      description_el = XML::Node.new 'description'
294      @channel << description_el
295
[3169]296      description_el << feed_description
[1771]297      link = XML::Node.new 'link'
[3169]298      link << feed_url
[1771]299      @channel << link
300      image = XML::Node.new 'image'
301      @channel << image
302      url = XML::Node.new 'url'
[4156]303      url << 'http://www.openstreetmap.org/images/mag_map-rss2.0.png'
[1771]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'
[3169]315      link << feed_url
[1771]316      image << link
317    end
318
[4111]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)
[1771]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
[3252]329      guid = XML::Node.new 'guid'
330      guid << url
331      item << guid
332
[1771]333      description = XML::Node.new 'description'
334      description << description_text
335      item << description
336
[4111]337      author = XML::Node.new 'author'
338      author << author_text
339      item << author
340
[1771]341      pubDate = XML::Node.new 'pubDate'
[3251]342      pubDate << timestamp.to_s(:rfc822)
[1771]343      item << pubDate
344
[3169]345      if latitude
346        lat_el = XML::Node.new 'geo:lat'
347        lat_el << latitude.to_s
[3170]348        item << lat_el
[3169]349      end
[1771]350
[3169]351      if longitude
352        lon_el = XML::Node.new 'geo:long'
353        lon_el << longitude.to_s
354        item << lon_el
355      end
[1771]356
357      @channel << item
358    end
359
360    def to_s
361      return @doc.to_s
362    end
363  end
[2418]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
[4008]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
[4953]381        country = "GB" if country == "UK"
[4008]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
[4031]396  rescue Exception
[4008]397    return nil
398  end
[4140]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
[4141]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
[4517]417
418  # Return an SQL fragment to select a given area of the globe
[4578]419  def self.sql_for_area(minlat, minlon, maxlat, maxlon, prefix = nil)
420    tilesql = QuadTile.sql_for_area(minlat, minlon, maxlat, maxlon, prefix)
[4611]421    minlat = (minlat * 10000000).round
422    minlon = (minlon * 10000000).round
423    maxlat = (maxlat * 10000000).round
424    maxlon = (maxlon * 10000000).round
[4517]425
[4578]426    return "#{tilesql} AND #{prefix}latitude BETWEEN #{minlat} AND #{maxlat} AND #{prefix}longitude BETWEEN #{minlon} AND #{maxlon}"
[4517]427  end
[1709]428end
Note: See TracBrowser for help on using the repository browser.