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

Last change on this file since 6589 was 6589, checked in by steve, 12 years ago

Lots of documentation updates, plus split out potlatch libraries in to lib

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
427
428
429end
Note: See TracBrowser for help on using the repository browser.