oisin/ierail

View on GitHub
lib/ierail.rb

Summary

Maintainability
A
0 mins
Test Coverage
require 'rest-client'
require 'nokogiri'
require 'uri'
require 'time'

require 'train'
require 'station'
require 'station_data'
require 'train_movement'
require 'core_ext'

class IERail
  
  URL = "http://api.irishrail.ie/realtime/realtime.asmx"
  
  class IERailGet < Nokogiri::XML::SAX::Document
    
    attr_reader :result
    
    def initialize(url, array_name, object_name)
      @ws_url = url.gsub(' ', '%20')
      @ws_array_name = array_name.downcase
      @ws_object_name = object_name.downcase
      @result = nil
      @current = nil
    end
      
    def response
      unless @result
        parser = Nokogiri::XML::SAX::Parser.new(self)
        parser.parse(RestClient.get(URL + "/" + @ws_url))
      end
      @result
    end

    def start_document 
      @result = []
    end
    
    def characters string
      string.strip!
      @result.last[@current] << string unless string.empty?
    end
    
    def start_element name, attrs = []
      case name.downcase
        when @ws_object_name 
          @result << Hash.new
        when @ws_array_name
          ;
        else
          @current = name          
          @result.last[@current] = ""
      end
    end
    
    def end_element name
      if @current && @result.last[@current].empty?
        @result.last.delete(@current)
      end
      @current = nil
    end
  end
  
  # Get ALL the stations!
  # Returns array of Station objects, and each object responds to
  # {
  #    obj#name => "Belfast Central",
  #    obj#location => [-5.91744, 54.6123]
  #    obj#code => "BFSTC",
  #    obj#id => 228
  #  }
  # Returns empty array if no data, but that would be odd.
  #
  def stations
    ier = IERailGet.new("getAllStationsXML?", "arrayofobjstation", "objstation")
    
    ier.response.map { |s| Station.new(s) }
  end
  
  # Get ALL the trains! That are on the go at the moment.
  # Returns array of Train objects, and each object responds to
  #  {
  #    obj#status => "R",
  #    obj#location => [-6.23929, 53.3509]
  #    obj#code =>  "D303",
  #    obj#date => 20 Jan 2012,
  #    obj#message => "D303\\n09:30 - Docklands to M3 Parkway (1 mins late)\\nDeparted Docklands next stop Broombridge",
  #    obj#direction => "Northbound"
  #  }
  # Returns empty array if no data
  #
  def trains
    ier = IERailGet.new("getCurrentTrainsXML?", "arrayofobjtrainpositions", "objtrainpositions")
    
    ier.response.map { |t| Train.new(t) }
  end
  
  # Get train information for a particular station, by station name. This gives data on trains thru that station
  # Returns array of StationData objects, and each object responds to
  # {
  #   obj#server_time => 2012-01-20T10:03:33.777,
  #   obj#train_code => "E909",
  #   obj#name / obj#station_name => "Glenageary",
  #   obj#code / obj#station_code => "GLGRY",
  #   obj#query_time => 10:03:33,
  #   obj#train_date => 20 Jan 2012,
  #   obj#origin => {:name => "Bray", :time => 09:55}
  #   obj#destination => {:name => "Howth", :time => 11:03}
  #   obj#status => "En Route",
  #   obj#last_location => "Arrived Killiney",
  #   obj#due_in => 6,
  #   obj#minutes_early => 0,
  #   obj#minutes_late => 0,
  #   obj#on_time? => true / false,
  #   obj#early? => true / false,
  #   obj#late? => true / false,
  #   obj#arrival => {:scheduled => 10:09, :expected => 10:09}
  #   obj#departure => {:scheduled => 10:09, :expected => 10:09}
  #   obj#direction => "Northbound",
  #   obj#train_type => "DART",
  # }
  # Returns empty array if no data.
  #
  def station(name)
    ier = IERailGet.new("getStationDataByNameXML?StationDesc=#{name}", "arrayofobjstationdata", "objstationdata")
    
    ier.response.map { |sd| StationData.new(sd) }
  end
  
  # Get train information for a particular station, by station name, within the time period in minutes from now. 
  # This gives data on trains thru that station.
  # Returns array of StationData objects, and each obj looks like the one for IERail#station
  # Will return an empty array if no information.
  #
  def station_times(name, mins)
    ier = IERailGet.new("getStationDataByNameXML_withNumMins?StationDesc=#{name}&NumMins=#{mins}", "arrayofobjstationdata", "objstationdata")
    
    ier.response.map { |sd| StationData.new(sd) }
  end
  
  # Get the movements of a particular train, by train code, on a date.
  #  If no date is supplied assume train has run/is running today.
  #
  # This gives all the stations that the train has or will be stopped at.
  # Returns an array of TrainMovement objects, and each object responds to
  # {
  #    obj#location => {code: "GLGRY", name: "Glenageary", stop_number: 1 (Stop number on route), type: "O" (Origin) \ "S" (Stop) \ "D" (Destination)}
  #    obj#train => {:code => "E909", :date => 20 Jan 2012, :origin => "Glenageary"}
  #    obj#arrival => {:scheduled => 10:09, :expected => 10:09, :actual => 10.09}
  #    obj#departure => {:scheduled => 10:09, :expected => 10:09, :actual => 10.09}
  #    obj#station => "Glenageary"
  # }
  # Returns empty array if no data.
  #
  def train_movements(code, date=Time.now)
    ier = IERailGet.new("getTrainMovementsXML?TrainId=#{code}&TrainDate=#{date.strftime("%d/%m/%Y")}", "ArrayOfObjTrainMovements", "ObjTrainMovements")
    
    ier.response.map{ |tm| TrainMovement.new(tm) }
  end
  
  # Find station codes and descriptions using a partial string to match the station name
  # Returns an array of Structs that each respond to
  # {
  #    struct#name => "Sandycove",
  #    struct#description => "Glasthule (Sandycove)",
  #    struct#code => "SCOVE"
  #  }
  # or an empty array if no matches.
  #
  def find_station(partial)
    ier = IERailGet.new("getStationsFilterXML?StationText=#{partial}", "ArrayOfObjStationFilter", "objStationFilter")
    
    unless defined?(Struct::Station)
        Struct.new("Station", :name, :description, :code)
    end
    
    ier.response.map do |st|
       Struct::Station.new(st['StationDesc_sp'],
                           st['StationDesc'],
                           st['StationCode'])
    end
  end

  # Get direction-specific train information for a particular station, by station name.
  # This gives data on trains through that station
  # Returns array of StationData objects, and each obj looks like the one for IERail#station
  # Returns empty array if no data.
  #

  def method_missing(name, *args, &block)
    # Only handle *bound_from (e.g northbound_from / southbound_from)
    if name =~ /bound_from/
      direction = name.to_s.split('_').first.capitalize

      ier = IERailGet.new("getStationDataByNameXML?StationDesc=#{args.first}", "arrayofobjstationdata", "objstationdata")
     
      ier.response.select { |sd| sd['Direction'] == direction }.map { |sd| StationData.new(sd) }
    end
  end
end