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

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_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.

Parameters:

  • lat (Float)

    Latitude in decimal degrees

  • lon (Float)

    Longitude in decimal degrees

  • type (Symbol, nil) (defaults to: nil)

    Optional filter: :reference, :subordinate, or nil for all

Returns:

  • (Station, nil)

    The nearest station, or nil if no stations found



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.

Parameters:

  • lat (Float)

    Latitude in decimal degrees

  • lon (Float)

    Longitude in decimal degrees

  • radius (Float)

    Radius in degrees (roughly: 1° ≈ 111 km at equator)

  • type (Symbol, nil) (defaults to: nil)

    Optional filter: :reference, :subordinate, or nil for all

Returns:

  • (Array<Station>)

    Stations within the radius, sorted by distance



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