fcc 1.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/Gemfile +2 -14
- data/Gemfile.lock +15 -15
- data/README.md +70 -24
- data/VERSION +1 -1
- data/fcc.gemspec +3 -3
- data/lib/fcc/station/cache.rb +20 -14
- data/lib/fcc/station/extended_info.rb +29 -22
- data/lib/fcc/station/index.rb +1 -1
- data/lib/fcc/station/info.rb +9 -2
- data/lib/fcc/station/lms_data.rb +139 -0
- data/lib/fcc/station/parsers/extended_info.rb +78 -0
- data/lib/fcc/station/parsers/lms_data.rb +13 -0
- data/lib/fcc/station/record_delegate.rb +121 -0
- data/lib/fcc/station/record_group.rb +49 -0
- data/lib/fcc/station/result.rb +178 -0
- data/lib/fcc/station.rb +9 -71
- data/lib/fcc.rb +22 -1
- data/lib/version.rb +1 -1
- metadata +47 -28
- data/lib/fcc/station/extended_info/parser.rb +0 -66
- data/lib/fcc/station/result_delegate.rb +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a40f16f549528352153a0d8fb4c1bed24c966b9c3647ae7aee71f46b419920f
|
4
|
+
data.tar.gz: db47ff830e6c821ffce6fa3c07bd1bb75f34dfb60ca1ffd85566cf6cb95ce7a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 14d033f2b67c0d3a1e8528fd2d4d787c3bf1be01e2f438feb0df731a0d39e5ab6695e721e4b292e2561efb6374591080186b20eff77b82c26f3fcb8df5556ddd
|
7
|
+
data.tar.gz: 0a9da9bf1d013f096a6c9eb4cc798b8cf98546024702e7c774fe0303d82800a6f1235a0303438b6162585a6673bcd2d6cb7d7e0aadf0b4406bcfa87dc8273f2b
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
fcc changelog
|
2
|
+
|
3
|
+
## [1.3.0](https://github.com/jkeen/fcc/compare/v1.3.0...v1.2.1) (2022-07-10)
|
4
|
+
|
5
|
+
- Combine data from multiple sources to get a full picture of the station data (transition api, public api, LMS csv files)
|
6
|
+
- Now includes related transmitters, repeaters, low power stations, etc
|
data/Gemfile
CHANGED
@@ -5,19 +5,7 @@ git_source(:github) { |repo_name| 'https://github.com/jkeen/fcc' }
|
|
5
5
|
# Specify your gem's dependencies in popularity.gemspec
|
6
6
|
gemspec
|
7
7
|
|
8
|
-
#
|
9
|
-
# source "http://rubygems.org"
|
10
|
-
# # Add dependencies required to use your gem here.
|
11
|
-
# # Example:
|
12
|
-
# # gem "activesupport", ">= 2.3.5"
|
13
|
-
#
|
14
|
-
# # Add dependencies to develop your gem here.
|
15
|
-
# # Include everything needed to run rake, tests, features, etc.
|
16
|
-
#
|
17
|
-
# gem "nokogiri"
|
18
|
-
#
|
19
8
|
# group :development do
|
20
|
-
# gem "
|
21
|
-
# gem "
|
22
|
-
# gem "jeweler"
|
9
|
+
# gem "awesome_print", require: true
|
10
|
+
# gem "byebug", require: true
|
23
11
|
# end
|
data/Gemfile.lock
CHANGED
@@ -1,32 +1,34 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
fcc (1.
|
4
|
+
fcc (1.3.0)
|
5
5
|
activesupport (>= 6.1)
|
6
6
|
httparty (~> 0.18)
|
7
|
+
lightly (~> 0.3.3)
|
8
|
+
logger
|
9
|
+
rubyzip
|
7
10
|
|
8
11
|
GEM
|
9
12
|
remote: https://rubygems.org/
|
10
13
|
specs:
|
11
|
-
activesupport (
|
14
|
+
activesupport (7.0.3)
|
12
15
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
13
16
|
i18n (>= 1.6, < 2)
|
14
17
|
minitest (>= 5.1)
|
15
18
|
tzinfo (~> 2.0)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
concurrent-ruby (1.1.7)
|
20
|
-
diff-lcs (1.4.4)
|
21
|
-
httparty (0.18.1)
|
19
|
+
concurrent-ruby (1.1.10)
|
20
|
+
diff-lcs (1.5.0)
|
21
|
+
httparty (0.20.0)
|
22
22
|
mime-types (~> 3.0)
|
23
23
|
multi_xml (>= 0.5.2)
|
24
|
-
i18n (1.
|
24
|
+
i18n (1.10.0)
|
25
25
|
concurrent-ruby (~> 1.0)
|
26
|
-
|
26
|
+
lightly (0.3.3)
|
27
|
+
logger (1.5.0)
|
28
|
+
mime-types (3.4.1)
|
27
29
|
mime-types-data (~> 3.2015)
|
28
|
-
mime-types-data (3.
|
29
|
-
minitest (5.
|
30
|
+
mime-types-data (3.2022.0105)
|
31
|
+
minitest (5.16.2)
|
30
32
|
multi_xml (0.6.0)
|
31
33
|
rake (12.3.3)
|
32
34
|
rspec (3.9.0)
|
@@ -42,17 +44,15 @@ GEM
|
|
42
44
|
diff-lcs (>= 1.2.0, < 2.0)
|
43
45
|
rspec-support (~> 3.9.0)
|
44
46
|
rspec-support (3.9.4)
|
47
|
+
rubyzip (2.3.2)
|
45
48
|
tzinfo (2.0.4)
|
46
49
|
concurrent-ruby (~> 1.0)
|
47
|
-
zeitwerk (2.4.2)
|
48
50
|
|
49
51
|
PLATFORMS
|
50
52
|
ruby
|
51
53
|
|
52
54
|
DEPENDENCIES
|
53
|
-
awesome_print
|
54
55
|
bundler (~> 2.1)
|
55
|
-
byebug
|
56
56
|
fcc!
|
57
57
|
rake (~> 12.3.3)
|
58
58
|
rspec (~> 3.9.0)
|
data/README.md
CHANGED
@@ -2,6 +2,9 @@
|
|
2
2
|
|
3
3
|
Query the FCC database for fm, am, and tv information.
|
4
4
|
|
5
|
+
|
6
|
+
This gem uses the new FCC apis and the old FCC apis and combines records to be the most complete version possible. The old (transition.fcc.gov) API provides way more data than the new one does.
|
7
|
+
|
5
8
|
## Install
|
6
9
|
|
7
10
|
```
|
@@ -12,35 +15,78 @@ gem install fcc
|
|
12
15
|
|
13
16
|
* Ruby 2.0.0 or higher
|
14
17
|
|
15
|
-
## Examples
|
16
|
-
|
17
18
|
#### Get station information by call sign
|
18
19
|
|
19
20
|
```ruby
|
20
21
|
station = FCC::Station.find(:fm, "KOOP")
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
station.
|
25
|
-
station.
|
26
|
-
station.
|
27
|
-
station.
|
28
|
-
station.
|
29
|
-
station.
|
30
|
-
station.
|
31
|
-
station.
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
station.
|
36
|
-
station.
|
37
|
-
station.
|
38
|
-
station.
|
39
|
-
station.
|
40
|
-
station.
|
41
|
-
station.
|
42
|
-
station.
|
43
|
-
|
23
|
+
if station.exists? && station.licensed?
|
24
|
+
#Basic attributes, available quickly because the FCC actually caches these in a CDN:
|
25
|
+
station.id #=> 65320
|
26
|
+
station.status #=> LICENSED
|
27
|
+
station.rf_channel #=> 219
|
28
|
+
station.license_expiration_date #=> "08/01/2021"
|
29
|
+
station.facility_type #=> ED
|
30
|
+
station.frequency #=> 91.7
|
31
|
+
station.contact #=> <struct FCC::Station::Contact>
|
32
|
+
station.owner #=> <struct FCC::Station::Contact>
|
33
|
+
station.community #=> <struct FCC::Station::Community city="HORNSBY", state="TX">
|
34
|
+
|
35
|
+
# Extended attributes, takes several seconds to load initially because the FCC is running this endpoint on a 1960s era mainframe operated by trained hamsters.
|
36
|
+
station.station_class #=> A
|
37
|
+
station.signal_strength #=> 3.0 kW
|
38
|
+
station.antenna_type #=> ND
|
39
|
+
station.effective_radiated_power #=> 3.0 kW
|
40
|
+
station.haat_horizontal #=> 26.0
|
41
|
+
station.haat_vertical #=> 26.0
|
42
|
+
station.latitude #=> "30.266861111111112"
|
43
|
+
station.longitude #=> "-97.67444444444445"
|
44
|
+
station.file_number #=> BLED-19950103KA
|
45
|
+
|
46
|
+
|
47
|
+
station.to_json #=>
|
48
|
+
[{:band=>"FM",
|
49
|
+
:signal_strength=>"3.0 kW",
|
50
|
+
:latitude=>"30.266861111111112",
|
51
|
+
:longitude=>"-97.67444444444445",
|
52
|
+
:station_class=>"A",
|
53
|
+
:file_number=>"BLED-19950103KA",
|
54
|
+
:effective_radiated_power=>"3.0 kW",
|
55
|
+
:haat_horizontal=>"26.0",
|
56
|
+
:haat_vertical=>"26.0",
|
57
|
+
:antenna_type=>"ND",
|
58
|
+
:operating_hours=>nil,
|
59
|
+
:licensed_to=>"TEXAS EDUCATIONAL BROADCASTING CO-OPERATIVE, INC.",
|
60
|
+
:city=>"HORNSBY",
|
61
|
+
:state=>"TX",
|
62
|
+
:country=>"US",
|
63
|
+
:id=>"65320",
|
64
|
+
:call_sign=>"KOOP",
|
65
|
+
:status=>"LICENSED",
|
66
|
+
:rf_channel=>"219",
|
67
|
+
:license_expiration_date=>"08/01/2029",
|
68
|
+
:facility_type=>"ED",
|
69
|
+
:frequency=>"91.7",
|
70
|
+
:contact=>
|
71
|
+
{:name=>"Federico Pacheco",
|
72
|
+
:title=>"",
|
73
|
+
:address=>"3823 Airport Blvd. Suite B",
|
74
|
+
:address2=>"",
|
75
|
+
:city=>"Austin",
|
76
|
+
:state=>"TX",
|
77
|
+
:zip_code=>"78722",
|
78
|
+
:phone=>"(512)472-1369",
|
79
|
+
:fax=>"",
|
80
|
+
:email=>"federico.pacheco@koop.org",
|
81
|
+
:website=>"koop.org"},
|
82
|
+
:owner=>{:name=>"TEXAS EDUCATIONAL BROADCASTING CO-OPERATIVE, INC.", :city=>"AUSTIN", :state=>"TX", :phone=>"(512)472-1369"},
|
83
|
+
:community=>{:city=>"HORNSBY", :state=>"TX"}}]
|
84
|
+
```
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
### Caching
|
89
|
+
Extended attributes take several seconds to load from transition.fcc.gov. In order to work around this, we query the entire dataset and then cache the result locally for 3 days (using the lightly gem). To use your own cache, set `FCC.cache=` to your cache class (Rails.cache, maybe?) which should have a #fetch method that should take a key and a block like `cache.fetch(key) { // yield for expensive fetch }}`.
|
44
90
|
|
45
91
|
### Get all station call signs on a particular service (:fm, :am, :tv)
|
46
92
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
1.2.1
|
data/fcc.gemspec
CHANGED
@@ -6,7 +6,6 @@ Gem::Specification.new do |spec|
|
|
6
6
|
spec.name = %q{fcc}
|
7
7
|
spec.version = FCC::VERSION
|
8
8
|
spec.authors = ["Jeff Keen"]
|
9
|
-
spec.date = %q{2011-01-30}
|
10
9
|
spec.description = %q{}
|
11
10
|
spec.email = %q{jeff@keen.me}
|
12
11
|
spec.homepage = "http://github.com/jkeen/fcc"
|
@@ -20,9 +19,10 @@ Gem::Specification.new do |spec|
|
|
20
19
|
spec.summary = %q{Searches the FCC's FM, AM, and TV databases}
|
21
20
|
spec.add_dependency "activesupport", ">= 6.1"
|
22
21
|
spec.add_dependency "httparty", "~> 0.18"
|
22
|
+
spec.add_dependency "lightly", "~> 0.3.3"
|
23
|
+
spec.add_dependency "rubyzip"
|
24
|
+
spec.add_dependency "logger"
|
23
25
|
spec.add_development_dependency "bundler", "~> 2.1"
|
24
26
|
spec.add_development_dependency "rake", "~> 12.3.3"
|
25
27
|
spec.add_development_dependency 'rspec', '~> 3.9.0'
|
26
|
-
spec.add_development_dependency "byebug", ">= 0"
|
27
|
-
spec.add_development_dependency "awesome_print", ">= 0"
|
28
28
|
end
|
data/lib/fcc/station/cache.rb
CHANGED
@@ -1,28 +1,34 @@
|
|
1
1
|
require 'httparty'
|
2
|
-
|
3
|
-
|
2
|
+
require_relative './parsers/extended_info'
|
3
|
+
require 'lightly'
|
4
|
+
require 'logger'
|
4
5
|
|
5
6
|
module FCC
|
6
7
|
module Station
|
7
8
|
class Cache
|
8
9
|
attr_reader :store
|
9
10
|
|
10
|
-
def initialize
|
11
|
-
@
|
12
|
-
|
11
|
+
def initialize
|
12
|
+
@lightly = Lightly.new(life: '3d', hash: true).tap do |cache|
|
13
|
+
cache.prune
|
14
|
+
end
|
13
15
|
end
|
14
16
|
|
15
|
-
def
|
16
|
-
|
17
|
+
def flush
|
18
|
+
@lightly.flush
|
17
19
|
end
|
18
20
|
|
19
|
-
def
|
20
|
-
#
|
21
|
-
@
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
def fetch(key)
|
22
|
+
FCC.log("Retreiving #{key} from cache")
|
23
|
+
@lightly.get(key.to_s) do
|
24
|
+
FCC.log "Loading up the cache with results for key: #{key}. This might take a minute…"
|
25
|
+
value = yield
|
26
|
+
if value && value.present?
|
27
|
+
value
|
28
|
+
else
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
end
|
26
32
|
end
|
27
33
|
end
|
28
34
|
end
|
@@ -1,26 +1,27 @@
|
|
1
1
|
require 'httparty'
|
2
|
-
|
3
|
-
require_relative './extended_info/parser'
|
2
|
+
require_relative './parsers/extended_info'
|
4
3
|
|
5
4
|
module FCC
|
6
5
|
module Station
|
7
6
|
class ExtendedInfo
|
8
7
|
include HTTParty
|
9
8
|
attr_accessor :results, :service
|
10
|
-
|
9
|
+
|
10
|
+
base_uri 'https://transition.fcc.gov/fcc-bin/'
|
11
11
|
|
12
12
|
def initialize(service)
|
13
13
|
@service = service
|
14
14
|
@options = {
|
15
15
|
follow_redirects: true
|
16
16
|
}
|
17
|
+
|
17
18
|
@query = {
|
18
19
|
# state: nil,
|
19
20
|
# call: nil,
|
20
21
|
# city: nil,
|
21
22
|
# arn: nil,
|
22
|
-
serv: service.to_s.downcase,
|
23
|
-
|
23
|
+
# serv: service.to_s.downcase, # Only return primary main records, no backup transmitters, etc… for now
|
24
|
+
status: 3, # licensed records only
|
24
25
|
# freq: @service.to_sym == :fm ? '87.1' : '530',
|
25
26
|
# fre2: @service.to_sym == :fm ? '107.9' : '1700',
|
26
27
|
# facid: nil,
|
@@ -42,33 +43,39 @@ module FCC
|
|
42
43
|
end
|
43
44
|
|
44
45
|
def all_results
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
46
|
+
|
47
|
+
begin
|
48
|
+
cache_key = "#{self.class.instance_variable_get('@default_options')[:base_uri]}/#{@service.to_s.downcase}q"
|
49
|
+
FCC.cache.fetch cache_key do
|
50
|
+
response = self.class.get("/#{service.to_s.downcase}q", @options.merge(query: @query))
|
51
|
+
FCC.log(response.request.uri.to_s.gsub('&list=4', '&list=0'))
|
52
|
+
response.parsed_response
|
53
|
+
end
|
54
|
+
rescue StandardError => e
|
55
|
+
FCC.error(e.message)
|
56
|
+
FCC.error(e.backtrace)
|
50
57
|
end
|
51
58
|
end
|
52
59
|
|
53
60
|
def find(id_or_call_sign)
|
54
61
|
if id_or_call_sign =~ /^\d+$/
|
55
62
|
id = id_or_call_sign
|
63
|
+
all_results.filter { |r| r[:fcc_id].to_s == id.to_s } || find_directly({ facid: id_or_call_sign })
|
56
64
|
else
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
begin
|
61
|
-
FCC::Station.extended_info_cache(@service).find(id)
|
62
|
-
rescue Exception => e
|
63
|
-
response = self.class.get("/#{service.to_s.downcase}q", @options.merge(query: @query.merge(facid: id)))
|
64
|
-
puts response.request.uri.to_s.gsub('&list=4', '&list=0')
|
65
|
-
result = response.first
|
66
|
-
result['source_url'] = response.request.uri.to_s.gsub('&list=4', '&list=0')
|
67
|
-
result
|
65
|
+
all_results.filter { |r|
|
66
|
+
r[:call_sign].to_s == id_or_call_sign.to_s || r[:call_sign].to_s.upcase =~ Regexp.new("^#{id_or_call_sign.upcase}[-—–][A-Z0-9]+$")
|
67
|
+
} || find_directly({ call: id_or_call_sign })
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
|
-
|
71
|
+
def find_directly(options)
|
72
|
+
|
73
|
+
response = self.class.get("/#{service.to_s.downcase}q", @options.merge(query: @query.merge(options)))
|
74
|
+
FCC.log response.request.uri.to_s.gsub('&list=4', '&list=0')
|
75
|
+
response.parsed_response
|
76
|
+
end
|
77
|
+
|
78
|
+
parser FCC::Station::Parsers::ExtendedInfo
|
72
79
|
end
|
73
80
|
end
|
74
81
|
end
|
data/lib/fcc/station/index.rb
CHANGED
data/lib/fcc/station/info.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'httparty'
|
2
|
-
require 'byebug'
|
3
2
|
|
4
3
|
module FCC
|
5
4
|
module Station
|
@@ -21,7 +20,15 @@ module FCC
|
|
21
20
|
end
|
22
21
|
|
23
22
|
response = self.class.get("/api/service/#{service.to_s.downcase}/facility/id/#{id}.json")
|
24
|
-
|
23
|
+
|
24
|
+
begin
|
25
|
+
body = response['results']['facility']
|
26
|
+
body['band'] = service.to_s.upcase.to_s
|
27
|
+
|
28
|
+
body
|
29
|
+
rescue StandardError => e
|
30
|
+
return nil
|
31
|
+
end
|
25
32
|
end
|
26
33
|
end
|
27
34
|
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'open-uri'
|
3
|
+
require 'net/http'
|
4
|
+
require 'uri'
|
5
|
+
require 'date'
|
6
|
+
require_relative './parsers/lms_data'
|
7
|
+
|
8
|
+
module FCC
|
9
|
+
module Station
|
10
|
+
class LmsData
|
11
|
+
BASE_URI = 'https://enterpriseefiling.fcc.gov/dataentry/api/download/dbfile'
|
12
|
+
# include HTTParty
|
13
|
+
|
14
|
+
def find_all_related(call_sign: )
|
15
|
+
stations = find_related_stations(call_sign: call_sign)
|
16
|
+
translators = find_translators_for(call_sign: stations.keys)
|
17
|
+
stations.merge(translators)
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_translators_for(call_sign: )
|
21
|
+
call_signs = [call_sign].flatten
|
22
|
+
|
23
|
+
records = common_station_data.entries.select do |entry|
|
24
|
+
call_signs.any? { |call_sign| call_signs_match?(call_sign, entry['callsign']) }
|
25
|
+
end
|
26
|
+
|
27
|
+
facility_ids = records.map { |r| r['facility_id'] }.uniq.compact
|
28
|
+
|
29
|
+
matched_facilities = facilities.entries.select do |facility|
|
30
|
+
facility_ids.include?(facility['primary_station']) #{}|| facility_ids.include?(facility['facility_id'])
|
31
|
+
end
|
32
|
+
|
33
|
+
{}.tap do |hash|
|
34
|
+
matched_facilities.each do |record|
|
35
|
+
hash[record['callsign']] = record['facility_id']
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def find_related_stations(call_sign: )
|
41
|
+
call_signs = [call_sign].flatten
|
42
|
+
|
43
|
+
records = common_station_data.entries.select do |entry|
|
44
|
+
call_signs.any? { call_signs_match?(call_sign, entry['callsign']) }
|
45
|
+
end
|
46
|
+
|
47
|
+
correlated_app_ids = records.map { |m| m['eeo_application_id'] }
|
48
|
+
matches = common_station_data.entries.select do |entry|
|
49
|
+
correlated_app_ids.include?(entry['eeo_application_id'])
|
50
|
+
end
|
51
|
+
|
52
|
+
{}.tap do |hash|
|
53
|
+
matches.each do |record|
|
54
|
+
hash[record['callsign']] = record['facility_id']
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def facilities
|
60
|
+
@facilities ||= read(:facility)
|
61
|
+
end
|
62
|
+
|
63
|
+
def common_station_data
|
64
|
+
@common_station_data ||= read(:common_station)
|
65
|
+
end
|
66
|
+
|
67
|
+
def find_facilities(facility_ids:, call_signs: [])
|
68
|
+
matched_facilities = facilities.entries.select do |facility|
|
69
|
+
facility_ids.include?(facility['primary_station']) || facility_ids.include?(facility['facility_id']) || call_signs.include?(facility['callsign'])
|
70
|
+
end
|
71
|
+
|
72
|
+
{}.tap do |hash|
|
73
|
+
matched_facilities.each do |record|
|
74
|
+
hash[record['callsign']] = record['facility_id']
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def find_call_signs(call_signs:)
|
80
|
+
common_station_data.entries.select do |entry|
|
81
|
+
call_signs.any? do |call_sign|
|
82
|
+
call_signs_match?(call_sign, entry['callsign'])
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def read(file_name)
|
88
|
+
key = "#{lms_date}-#{file_name}"
|
89
|
+
remote_url = URI("#{BASE_URI}/#{lms_date}/#{file_name}.zip")
|
90
|
+
FCC.log remote_url
|
91
|
+
contents = FCC.cache.fetch key do
|
92
|
+
begin
|
93
|
+
temp_file = http_download_uri(remote_url)
|
94
|
+
break if temp_file.empty?
|
95
|
+
|
96
|
+
contents = ""
|
97
|
+
Zip::File.open_buffer(temp_file) do |zf|
|
98
|
+
contents = zf.read(zf.entries.first)
|
99
|
+
break
|
100
|
+
end
|
101
|
+
|
102
|
+
value = contents
|
103
|
+
rescue Exception => e
|
104
|
+
FCC.error(e.message)
|
105
|
+
value = nil
|
106
|
+
ensure
|
107
|
+
value
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
if contents
|
112
|
+
CSV.parse(contents, col_sep: '|', headers: true)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
protected
|
117
|
+
|
118
|
+
def call_signs_match?(ours, theirs)
|
119
|
+
theirs.to_s.upcase.to_s == ours.to_s.upcase.to_s || theirs.to_s.upcase =~ Regexp.new("^#{ours.to_s.upcase}[-—–][A-Z0-9]+$")
|
120
|
+
end
|
121
|
+
|
122
|
+
def http_download_uri(uri)
|
123
|
+
FCC.log 'Downloading ' + uri.to_s
|
124
|
+
begin
|
125
|
+
Tempfile.create { HTTParty.get(uri)&.body }
|
126
|
+
rescue Exception => e
|
127
|
+
FCC.error "=> Exception: '#{e}'. Skipping download."
|
128
|
+
|
129
|
+
raise e
|
130
|
+
return false
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def lms_date
|
135
|
+
Date.today.strftime('%m-%d-%Y')
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
3
|
+
module FCC
|
4
|
+
module Station
|
5
|
+
module Parsers
|
6
|
+
class ExtendedInfo < HTTParty::Parser
|
7
|
+
def parse
|
8
|
+
results = []
|
9
|
+
body.each_line do |row|
|
10
|
+
attrs = {}
|
11
|
+
attrs[:raw] = row
|
12
|
+
fields = row.split('|').slice(1...-1).collect(&:strip).map { |v| v == '-' ? "" : v }
|
13
|
+
|
14
|
+
attrs[:call_sign] = fields[0]
|
15
|
+
attrs[:frequency] = parse_frequency(fields[1])
|
16
|
+
attrs[:band] = fields[2]
|
17
|
+
attrs[:channel] = fields[3]
|
18
|
+
attrs[:antenna_type] = fields[4] # Directional Antenna (DA) or NonDirectional (ND)
|
19
|
+
attrs[:operating_hours] = fields[5] if fields[5] && attrs[:band]&.upcase == "AM" # (Only used for AM)
|
20
|
+
attrs[:station_class] = fields[6]
|
21
|
+
attrs[:region_2_station_class] = fields[7] if fields[7] && attrs[:band]&.upcase == "AM" # (only used for AM)
|
22
|
+
attrs[:status] = fields[8]
|
23
|
+
attrs[:city] = fields[9]
|
24
|
+
attrs[:state] = fields[10]
|
25
|
+
attrs[:country] = fields[11]
|
26
|
+
attrs[:file_number] = fields[12] #File Number (Application, Construction Permit or License) or
|
27
|
+
attrs[:signal_strength] = parse_signal_strength(fields[13]) # Effective Radiated Power --
|
28
|
+
attrs[:effective_radiated_power] = parse_signal_strength(fields[14]) # Effective Radiated Power -- vertically polarized (maximum)
|
29
|
+
attrs[:haat_horizontal] = fields[15] # Antenna Height Above Average Terrain (HAAT) -- horizontal polarization
|
30
|
+
attrs[:haat_vertical] = fields[16] # Antenna Height Above Average Terrain (HAAT) -- vertical polarization
|
31
|
+
attrs[:fcc_id] = fields[17] # Facility ID Number (unique to each station)
|
32
|
+
attrs[:latitude] = parse_latitude(fields[18], fields[19], fields[20], fields[21])
|
33
|
+
attrs[:longitude] = parse_longitude(fields[22], fields[23], fields[24], fields[25])
|
34
|
+
attrs[:licensed_to] = fields[26] # Licensee or Permittee
|
35
|
+
|
36
|
+
results << attrs
|
37
|
+
end
|
38
|
+
|
39
|
+
results
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse_longitude(direction, degrees, minutes, seconds)
|
43
|
+
decimal_degrees = degrees.to_i + (minutes.to_f / 60) + (seconds.to_f / 3600)
|
44
|
+
|
45
|
+
"#{direction =~ /W/ ? '-' : ''}#{decimal_degrees}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse_latitude(direction, degrees, minutes, seconds)
|
49
|
+
decimal_degrees = degrees.to_i + (minutes.to_f / 60) + (seconds.to_f / 3600)
|
50
|
+
|
51
|
+
"#{direction =~ /S/ ? '-' : ''}#{decimal_degrees}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse_signal_strength(power_string)
|
55
|
+
return unless power_string.present?
|
56
|
+
|
57
|
+
number, unit = power_string.strip.scan(/^([0-9.]+)\s+(\w+)$?/).flatten
|
58
|
+
multiplier = case unit&.downcase
|
59
|
+
when "w"
|
60
|
+
1
|
61
|
+
when "kw"
|
62
|
+
1000
|
63
|
+
when "mw"
|
64
|
+
1000000
|
65
|
+
else
|
66
|
+
1
|
67
|
+
end
|
68
|
+
|
69
|
+
number.to_f * multiplier
|
70
|
+
end
|
71
|
+
|
72
|
+
def parse_frequency(freq)
|
73
|
+
freq.to_f
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|