Class: TCD::Reader
- Inherits:
-
Object
- Object
- TCD::Reader
- Defined in:
- lib/tcd/reader.rb
Overview
Main reader class for TCD (Tidal Constituent Database) files. Provides access to header info, lookup tables, constituents, and stations.
Instance Attribute Summary collapse
-
#constituent_data ⇒ Object
readonly
Returns the value of attribute constituent_data.
-
#header ⇒ Object
readonly
Returns the value of attribute header.
-
#lookup_tables ⇒ Object
readonly
Returns the value of attribute lookup_tables.
-
#path ⇒ Object
readonly
Returns the value of attribute path.
Instance Method Summary collapse
-
#close ⇒ Object
Close the file handle.
-
#constituent(name) ⇒ Object
Find constituent by name.
-
#constituent_count ⇒ Object
Number of constituents.
-
#constituents ⇒ Object
Access constituents.
-
#each_station(&block) ⇒ Object
Iterate over stations without loading all into memory.
-
#file_size ⇒ Object
File size in bytes.
-
#find_stations(query) ⇒ Object
Find stations by name (substring match, case-insensitive).
-
#infer_constituents(station) ⇒ Object
Infer missing constituents for a reference station.
-
#initialize(path) ⇒ Reader
constructor
A new instance of Reader.
-
#last_modified ⇒ Object
Last modified date string.
-
#nearest_station(lat, lon, type: nil) ⇒ Station?
Find the nearest station to a given latitude/longitude.
-
#reference_stations ⇒ Object
Get reference stations only.
-
#station_by_name(name) ⇒ Object
Find station by exact name.
-
#station_count ⇒ Object
Number of station records.
-
#stations ⇒ Object
Load and return all stations (lazy-loaded).
-
#stations_near(lat, lon, radius:, type: nil) ⇒ Array<Station>
Find stations within a given radius of a latitude/longitude.
-
#stats ⇒ Object
Summary statistics.
-
#subordinate_stations ⇒ Object
Get subordinate stations only.
-
#version ⇒ Object
Database version string.
-
#year_range ⇒ Object
Year range covered by equilibrium/node factor data.
Constructor Details
#initialize(path) ⇒ Reader
Returns a new instance of Reader.
15 16 17 18 19 20 21 22 |
# File 'lib/tcd/reader.rb', line 15 def initialize(path) @path = path @file = File.open(path, "rb") @stations_loaded = false @stations = [] end |
Instance Attribute Details
#constituent_data ⇒ Object (readonly)
Returns the value of attribute constituent_data.
13 14 15 |
# File 'lib/tcd/reader.rb', line 13 def constituent_data @constituent_data end |
#header ⇒ Object (readonly)
Returns the value of attribute header.
13 14 15 |
# File 'lib/tcd/reader.rb', line 13 def header @header end |
#lookup_tables ⇒ Object (readonly)
Returns the value of attribute lookup_tables.
13 14 15 |
# File 'lib/tcd/reader.rb', line 13 def lookup_tables @lookup_tables end |
#path ⇒ Object (readonly)
Returns the value of attribute path.
13 14 15 |
# File 'lib/tcd/reader.rb', line 13 def path @path end |
Instance Method Details
#close ⇒ Object
Close the file handle
25 26 27 |
# File 'lib/tcd/reader.rb', line 25 def close @file.close unless @file.closed? end |
#constituent(name) ⇒ Object
Find constituent by name
67 68 69 |
# File 'lib/tcd/reader.rb', line 67 def constituent(name) @constituent_data.find(name) end |
#constituent_count ⇒ Object
Number of constituents
45 46 47 |
# File 'lib/tcd/reader.rb', line 45 def constituent_count @header.constituents end |
#constituents ⇒ Object
Access constituents
62 63 64 |
# File 'lib/tcd/reader.rb', line 62 def constituents @constituent_data end |
#each_station(&block) ⇒ Object
Iterate over stations without loading all into memory
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/tcd/reader.rb', line 78 def each_station(&block) return enum_for(:each_station) unless block_given? if @stations_loaded @stations.each(&block) else @file.seek(@stations_offset) @bit = BitBuffer.new(@file) parser = StationParser.new(@bit, @header, @lookup_tables) @header.number_of_records.times do |i| station = parser.parse(i) yield station end end end |
#file_size ⇒ Object
File size in bytes
57 58 59 |
# File 'lib/tcd/reader.rb', line 57 def file_size @header.end_of_file end |
#find_stations(query) ⇒ Object
Find stations by name (substring match, case-insensitive)
96 97 98 99 |
# File 'lib/tcd/reader.rb', line 96 def find_stations(query) query_down = query.downcase stations.select { |s| s.name.downcase.include?(query_down) } end |
#infer_constituents(station) ⇒ Object
Infer missing constituents for a reference station. Requires the station to have non-zero values for M2, S2, K1, and O1. Returns true if inference was performed, false if not enough data.
119 120 121 |
# File 'lib/tcd/reader.rb', line 119 def infer_constituents(station) Inference.infer_constituents(station, @constituent_data) end |
#last_modified ⇒ Object
Last modified date string
35 36 37 |
# File 'lib/tcd/reader.rb', line 35 def last_modified @header.last_modified end |
#nearest_station(lat, lon, type: nil) ⇒ Station?
Find the nearest station to a given latitude/longitude. Uses simple Euclidean distance (suitable for nearby searches). For more accurate global searches, consider using the Haversine formula.
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/tcd/reader.rb', line 131 def nearest_station(lat, lon, type: nil) candidates = case type when :reference then reference_stations when :subordinate then subordinate_stations else stations end return nil if candidates.empty? candidates.min_by do |s| dlat = lat - s.latitude dlon = lon - s.longitude dlat * dlat + dlon * dlon end end |
#reference_stations ⇒ Object
Get reference stations only
107 108 109 |
# File 'lib/tcd/reader.rb', line 107 def reference_stations stations.select(&:reference?) end |
#station_by_name(name) ⇒ Object
Find station by exact name
102 103 104 |
# File 'lib/tcd/reader.rb', line 102 def station_by_name(name) stations.find { |s| s.name == name } end |
#station_count ⇒ Object
Number of station records
40 41 42 |
# File 'lib/tcd/reader.rb', line 40 def station_count @header.number_of_records end |
#stations ⇒ Object
Load and return all stations (lazy-loaded)
72 73 74 75 |
# File 'lib/tcd/reader.rb', line 72 def stations load_stations unless @stations_loaded @stations end |
#stations_near(lat, lon, radius:, type: nil) ⇒ Array<Station>
Find stations within a given radius of a latitude/longitude. Uses simple Euclidean distance in degrees.
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/tcd/reader.rb', line 155 def stations_near(lat, lon, radius:, type: nil) candidates = case type when :reference then reference_stations when :subordinate then subordinate_stations else stations end radius_sq = radius * radius candidates.select do |s| dlat = lat - s.latitude dlon = lon - s.longitude dlat * dlat + dlon * dlon <= radius_sq end.sort_by do |s| dlat = lat - s.latitude dlon = lon - s.longitude dlat * dlat + dlon * dlon end end |
#stats ⇒ Object
Summary statistics
176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/tcd/reader.rb', line 176 def stats all = stations { total_stations: all.size, reference_stations: all.count(&:reference?), subordinate_stations: all.count(&:subordinate?), constituents: constituent_count, countries: @lookup_tables.countries.size, timezones: @lookup_tables.timezones.size, datums: @lookup_tables.datums.size, year_range: year_range, file_size: file_size } end |
#subordinate_stations ⇒ Object
Get subordinate stations only
112 113 114 |
# File 'lib/tcd/reader.rb', line 112 def subordinate_stations stations.select(&:subordinate?) end |
#version ⇒ Object
Database version string
30 31 32 |
# File 'lib/tcd/reader.rb', line 30 def version @header.version end |
#year_range ⇒ Object
Year range covered by equilibrium/node factor data
50 51 52 53 54 |
# File 'lib/tcd/reader.rb', line 50 def year_range start_year = @header.start_year end_year = start_year + @header.number_of_years - 1 start_year..end_year end |