Class: TCD::Reader

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ 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_dataObject (readonly)

Returns the value of attribute constituent_data.



13
14
15
# File 'lib/tcd/reader.rb', line 13

def constituent_data
  @constituent_data
end

#headerObject (readonly)

Returns the value of attribute header.



13
14
15
# File 'lib/tcd/reader.rb', line 13

def header
  @header
end

#lookup_tablesObject (readonly)

Returns the value of attribute lookup_tables.



13
14
15
# File 'lib/tcd/reader.rb', line 13

def lookup_tables
  @lookup_tables
end

#pathObject (readonly)

Returns the value of attribute path.



13
14
15
# File 'lib/tcd/reader.rb', line 13

def path
  @path
end

Instance Method Details

#closeObject

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_countObject

Number of constituents



45
46
47
# File 'lib/tcd/reader.rb', line 45

def constituent_count
    @header.constituents
end

#constituentsObject

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_sizeObject

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_modifiedObject

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_stationsObject

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_countObject

Number of station records



40
41
42
# File 'lib/tcd/reader.rb', line 40

def station_count
    @header.number_of_records
end

#stationsObject

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

#statsObject

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_stationsObject

Get subordinate stations only



112
113
114
# File 'lib/tcd/reader.rb', line 112

def subordinate_stations
    stations.select(&:subordinate?)
end

#versionObject

Database version string



30
31
32
# File 'lib/tcd/reader.rb', line 30

def version
    @header.version
end

#year_rangeObject

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