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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c1a2e65473c73d939b88beed853dfe8cb2ab4bb67684c522996a4b1eb0f8d7f8
4
- data.tar.gz: d86f808b6da433d743ac6e2546307cf4bdfeb42ec521796cb78a0ecfffb7da63
3
+ metadata.gz: 0a40f16f549528352153a0d8fb4c1bed24c966b9c3647ae7aee71f46b419920f
4
+ data.tar.gz: db47ff830e6c821ffce6fa3c07bd1bb75f34dfb60ca1ffd85566cf6cb95ce7a4
5
5
  SHA512:
6
- metadata.gz: e6a3bea8e717984ddd25fed17731e3ba06709c70dcb49340a274d148fcdd281e1463baf6483879d9a2427961589f2e0d7af908d8ce4ab6508776c8ba1d80c5e3
7
- data.tar.gz: 6da0ba2408b94f8488c45900938d3950e580a5cb71493b942a78696b462f0bc4dee61ea26b5bf4cbd1c54a155ad0b0c5670cde37e90c99ede9581b196cd75586
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 "shoulda", ">= 0"
21
- # gem "bundler"
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.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 (6.1.0)
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
- zeitwerk (~> 2.3)
17
- awesome_print (1.8.0)
18
- byebug (11.1.3)
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.8.6)
24
+ i18n (1.10.0)
25
25
  concurrent-ruby (~> 1.0)
26
- mime-types (3.3.1)
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.2020.1104)
29
- minitest (5.14.2)
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
- #Basic attributes, available quickly because the FCC actually caches these in a CDN:
23
- station.id #=> 65320
24
- station.status #=> LICENSED
25
- station.rf_channel #=> 219
26
- station.license_expiration_date #=> "08/01/2021"
27
- station.facility_type #=> ED
28
- station.frequency #=> 91.7
29
- station.contact #=> <struct FCC::Station::Contact>
30
- station.owner #=> <struct FCC::Station::Contact>
31
- station.community #=> <struct FCC::Station::Community city="HORNSBY", state="TX">
32
-
33
- #Extended attributes, takes several seconds to load because the FCC is running this endpoint on a 1960s era mainframe operated by trained hamsters
34
- station.station_class #=> A
35
- station.signal_strength #=> 3.0 kW
36
- station.antenna_type #=> ND
37
- station.effective_radiated_power #=> 3.0 kW
38
- station.haat_horizontal #=> 26.0
39
- station.haat_vertical #=> 26.0
40
- station.latitude #=> "30.266861111111112"
41
- station.longitude #=> "-97.67444444444445"
42
- station.file_number #=> BLED-19950103KA
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
- 0.1.0
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
@@ -1,28 +1,34 @@
1
1
  require 'httparty'
2
- require 'byebug'
3
- require_relative './extended_info/parser'
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(service)
11
- @service = service
12
- @store = Station::ExtendedInfo.new(@service)
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 find(fcc_id)
16
- results.detect { |r| r[:fcc_id].to_s == fcc_id.to_s }
17
+ def flush
18
+ @lightly.flush
17
19
  end
18
20
 
19
- def results
20
- #TODO: add redis caching tie-in because this query is molasses
21
- @store.all_results.parsed_response
22
- end
23
-
24
- def inspect
25
- "<Cache @service=#{service}>"
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
- require 'byebug'
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
- base_uri 'http://transition.fcc.gov/fcc-bin/'
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
- vac: 3, # licensed records only
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
- @all_results ||= begin
46
- puts "charging cache for #{service} extended info"
47
- response = self.class.get("/#{service.to_s.downcase}q", @options.merge(query: @query))
48
- puts response.request.uri.to_s.gsub('&list=4', '&list=0')
49
- response
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
- id = FCC::Station.index(@service).call_sign_to_id(id_or_call_sign)
58
- end
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
- parser FCC::Station::ExtendedInfoParser
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
@@ -1,5 +1,5 @@
1
1
  require 'httparty'
2
- require 'byebug'
2
+
3
3
  module FCC
4
4
  module Station
5
5
  class Index
@@ -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
- response['results']['facility']
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
@@ -0,0 +1,13 @@
1
+ require 'httparty'
2
+
3
+ module FCC
4
+ module Station
5
+ module Parsers
6
+ class LmsData < HTTParty::Parser
7
+ def parse
8
+ body
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end