vatsim_online 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in vatsim_online.gemspec
4
+ gemspec
@@ -0,0 +1,5 @@
1
+ guard 'rspec', :version => 2 do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Svilen Vassilev
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.
@@ -0,0 +1,202 @@
1
+ # Vatsim Online
2
+
3
+ A Ruby gem for selectively pulling, parsing and displaying Vatsim online
4
+ stations data. Essentially it's a "Who's online" library, capable of displaying
5
+ online ATC and/or pilots for given airports, areas or globally. Stations are
6
+ returned as objects, exposing a rich set of attributes. Vatsim data is pulled
7
+ on preset intervals and cached locally to avoid flooding the servers.
8
+
9
+ ### Badges of (dis)honour
10
+
11
+ * Testing (Travis CI): [![Build Status](https://secure.travis-ci.org/tarakanbg/vatsim_online.png?branch=master)](http://travis-ci.org/tarakanbg/vatsim_online)
12
+ * Code Analysis (CodeClimate): [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/tarakanbg/vatsim_online)
13
+ * Dependencies: (Gemnasium) [![Gemnasium](https://gemnasium.com/tarakanbg/vatsim_online.png?travis)](https://gemnasium.com/tarakanbg/vatsim_online)
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ gem 'vatsim_online'
20
+
21
+ And then execute:
22
+
23
+ $ bundle
24
+
25
+ Or install it yourself as:
26
+
27
+ $ gem install vatsim_online
28
+
29
+ ## Usage
30
+
31
+ This gem provides one public method: `vatsim_online`, which can be applied to
32
+ any string (or variable containing a string) representing a full or partial ICAO
33
+ code. The provided ICAO code or fragment will be used as a search criteria and
34
+ matched against the current vatsim data.
35
+
36
+ For example if you want to retrieve all active stations (ATC positions and pilots)
37
+ for Vienna airport (ICAO: LOWW), then you can use:
38
+
39
+ ```ruby
40
+ # Attaching the method directly to a string:
41
+ "LOWW".vatsim_online
42
+
43
+ # Attaching the method to a variable containing a string:
44
+ icao = "LOWW"
45
+ icao.vatsim_online
46
+ ```
47
+ If you want to retrieve the currently active stations for an entire region
48
+ (FIR/ARTCC), then you can use the first 2-3 letters of the region's ICAO name.
49
+ For example if you want to pull all the stations active in Austria (ICAO code
50
+ for the FIR is LOVV), you can use `"LO"` as your ICAO search string: all Austrian
51
+ airports and ATC station callsigns start with `"LO"`:
52
+
53
+ ```ruby
54
+ # Attaching the method directly to a string:
55
+ "LO".vatsim_online
56
+
57
+ # Attaching the method to a variable containing a string:
58
+ icao = "LO"
59
+ icao.vatsim_online
60
+ ```
61
+ When parsing the pilot stations for particular airport or area, the library will
62
+ return the pilots that are flying **to or from** the given area or airport,
63
+ not the current enroute stations. The discovery algorithm is based on **origin
64
+ and destination**.
65
+
66
+
67
+ ### Anatomy of method returns
68
+
69
+ The `vatsim_online` method returns a **hash** of 2 elements: the matching atc
70
+ stations and pilots stations. Each of those is an **array**, cosnsisting of
71
+ station **objects**. Each of these objects includes a number of **attributes**:
72
+
73
+ ```ruby
74
+ icao_vatsim_online # => {:atc => [a1, a2, a3 ...], :pilots => [p1, p2, p3 ...]}
75
+
76
+ icao_vatsim_online[:atc] #=> [a1, a2, a3 ...]
77
+ icao_vatsim_online[:pilots] #=> [p1, p2, p3 ...]
78
+
79
+ icao_vatsim_online[:atc].first #=> a1
80
+ icao_vatsim_online[:pilots].first #=> p1
81
+
82
+ a1.callsign #=> "LQSA_TWR"
83
+ a1.frequency #=> "118.25"
84
+ a1.name #=> "Svilen Vassilev"
85
+ ...
86
+
87
+ p1.callsign #=> "ACH217S"
88
+ p1.departure #=> "LQSA"
89
+ p1.destination #=> "LDSP"
90
+ p1.remarks #=> "/V/ RMK/CHARTS"
91
+ ...
92
+ ```
93
+
94
+ ### Station attributes
95
+
96
+ Here's a complete list of the station object attributes that can be accessed:
97
+
98
+ * callsign
99
+ * name
100
+ * role
101
+ * frequency
102
+ * altitude
103
+ * groundspeed
104
+ * aircraft
105
+ * departure
106
+ * destination
107
+ * rating
108
+ * facility
109
+ * remarks
110
+ * route
111
+ * atis
112
+ * logon
113
+
114
+ ### Customizing the request
115
+
116
+ The `vatsim online` method can be customized by passing in a hash-style collection
117
+ of arguments. The currently supported arguments and their defaults are:
118
+
119
+ * :atc => true (Possible values: true, false. Default value: true)
120
+ * :pilots => true (Possible values: true, false. Default value: true)
121
+
122
+ Both options can be used to exclude all ATC or pilots stations respectively from
123
+ the request, in order to speed it up and avoid processing useless data.
124
+
125
+ **Examples:**
126
+
127
+ ```ruby
128
+ # Lets exclude all ATC from our request and get the pilots only
129
+ "LO".vatsim_online(:atc => false)[:pilots] #=> [p1, p2, p3...]
130
+
131
+ # Lets exclude all pilots from our request and get the ATC only
132
+ "LO".vatsim_online(:pilots => false)[:atc] #=> [a1, a2, a3...]
133
+
134
+ "LO".vatsim_online(:atc => false)[:pilots].first.callsign #=> "ACH0838"
135
+ "LO".vatsim_online(:pilots => false)[:atc].first.callsign #=> "LOVV_CTR"
136
+
137
+ ```
138
+
139
+ ### Example of Ruby on Rails implementation
140
+
141
+ Here's a possible scenario of using this gem in a Ruby on Rails application.
142
+ Verbosity is kept on purpose for clarity.
143
+
144
+ **In your controller:**
145
+ ```ruby
146
+ def index
147
+ # We want to retrieve all Austrian online stations (ATC and pilots)
148
+ icao = "LO"
149
+ stations = icao.vatsim_online
150
+
151
+ # Now we will assign the ATCs and the pilots to separate instance variables,
152
+ # to be able to loop through them separately in the view
153
+ @atc = stations[:atc]
154
+ @pilots = stations[:pilots]
155
+ end
156
+ ```
157
+
158
+ **In your view (HAML is used for clarity):**
159
+
160
+ ```haml
161
+ - for atc in @atc
162
+ %li
163
+ = atc.callsign
164
+ = atc.frequency
165
+ = atc.rating
166
+ = atc.name
167
+ = atc.atis
168
+
169
+ - for pilot in @pilots
170
+ %li
171
+ = pilot.callsign
172
+ = atc.name
173
+ = atc.origin
174
+ = atc.destination
175
+ = atc.route
176
+ = atc.altitude
177
+ = atc.groundspeed
178
+ = atc.remarks
179
+ ```
180
+
181
+ ### Notes
182
+
183
+ * Vatsim status and data files are cached locally to reduce the load on vatsim
184
+ servers. Random server is chosen to retrieve the data each time. By default the
185
+ status file is updated once every 4 hours and the data file once every 3 minutes
186
+ regardless of the number of incoming requests.
187
+ * The data is cached in your default TEMP directory (OS specific)
188
+ * All the data retrieval and caching logic is encapsulated in a separate class
189
+ `VatsimTools::DataDownloader` which can be mixed in other applications and
190
+ libraries too.
191
+ * The ICAO string used as a search criteria **is not** case sensitive
192
+ * Pilot stations returned are based on origin and destination airports, the
193
+ current algorithm does not evaluate enroute flights.
194
+
195
+ ## Contributing
196
+
197
+ 1. Fork it
198
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
199
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
200
+ 4. Make sure all tests are passing!
201
+ 5. Push to the branch (`git push origin my-new-feature`)
202
+ 6. Create new Pull Request
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task default: :spec
@@ -0,0 +1,16 @@
1
+ %w{vatsim_online/version vatsim_online/station vatsim_online/data_downloader
2
+ vatsim_online/station_parser}.each { |lib| require lib }
3
+
4
+ class String
5
+ def vatsim_online(args={})
6
+ VatsimOnline.vatsim_online(self, args)
7
+ end
8
+ end
9
+
10
+ module VatsimOnline
11
+
12
+ def self.vatsim_online(icao, args)
13
+ VatsimTools::StationParser.new(icao,args).sorted_station_objects
14
+ end
15
+
16
+ end
@@ -0,0 +1,58 @@
1
+ module VatsimTools
2
+
3
+ class DataDownloader
4
+
5
+ %w{curb tempfile time_diff tmpdir csv}.each { |lib| require lib }
6
+
7
+ STATUS_URL = "http://status.vatsim.net/status.txt"
8
+ LOCAL_STATUS = "#{Dir.tmpdir}/vatsim_status.txt"
9
+ LOCAL_DATA = "#{Dir.tmpdir}/vatsim_data.txt"
10
+
11
+ def initialize
12
+ data_file
13
+ end
14
+
15
+ def create_status_tempfile
16
+ status = Tempfile.new('vatsim_status')
17
+ File.rename status.path, LOCAL_STATUS
18
+ File.open(LOCAL_STATUS, "w+") {|f| f.write(Curl::Easy.perform(STATUS_URL).body_str) }
19
+ end
20
+
21
+ def read_status_tempfile
22
+ status = File.open(LOCAL_STATUS)
23
+ difference = Time.diff(status.ctime, Time.now)[:hour]
24
+ difference > 3 ? create_status_tempfile : status.read
25
+ end
26
+
27
+ def status_file
28
+ File.exists?(LOCAL_STATUS) ? read_status_tempfile : create_status_tempfile
29
+ LOCAL_STATUS
30
+ end
31
+
32
+ def servers
33
+ urls = []
34
+ CSV.foreach(status_file, :col_sep =>'=') {|row| urls << row[1] if row[0] == "url0"}
35
+ urls
36
+ end
37
+
38
+ def create_local_data_file
39
+ data = Tempfile.new('vatsim_data', :encoding => 'iso-8859-15')
40
+ File.rename data.path, LOCAL_DATA
41
+ data = Curl::Easy.perform(servers.sample).body_str.gsub(/["]/, '\s').force_encoding('iso-8859-15')
42
+ File.open(LOCAL_DATA, "w+") {|f| f.write(data)}
43
+ end
44
+
45
+ def read_local_datafile
46
+ data = File.open(LOCAL_DATA)
47
+ difference = Time.diff(data.ctime, Time.now)[:minute]
48
+ difference > 2 ? create_local_data_file : data.read
49
+ end
50
+
51
+ def data_file
52
+ File.exists?(LOCAL_DATA) ? read_local_datafile : create_local_data_file
53
+ LOCAL_DATA
54
+ end
55
+
56
+ end
57
+
58
+ end
@@ -0,0 +1,27 @@
1
+ module VatsimTools
2
+ class Station
3
+ attributes = %w{callsign name role frequency altitude groundspeed aircraft
4
+ departure destination rating facility remarks route atis logon}
5
+ attributes.each {|attribute| attr_accessor attribute.to_sym }
6
+
7
+
8
+ def initialize(station)
9
+ @callsign = station[0]
10
+ @name = station[2]
11
+ @role = station[3]
12
+ @frequency = station[4]
13
+ @altitude = station[7]
14
+ @groundspeed = station[8]
15
+ @aircraft = station[9]
16
+ @departure = station[11]
17
+ @destination = station[13]
18
+ @rating = station[16]
19
+ @facility = station[18]
20
+ @remarks = station[29]
21
+ @route = station[30]
22
+ @atis = station[35]
23
+ @logon = station[37]
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,51 @@
1
+ module VatsimTools
2
+
3
+ class StationParser
4
+
5
+ %w{tmpdir csv}.each { |lib| require lib }
6
+ require_relative "data_downloader"
7
+ require_relative "station"
8
+
9
+ attr_accessor :role
10
+ attr_accessor :icao
11
+
12
+ LOCAL_DATA = "#{Dir.tmpdir}/vatsim_data.txt"
13
+
14
+ def initialize(icao, args = nil)
15
+ VatsimTools::DataDownloader.new
16
+ args.class == Hash ? @role = determine_role(args) : @role = "all"
17
+ @icao = icao.upcase
18
+ end
19
+
20
+ def determine_role(args)
21
+ args[:atc] == false ? role = "pilot" : role = "all"
22
+ args[:pilots] == false ? role = "atc" : role = role
23
+ role = "all" if args[:pilots] == false && args[:atc] == false
24
+ role
25
+ end
26
+
27
+ def stations
28
+ stations = []
29
+ CSV.foreach(LOCAL_DATA, :col_sep =>':', encoding: "iso-8859-15") do |row|
30
+ callsign, origin, destination, client = row[0].to_s, row[11].to_s, row[13].to_s, row[3].to_s
31
+ stations << row if (callsign[0...@icao.length] == @icao && client == "ATC") unless @role == "pilot"
32
+ stations << row if (origin[0...@icao.length] == @icao || destination[0...@icao.length] == @icao) unless @role == "atc"
33
+ end
34
+ stations
35
+ end
36
+
37
+ def station_objects
38
+ station_objects= []
39
+ stations.each {|station| station_objects << VatsimTools::Station.new(station) }
40
+ station_objects
41
+ end
42
+
43
+ def sorted_station_objects
44
+ atc = []; pilots = []
45
+ station_objects.each {|sobj| sobj.role == "ATC" ? atc << sobj : pilots << sobj}
46
+ {:atc => atc, :pilots => pilots}
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,3 @@
1
+ module VatsimOnline
2
+ VERSION = "0.1"
3
+ end
@@ -0,0 +1,91 @@
1
+ require 'spec_helper.rb'
2
+ require 'data_downloader_spec_helper.rb'
3
+
4
+ describe VatsimTools::DataDownloader do
5
+
6
+ target = VatsimTools::DataDownloader
7
+ LOCAL_STATUS = "#{Dir.tmpdir}/vatsim_status.txt"
8
+ LOCAL_DATA = "#{Dir.tmpdir}/vatsim_data.txt"
9
+
10
+ describe "create_status_tempfile" do
11
+ it "should create a file" do
12
+ delete_local_files
13
+ File.exists?(LOCAL_STATUS).should be false
14
+ target.new.create_status_tempfile
15
+ File.exists?(LOCAL_STATUS).should be true
16
+ File.open(LOCAL_STATUS).path.should eq("#{Dir.tmpdir}/vatsim_status.txt")
17
+ File.open(LOCAL_STATUS).size.should be > 100
18
+ end
19
+ end
20
+
21
+ describe "read_status_tempfile" do
22
+ it "should confirm a file exists" do
23
+ target.new.read_status_tempfile
24
+ File.exists?(LOCAL_STATUS).should be true
25
+ File.open(LOCAL_STATUS).size.should be > 100
26
+ end
27
+ end
28
+
29
+ describe "status_file" do
30
+ it "should return status.txt path" do
31
+ delete_local_files
32
+ File.exists?(LOCAL_STATUS).should be false
33
+ target.new.status_file.class.should eq(String)
34
+ target.new.status_file.should include("vatsim_status.txt")
35
+ target.new.status_file.should eq(LOCAL_STATUS)
36
+ target.new.status_file.should eq("#{Dir.tmpdir}/vatsim_status.txt")
37
+ File.exists?(LOCAL_STATUS).should be true
38
+ end
39
+ end
40
+
41
+ describe "servers" do
42
+ it "should contain an array of server URLs" do
43
+ File.exists?(LOCAL_STATUS).should be true
44
+ target.new.servers.class.should eq(Array)
45
+ target.new.servers.size.should eq(5)
46
+ end
47
+ end
48
+
49
+ describe "create_local_data_file" do
50
+ it "should confirm a file exists" do
51
+ target.new.create_local_data_file
52
+ File.exists?(LOCAL_DATA).should be true
53
+ File.open(LOCAL_DATA).path.should eq("#{Dir.tmpdir}/vatsim_data.txt")
54
+ File.open(LOCAL_DATA).size.should be > 100
55
+ end
56
+ end
57
+
58
+ describe "read_local_datafile" do
59
+ it "should confirm a file exists" do
60
+ target.new.read_local_datafile
61
+ File.exists?(LOCAL_DATA).should be true
62
+ File.open(LOCAL_DATA).size.should be > 100
63
+ end
64
+ end
65
+
66
+ describe "data_file" do
67
+ it "should contain file path" do
68
+ delete_local_files
69
+ File.exists?(LOCAL_DATA).should be false
70
+ target.new.data_file.class.should eq(String)
71
+ target.new.data_file.should include("vatsim_data.txt")
72
+ target.new.data_file.should eq(LOCAL_DATA)
73
+ target.new.data_file.should eq("#{Dir.tmpdir}/vatsim_data.txt")
74
+ File.exists?(LOCAL_DATA).should be true
75
+ end
76
+ end
77
+
78
+ describe "new" do
79
+ it "should return" do
80
+ delete_local_files
81
+ File.exists?(LOCAL_DATA).should be false
82
+ File.exists?(LOCAL_STATUS).should be false
83
+ target.new
84
+ File.exists?(LOCAL_DATA).should be true
85
+ File.exists?(LOCAL_STATUS).should be true
86
+ File.open(LOCAL_DATA).size.should be > 100
87
+ File.open(LOCAL_STATUS).size.should be > 100
88
+ end
89
+ end
90
+
91
+ end