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.
- data/.gitignore +17 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/Guardfile +5 -0
- data/LICENSE +22 -0
- data/README.md +202 -0
- data/Rakefile +7 -0
- data/lib/vatsim_online.rb +16 -0
- data/lib/vatsim_online/data_downloader.rb +58 -0
- data/lib/vatsim_online/station.rb +27 -0
- data/lib/vatsim_online/station_parser.rb +51 -0
- data/lib/vatsim_online/version.rb +3 -0
- data/spec/data_downloader_spec.rb +91 -0
- data/spec/data_downloader_spec_helper.rb +4 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/station_parser_spec.rb +111 -0
- data/spec/station_parser_spec_helper.rb +7 -0
- data/spec/vatsim_data.txt +608 -0
- data/spec/vatsim_online_spec.rb +54 -0
- data/vatsim_online.gemspec +26 -0
- metadata +192 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
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.
|
data/README.md
ADDED
@@ -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): [](http://travis-ci.org/tarakanbg/vatsim_online)
|
12
|
+
* Code Analysis (CodeClimate): [](https://codeclimate.com/github/tarakanbg/vatsim_online)
|
13
|
+
* Dependencies: (Gemnasium) [](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
|
data/Rakefile
ADDED
@@ -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,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
|