network_rail 0.0.1.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. data/.gitignore +17 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +88 -0
  6. data/Rakefile +5 -0
  7. data/lib/network_rail/client.rb +64 -0
  8. data/lib/network_rail/configuration.rb +25 -0
  9. data/lib/network_rail/exception/authentication_error.rb +6 -0
  10. data/lib/network_rail/exception/block_required.rb +6 -0
  11. data/lib/network_rail/exception/connection_error.rb +6 -0
  12. data/lib/network_rail/exception/no_login_credentials.rb +6 -0
  13. data/lib/network_rail/message/train_movement/activation.rb +10 -0
  14. data/lib/network_rail/message/train_movement/arrival.rb +10 -0
  15. data/lib/network_rail/message/train_movement/base.rb +18 -0
  16. data/lib/network_rail/message/train_movement/cancellation.rb +10 -0
  17. data/lib/network_rail/message/train_movement/change_of_identity.rb +10 -0
  18. data/lib/network_rail/message/train_movement/change_of_origin.rb +10 -0
  19. data/lib/network_rail/message/train_movement/departure.rb +10 -0
  20. data/lib/network_rail/message/train_movement/movement.rb +37 -0
  21. data/lib/network_rail/message/train_movement/reinstatement.rb +10 -0
  22. data/lib/network_rail/message/train_movement.rb +31 -0
  23. data/lib/network_rail/operators.rb +82 -0
  24. data/lib/network_rail/version.rb +3 -0
  25. data/lib/network_rail.rb +9 -0
  26. data/network_rail.gemspec +28 -0
  27. data/spec/fixtures/train_movements.json +412 -0
  28. data/spec/network_rail/client_spec.rb +137 -0
  29. data/spec/network_rail/message/train_movement/base_spec.rb +21 -0
  30. data/spec/network_rail/message/train_movement/movement_spec.rb +69 -0
  31. data/spec/network_rail/message/train_movement_spec.rb +25 -0
  32. data/spec/spec_helper.rb +10 -0
  33. metadata +161 -0
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in network_rail.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Steve Lorek
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # Network Rail Data Feeds Ruby Gem
2
+
3
+ Provides a convenient Ruby wrapper for the [Network Rail Data Feeds](https://datafeeds.networkrail.co.uk) - a live 'firehose' of real-time data about train timings and movements on the national UK rail network.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'network_rail'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install network_rail
18
+
19
+ ## Pre-Requisites
20
+
21
+ ### Register an account
22
+
23
+ You must first register an account on the [Network Rail Data Feeds](https://datafeeds.networkrail.co.uk) web site. The user name and password you choose will be used for authenticating with the service via the gem.
24
+
25
+ The service supports only a limited number of users, so you will need to wait for your account status to change to 'Active' before you can use the gem. You will be e-mailed by Network Rail when this happens.
26
+
27
+ If you do no access the data feeds for for 30 days, your account may switch to an 'Inactive' state, which will prevent you accessing the feeds.
28
+
29
+ ### Subscribe to feeds
30
+
31
+ Once you're in, you need to subscribe to feeds on the [Network Rail Data Feeds](https://datafeeds.networkrail.co.uk) web site in order to be able to receive them.
32
+
33
+ ## Quick Start
34
+
35
+ First set your Network Rail user name and password:
36
+
37
+ NetworkRail.configure do |config|
38
+ config.user_name = YOUR_EMAIL_ADDRESS
39
+ config.password = YOUR_PASSWORD
40
+ end
41
+
42
+ You don't need to supply your security token because this isn't yet used (according to the documentation in the [Developer Pack](http://www.networkrail.co.uk/WorkArea/DownloadAsset.aspx?id=30064782140)).
43
+
44
+ The gem implements a [STOMP](http://stomp.github.com/) client to allow you to consume the data feeds, and parses each messages into a convenient Ruby object. Once the client is loaded, it will continue listening for new messages until you tell it to stop.
45
+
46
+ ### Starting the client
47
+
48
+ You need to load the client to establish the connection between yourself and the feed provider. Create a new instance of the client as below.
49
+
50
+ client = NetworkRail::Client.new
51
+
52
+ ### Subscribing to feeds
53
+
54
+ Each client can listen to one or more data feeds. You need to specify a feed to begin receiving data. For example:
55
+
56
+ client.train_movements(operator: :all) do |movement|
57
+ # Do something
58
+ end
59
+
60
+ Refer to [NetworkRail::Operators#business_codes](https://github.com/slorek/network_rail/blob/master/lib/network_rail/operators.rb) for a list of all supported operator arguments.
61
+
62
+ Messages will begin to arrive from Network Rail in batches every few seconds. In the above example, `movement` will be an instance of a subclass of `NetworkRail::Message::TrainMovement::Base` (e.g. `NetworkRail::Message::TrainMovement::Arrival`). Some of the cool things you can do with this object are:
63
+
64
+ - Check if the train arrived on time with `movement.on_time?`
65
+ - Get the delay (if any) with `movement.delay`
66
+ - Determine the next station the train is due to stop at with `movement.next_station`
67
+
68
+ You can set the threshold at which a train is considered 'late' in the gem configuration.
69
+
70
+ ## Threading
71
+
72
+ The client runs on a separate thread, so once you've subscribed to all your feeds you need to ensure execution of your script doesn't end immediately. This can be most easily accomplished with something like this:
73
+
74
+ while true
75
+ # Continue forever
76
+ end
77
+
78
+ Add your own control logic if you need to do anything more complicated.
79
+
80
+ ## Contributing
81
+
82
+ 1. Fork it
83
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
84
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
85
+ 4. Push to the branch (`git push origin my-new-feature`)
86
+ 5. Create new Pull Request
87
+
88
+ This gem is created by Steven Lorek and is under the MIT License.
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task default: :spec
@@ -0,0 +1,64 @@
1
+ require "stomp"
2
+ require "json"
3
+ require "network_rail/operators"
4
+ require "network_rail/exception/authentication_error"
5
+ require "network_rail/exception/block_required"
6
+ require "network_rail/exception/connection_error"
7
+ require "network_rail/exception/no_login_credentials"
8
+
9
+ module NetworkRail
10
+ class Client
11
+
12
+ HOST_NAME = 'datafeeds.networkrail.co.uk'
13
+ HOST_PORT = 61618
14
+
15
+ def initialize
16
+ raise Exception::NoLoginCredentials if NetworkRail.user_name.nil? or NetworkRail.password.nil?
17
+
18
+ self.client = Stomp::Client.new(connection_parameters)
19
+
20
+ raise Exception::ConnectionError unless client.open?
21
+
22
+ if client.connection_frame().command == Stomp::CMD_ERROR
23
+ raise Exception::AuthenticationError if client.connection_frame().body.match /SecurityException/
24
+ raise Exception::ConnectionError
25
+ end
26
+ end
27
+
28
+ def train_movements(args = {operator: :all}, &block)
29
+ raise Exception::BlockRequired if !block
30
+
31
+ operator_code = NetworkRail::Operators.business_codes[args[:operator]]
32
+
33
+ client.subscribe("/topic/TRAIN_MVT_#{operator_code}_TOC") do |message|
34
+ movements = JSON.parse message
35
+ movements.each {|movement| yield NetworkRail::Message::TrainMovement.factory(movement) }
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ attr_accessor :client
42
+
43
+ def connection_headers
44
+ {
45
+ "accept-version" => "1.1",
46
+ "host" => HOST_NAME
47
+ }
48
+ end
49
+
50
+ def connection_parameters
51
+ {
52
+ hosts: [
53
+ {
54
+ login: NetworkRail.user_name,
55
+ passcode: NetworkRail.password,
56
+ host: HOST_NAME,
57
+ port: HOST_PORT
58
+ }
59
+ ],
60
+ connect_headers: connection_headers
61
+ }
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,25 @@
1
+ module NetworkRail
2
+ module Configuration
3
+ VALID_OPTIONS_KEYS = [:user_name, :password, :late_threshold].freeze
4
+
5
+ DEFAULT_USERNAME = nil
6
+ DEFAULT_PASSWORD = nil
7
+
8
+ attr_accessor *VALID_OPTIONS_KEYS
9
+
10
+ def self.extended(base)
11
+ base.reset
12
+ end
13
+
14
+ def configure
15
+ yield self
16
+ end
17
+
18
+ def reset
19
+ self.user_name = DEFAULT_USERNAME
20
+ self.password = DEFAULT_PASSWORD
21
+ self.late_threshold = 60
22
+ self
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,6 @@
1
+ module NetworkRail
2
+ module Exception
3
+ class AuthenticationError < ::Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module NetworkRail
2
+ module Exception
3
+ class BlockRequired < ::Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module NetworkRail
2
+ module Exception
3
+ class ConnectionError < ::Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module NetworkRail
2
+ module Exception
3
+ class NoLoginCredentials < ::Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,10 @@
1
+ require "network_rail/message/train_movement/base"
2
+
3
+ module NetworkRail
4
+ module Message
5
+ module TrainMovement
6
+ class Activation < Base
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ require "network_rail/message/train_movement/movement"
2
+
3
+ module NetworkRail
4
+ module Message
5
+ module TrainMovement
6
+ class Arrival < Movement
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ module NetworkRail
2
+ module Message
3
+ module TrainMovement
4
+ class Base
5
+
6
+ attr_accessor :original_message
7
+
8
+ def initialize(json_message)
9
+ self.original_message = json_message
10
+ end
11
+
12
+ def self.factory(json_message)
13
+ self.new(json_message)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ require "network_rail/message/train_movement/base"
2
+
3
+ module NetworkRail
4
+ module Message
5
+ module TrainMovement
6
+ class Cancellation < Base
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ require "network_rail/message/train_movement/base"
2
+
3
+ module NetworkRail
4
+ module Message
5
+ module TrainMovement
6
+ class ChangeOfIdentity < Base
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ require "network_rail/message/train_movement/base"
2
+
3
+ module NetworkRail
4
+ module Message
5
+ module TrainMovement
6
+ class ChangeOfOrigin < Base
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ require "network_rail/message/train_movement/movement"
2
+
3
+ module NetworkRail
4
+ module Message
5
+ module TrainMovement
6
+ class Departure < Movement
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,37 @@
1
+ require "network_rail/operators"
2
+ require "network_rail/message/train_movement/base"
3
+
4
+ module NetworkRail
5
+ module Message
6
+ module TrainMovement
7
+ class Movement < Base
8
+
9
+ require "network_rail/message/train_movement/arrival"
10
+ require "network_rail/message/train_movement/departure"
11
+
12
+ attr_accessor :time, :planned_time, :operator
13
+
14
+ def self.factory(json_message)
15
+ event_type = json_message['body']['event_type']
16
+ target_class = event_type == 'ARRIVAL' ? Arrival : Departure
17
+ target_class.new(json_message)
18
+ end
19
+
20
+ def initialize(json_message)
21
+ super
22
+ self.time = Time.at (json_message['body']['actual_timestamp'].to_i / 1000).to_i
23
+ self.planned_time = Time.at (json_message['body']['planned_timestamp'].to_i / 1000).to_i
24
+ self.operator = NetworkRail::Operators.numeric_codes.find {|key, value| value == json_message['body']['toc_id'].to_i }.first
25
+ end
26
+
27
+ def on_time?
28
+ delay <= NetworkRail.late_threshold
29
+ end
30
+
31
+ def delay
32
+ (time - planned_time).round
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,10 @@
1
+ require "network_rail/message/train_movement/base"
2
+
3
+ module NetworkRail
4
+ module Message
5
+ module TrainMovement
6
+ class Reinstatement < Base
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,31 @@
1
+ require "date"
2
+ require "network_rail/message/train_movement/movement"
3
+ require "network_rail/message/train_movement/activation"
4
+ require "network_rail/message/train_movement/cancellation"
5
+ require "network_rail/message/train_movement/change_of_identity"
6
+ require "network_rail/message/train_movement/change_of_origin"
7
+ require "network_rail/message/train_movement/reinstatement"
8
+
9
+ module NetworkRail
10
+ module Message
11
+ module TrainMovement
12
+ def self.factory(json_message)
13
+ target_class = message_type_to_class_mapping[json_message['header']['msg_type']]
14
+ target_class.factory(json_message)
15
+ end
16
+
17
+ private
18
+
19
+ def self.message_type_to_class_mapping
20
+ {
21
+ '0001' => Activation,
22
+ '0002' => Cancellation,
23
+ '0003' => Movement,
24
+ '0005' => Reinstatement,
25
+ '0006' => ChangeOfOrigin,
26
+ '0007' => ChangeOfIdentity
27
+ }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,82 @@
1
+ module NetworkRail
2
+ module Operators
3
+ def self.business_codes
4
+ {
5
+ all: 'ALL',
6
+ all_passenger: 'PASSENGER',
7
+ freight: 'GENERAL',
8
+ arriva_trains_wales: 'HL',
9
+ c2c: 'HT',
10
+ chiltern_railway: 'HO',
11
+ cross_country: 'EH',
12
+ devon_and_cornwall_railway: 'EN',
13
+ east_midlands_trains: 'EM',
14
+ east_coast: 'HB',
15
+ eurostar: 'GA',
16
+ first_capital_connect: 'EG',
17
+ first_great_western: 'EF',
18
+ first_hull_trains: 'PF',
19
+ first_scotrail: 'HA',
20
+ first_transpennine_xpress: 'EA',
21
+ gatwick_express: 'HV',
22
+ grand_central: 'EC',
23
+ heathrow_connect: 'EE',
24
+ heathrow_express: 'HM',
25
+ island_lines: 'HZ',
26
+ london_midland: 'EJ',
27
+ london_overground: 'EK',
28
+ london_underground_bakerloo_line: 'XC',
29
+ london_underground_district_line_wimbledon: 'XB',
30
+ london_underground_district_line_richmond: 'XE',
31
+ merseyrail: 'HE',
32
+ greater_anglia: 'EB',
33
+ nexus: 'PG',
34
+ north_yorkshire_moors_railway: 'PR',
35
+ northern_rail: 'ED',
36
+ south_west_trains: 'HY',
37
+ south_eastern: 'HU',
38
+ southern: 'HW',
39
+ virgin_trains: 'HF',
40
+ west_coast_railway: 'PA'
41
+ }
42
+ end
43
+
44
+ def self.numeric_codes
45
+ {
46
+ arriva_trains_wales: 71,
47
+ c2c: 79,
48
+ chiltern_railway: 74,
49
+ cross_country: 27,
50
+ devon_and_cornwall_railway: 34,
51
+ east_midlands_trains: 28,
52
+ east_coast: 61,
53
+ eurostar: 6,
54
+ first_capital_connect: 26,
55
+ first_great_western: 25,
56
+ first_hull_trains: 55,
57
+ first_scotrail: 60,
58
+ first_transpennine_xpress: 20,
59
+ gatwick_express: 81,
60
+ grand_central: 22,
61
+ heathrow_connect: 24,
62
+ heathrow_express: 86,
63
+ island_lines: 85,
64
+ london_midland: 29,
65
+ london_overground: 30,
66
+ london_underground_bakerloo_line: 91,
67
+ london_underground_district_line_wimbledon: 90,
68
+ london_underground_district_line_richmond: 93,
69
+ merseyrail: 64,
70
+ greater_anglia: 21,
71
+ nexus: 56,
72
+ north_yorkshire_moors_railway: 51,
73
+ northern_rail: 23,
74
+ south_west_trains: 84,
75
+ south_eastern: 80,
76
+ southern: 82,
77
+ virgin_trains: 65,
78
+ west_coast_railway: 50
79
+ }
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,3 @@
1
+ module NetworkRail
2
+ VERSION = "0.0.1.alpha"
3
+ end
@@ -0,0 +1,9 @@
1
+ require "network_rail/operators"
2
+ require "network_rail/client"
3
+ require "network_rail/configuration"
4
+ require "network_rail/version"
5
+ require "network_rail/message/train_movement"
6
+
7
+ module NetworkRail
8
+ extend Configuration
9
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'network_rail/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "network_rail"
8
+ spec.version = NetworkRail::VERSION
9
+ spec.authors = ["Steve Lorek"]
10
+ spec.email = ["steve@stevelorek.com"]
11
+ spec.description = %q{Provides a Ruby wrapper for consuming Network Rail Data Feeds}
12
+ spec.summary = %q{Ruby wrapper for consuming Network Rail Data Feeds}
13
+ spec.homepage = "http://github.com/slorek/network_rail"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency "i18n"
25
+ spec.add_development_dependency "active_support"
26
+ spec.add_runtime_dependency "stomp"
27
+ spec.add_runtime_dependency "json"
28
+ end