3   # This piece of magic reads a GPX with SAX and spits out
 
   6   # This would print every latitude value:
 
   8   # gpx = OSM::GPXImporter.new('somefile.gpx')
 
   9   # gpx.points {|p| puts p['latitude']}
 
  12   require 'rexml/parsers/sax2parser'
 
  21     #init me with your bounding box and the size of your image
 
  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
 
  30       xpad = width * scale - xsize
 
  31       ypad = height * scale - ysize
 
  36       @tx = xsheet(min_lon) - xpad / 2
 
  37       @ty = ysheet(min_lat) - ypad / 2
 
  39       @bx = xsheet(max_lon) + xpad / 2
 
  40       @by = ysheet(max_lat) + ypad / 2
 
  43     #the following two functions will give you the x/y on the entire sheet
 
  46       log(tan(PI / 4 + (lat * PI / 180 / 2))) / (PI / 180)
 
  53     #and these two will give you the right points on your image. all the constants can be reduced to speed things up. FIXME
 
  56       return @height - ((ysheet(lat) - @ty) / (@by - @ty) * @height)
 
  60       return  ((xsheet(lon) - @tx) / (@bx - @tx) * @width)
 
  66     # FIXME swap REXML for libXML
 
  67     attr_reader :possible_points
 
  68     attr_reader :actual_points
 
  69     attr_reader :tracksegs
 
  83       date = DateTime.now();
 
  90       parser = REXML::Parsers::SAX2Parser.new(@file)
 
  92       parser.listen( :start_element,  %w{ trkpt }) do |uri,localname,qname,attributes| 
 
  93         lat = attributes['lat'].to_f
 
  94         lon = attributes['lon'].to_f
 
 101       parser.listen( :characters, %w{ ele } ) do |text|
 
 106       parser.listen( :characters, %w{ time } ) do |text|
 
 107         if text && text != ''
 
 109             date = DateTime.parse(text)
 
 116       parser.listen( :end_element, %w{ trkseg } ) do |uri, localname, qname|
 
 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
 
 125             yield Hash['latitude' => lat, 'longitude' => lon, 'timestamp' => date, 'altitude' => ele, 'segment' => @tracksegs]
 
 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}"
 
 141       proj = OSM::Mercator.new(min_lat, min_lon, max_lat, max_lon, width, height)
 
 143       linegc = Magick::Draw.new
 
 144       linegc.stroke_linejoin('miter')
 
 145       linegc.stroke_width(1)
 
 146       linegc.stroke('#BBBBBB')
 
 147       linegc.fill('#BBBBBB')
 
 149       highlightgc = Magick::Draw.new
 
 150       highlightgc.stroke_linejoin('miter')
 
 151       highlightgc.stroke_width(3)
 
 152       highlightgc.stroke('#000000')
 
 153       highlightgc.fill('#000000')
 
 158         image = Magick::Image.new(width, height) do |image|
 
 159           image.background_color = 'white'
 
 174         px = proj.x(p['longitude'])
 
 175         py = proj.y(p['latitude'])
 
 185             gc.line(px, py, oldpx, oldpy)
 
 192         if m > num_points.to_f / frames.to_f * (mm+1)
 
 200       il = Magick::ImageList.new
 
 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}"
 
 216       proj = OSM::Mercator.new(min_lat, min_lon, max_lat, max_lon, width, height)
 
 218       gc = Magick::Draw.new
 
 219       gc.stroke_linejoin('miter')
 
 224       image = Magick::Image.new(width, height) do |image|
 
 225         image.background_color = 'white'
 
 235         px = proj.x(p['longitude'])
 
 236         py = proj.y(p['latitude'])
 
 238         gc.dup.line(px, py, oldpx, oldpy).draw(image) unless first
 
 253     # initialise with a base position
 
 254     def initialize(lat, lon)
 
 255       @lat = lat * PI / 180
 
 256       @lon = lon * PI / 180
 
 259     # get the distance from the base position to a given position
 
 260     def distance(lat, lon)
 
 263       return 6372.795 * 2 * asin(sqrt(sin((lat - @lat) / 2) ** 2 + cos(@lat) * cos(lat) * sin((lon - @lon)/2) ** 2))
 
 266     # get the worst case bounds for a given radius from the base position
 
 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 }
 
 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' 
 
 283       rss = XML::Node.new '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'
 
 289       title = XML::Node.new 'title'
 
 292       description_el = XML::Node.new 'description'
 
 293       @channel << description_el
 
 295       description_el << feed_description
 
 296       link = XML::Node.new 'link'
 
 299       image = XML::Node.new 'image'
 
 301       url = XML::Node.new 'url'
 
 302       url << 'http://www.openstreetmap.org/images/mag_map-rss2.0.png'
 
 304       title = XML::Node.new 'title'
 
 305       title << "OpenStreetMap"
 
 307       width = XML::Node.new 'width'
 
 310       height = XML::Node.new 'height'
 
 313       link = XML::Node.new 'link'
 
 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'
 
 321       title = XML::Node.new 'title'
 
 324       link = XML::Node.new 'link'
 
 328       guid = XML::Node.new 'guid'
 
 332       description = XML::Node.new 'description'
 
 333       description << description_text
 
 336       author = XML::Node.new 'author'
 
 337       author << author_text
 
 340       pubDate = XML::Node.new 'pubDate'
 
 341       pubDate << timestamp.to_s(:rfc822)
 
 345         lat_el = XML::Node.new 'geo:lat'
 
 346         lat_el << latitude.to_s
 
 351         lon_el = XML::Node.new 'geo:long'
 
 352         lon_el << longitude.to_s
 
 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'
 
 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 }
 
 399   # Construct a random token of a given length
 
 400   def self.make_token(length = 30)
 
 401     chars = 'abcdefghijklmnopqrtuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
 
 405       token += chars[(rand * chars.length).to_i].chr
 
 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)
 
 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
 
 425     return "#{tilesql} AND #{prefix}latitude BETWEEN #{minlat} AND #{maxlat} AND #{prefix}longitude BETWEEN #{minlon} AND #{maxlon}"