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

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

Allow a GPX to be fetched in XML format by fetching data.xml instead
of data (the data method returns it in the format it is stored in).

Closes #534.

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    #init me with your bounding box and the size of your image
22
23    def initialize(min_lat, min_lon, max_lat, max_lon, width, height)
24      xsize = xsheet(max_lon) - xsheet(min_lon)
25      ysize = ysheet(max_lat) - ysheet(min_lat)
26      xscale = xsize / width
27      yscale = ysize / height
28      scale = [xscale, yscale].max
29
30      xpad = width * scale - xsize
31      ypad = height * scale - ysize
32
33      @width = width
34      @height = height
35
36      @tx = xsheet(min_lon) - xpad / 2
37      @ty = ysheet(min_lat) - ypad / 2
38
39      @bx = xsheet(max_lon) + xpad / 2
40      @by = ysheet(max_lat) + ypad / 2
41    end
42
43    #the following two functions will give you the x/y on the entire sheet
44
45    def ysheet(lat)
46      log(tan(PI / 4 + (lat * PI / 180 / 2))) / (PI / 180)
47    end
48
49    def xsheet(lon)
50      lon
51    end
52
53    #and these two will give you the right points on your image. all the constants can be reduced to speed things up. FIXME
54
55    def y(lat)
56      return @height - ((ysheet(lat) - @ty) / (@by - @ty) * @height)
57    end
58
59    def x(lon)
60      return  ((xsheet(lon) - @tx) / (@bx - @tx) * @width)
61    end
62  end
63
64
65  class GPXImporter
66    # FIXME swap REXML for libXML
67    attr_reader :possible_points
68    attr_reader :actual_points
69    attr_reader :tracksegs
70
71    def initialize(file)
72      @file = file
73    end
74
75    def points
76      @possible_points = 0
77      @actual_points = 0
78      @tracksegs = 0
79
80      lat = -1
81      lon = -1
82      ele = -1
83      date = DateTime.now();
84      gotlatlon = false
85      gotele = false
86      gotdate = false
87
88      @file.rewind
89
90      parser = REXML::Parsers::SAX2Parser.new(@file)
91
92      parser.listen( :start_element,  %w{ trkpt }) do |uri,localname,qname,attributes| 
93        lat = attributes['lat'].to_f
94        lon = attributes['lon'].to_f
95        gotlatlon = true
96        gotele = false
97        gotdate = false
98        @possible_points += 1
99      end
100
101      parser.listen( :characters, %w{ ele } ) do |text|
102        ele = text
103        gotele = true
104      end
105
106      parser.listen( :characters, %w{ time } ) do |text|
107        if text && text != ''
108          begin
109            date = DateTime.parse(text)
110            gotdate = true
111          rescue
112          end
113        end
114      end
115
116      parser.listen( :end_element, %w{ trkseg } ) do |uri, localname, qname|
117        @tracksegs += 1
118      end
119
120      parser.listen( :end_element, %w{ trkpt } ) do |uri,localname,qname|
121        if gotlatlon && gotdate
122          ele = '0' unless gotele
123          if lat < 90 && lat > -90 && lon > -180 && lon < 180
124            @actual_points += 1
125            yield Hash['latitude' => lat, 'longitude' => lon, 'timestamp' => date, 'altitude' => ele, 'segment' => @tracksegs]
126          end
127        end
128        gotlatlon = false
129        gotele = false
130        gotdate = false
131      end
132
133      parser.parse
134    end
135
136    def get_picture(min_lat, min_lon, max_lat, max_lon, num_points)
137      #puts "getting picfor bbox #{min_lat},#{min_lon} - #{max_lat},#{max_lon}"
138      frames = 10
139      width = 250
140      height = 250
141      proj = OSM::Mercator.new(min_lat, min_lon, max_lat, max_lon, 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      proj = OSM::Mercator.new(min_lat, min_lon, max_lat, max_lon, width, height)
217
218      gc = Magick::Draw.new
219      gc.stroke_linejoin('miter')
220      gc.stroke_width(1)
221      gc.stroke('#000000')
222      gc.fill('#000000')
223
224      image = Magick::Image.new(width, height) do |image|
225        image.background_color = 'white'
226        image.format = 'GIF'
227      end
228
229      oldpx = 0.0
230      oldpy = 0.0
231
232      first = true
233
234      points do |p|
235        px = proj.x(p['longitude'])
236        py = proj.y(p['latitude'])
237
238        gc.dup.line(px, py, oldpx, oldpy).draw(image) unless first
239
240        first = false
241        oldpy = py
242        oldpx = px
243      end
244
245      return image.to_blob
246    end
247
248  end
249
250  class GreatCircle
251    include Math
252
253    # initialise with a base position
254    def initialize(lat, lon)
255      @lat = lat * PI / 180
256      @lon = lon * PI / 180
257    end
258
259    # get the distance from the base position to a given position
260    def distance(lat, lon)
261      lat = lat * PI / 180
262      lon = lon * PI / 180
263      return 6372.795 * 2 * asin(sqrt(sin((lat - @lat) / 2) ** 2 + cos(@lat) * cos(lat) * sin((lon - @lon)/2) ** 2))
264    end
265
266    # get the worst case bounds for a given radius from the base position
267    def bounds(radius)
268      latradius = 2 * asin(sqrt(sin(radius / 6372.795 / 2) ** 2))
269      lonradius = 2 * asin(sqrt(sin(radius / 6372.795 / 2) ** 2 / cos(@lat) ** 2))
270      minlat = (@lat - latradius) * 180 / PI
271      maxlat = (@lat + latradius) * 180 / PI
272      minlon = (@lon - lonradius) * 180 / PI
273      maxlon = (@lon + lonradius) * 180 / PI
274      return { :minlat => minlat, :maxlat => maxlat, :minlon => minlon, :maxlon => maxlon }
275    end
276  end
277
278  class GeoRSS
279    def initialize(feed_title='OpenStreetMap GPS Traces', feed_description='OpenStreetMap GPS Traces', feed_url='http://www.openstreetmap.org/traces/')
280      @doc = XML::Document.new
281      @doc.encoding = 'UTF-8' 
282     
283      rss = XML::Node.new 'rss'
284      @doc.root = rss
285      rss['version'] = "2.0"
286      rss['xmlns:geo'] = "http://www.w3.org/2003/01/geo/wgs84_pos#"
287      @channel = XML::Node.new 'channel'
288      rss << @channel
289      title = XML::Node.new 'title'
290      title <<  feed_title
291      @channel << title
292      description_el = XML::Node.new 'description'
293      @channel << description_el
294
295      description_el << feed_description
296      link = XML::Node.new 'link'
297      link << feed_url
298      @channel << link
299      image = XML::Node.new 'image'
300      @channel << image
301      url = XML::Node.new 'url'
302      url << 'http://www.openstreetmap.org/images/mag_map-rss2.0.png'
303      image << url
304      title = XML::Node.new 'title'
305      title << "OpenStreetMap"
306      image << title
307      width = XML::Node.new 'width'
308      width << '100'
309      image << width
310      height = XML::Node.new 'height'
311      height << '100'
312      image << height
313      link = XML::Node.new 'link'
314      link << feed_url
315      image << link
316    end
317
318    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)
319      item = XML::Node.new 'item'
320
321      title = XML::Node.new 'title'
322      item << title
323      title << title_text
324      link = XML::Node.new 'link'
325      link << url
326      item << link
327
328      guid = XML::Node.new 'guid'
329      guid << url
330      item << guid
331
332      description = XML::Node.new 'description'
333      description << description_text
334      item << description
335
336      author = XML::Node.new 'author'
337      author << author_text
338      item << author
339
340      pubDate = XML::Node.new 'pubDate'
341      pubDate << timestamp.to_s(:rfc822)
342      item << pubDate
343
344      if latitude
345        lat_el = XML::Node.new 'geo:lat'
346        lat_el << latitude.to_s
347        item << lat_el
348      end
349
350      if longitude
351        lon_el = XML::Node.new 'geo:long'
352        lon_el << longitude.to_s
353        item << lon_el
354      end
355
356      @channel << item
357    end
358
359    def to_s
360      return @doc.to_s
361    end
362  end
363
364  class API
365    def get_xml_doc
366      doc = XML::Document.new
367      doc.encoding = 'UTF-8' 
368      root = XML::Node.new 'osm'
369      root['version'] = API_VERSION
370      root['generator'] = 'OpenStreetMap server'
371      doc.root = root
372      return doc
373    end
374  end
375
376  def self.IPLocation(ip_address)
377    Timeout::timeout(4) do
378      Net::HTTP.start('api.hostip.info') do |http|
379        country = http.get("/country.php?ip=#{ip_address}").body
380        country = "GB" if country == "UK"
381        Net::HTTP.start('ws.geonames.org') do |http|
382          xml = REXML::Document.new(http.get("/countryInfo?country=#{country}").body)
383          xml.elements.each("geonames/country") do |ele|
384            minlon = ele.get_text("bBoxWest").to_s
385            minlat = ele.get_text("bBoxSouth").to_s
386            maxlon = ele.get_text("bBoxEast").to_s
387            maxlat = ele.get_text("bBoxNorth").to_s
388            return { :minlon => minlon, :minlat => minlat, :maxlon => maxlon, :maxlat => maxlat }
389          end
390        end
391      end
392    end
393
394    return nil
395  rescue Exception
396    return nil
397  end
398
399  # Construct a random token of a given length
400  def self.make_token(length = 30)
401    chars = 'abcdefghijklmnopqrtuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
402    token = ''
403
404    length.times do
405      token += chars[(rand * chars.length).to_i].chr
406    end
407
408    return token
409  end
410
411  # Return an encrypted version of a password
412  def self.encrypt_password(password, salt)
413    return Digest::MD5.hexdigest(password) if salt.nil?
414    return Digest::MD5.hexdigest(salt + password)
415  end
416
417  # Return an SQL fragment to select a given area of the globe
418  def self.sql_for_area(minlat, minlon, maxlat, maxlon, prefix = nil)
419    tilesql = QuadTile.sql_for_area(minlat, minlon, maxlat, maxlon, prefix)
420    minlat = (minlat * 10000000).round
421    minlon = (minlon * 10000000).round
422    maxlat = (maxlat * 10000000).round
423    maxlon = (maxlon * 10000000).round
424
425    return "#{tilesql} AND #{prefix}latitude BETWEEN #{minlat} AND #{maxlat} AND #{prefix}longitude BETWEEN #{minlon} AND #{maxlon}"
426  end
427end
Note: See TracBrowser for help on using the repository browser.