cdss-ruby 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +86 -0
  3. data/CHANGELOG.md +5 -0
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/README.md +101 -0
  6. data/Rakefile +26 -0
  7. data/docs/Cdss/AdminCalls.html +399 -0
  8. data/docs/Cdss/Analysis.html +972 -0
  9. data/docs/Cdss/Client.html +581 -0
  10. data/docs/Cdss/Climate.html +1257 -0
  11. data/docs/Cdss/Concerns/LogReadingAttributes.html +406 -0
  12. data/docs/Cdss/Concerns/WellReadingAttributes.html +414 -0
  13. data/docs/Cdss/Concerns.html +117 -0
  14. data/docs/Cdss/GroundWater.html +945 -0
  15. data/docs/Cdss/Models/AdminCall.html +252 -0
  16. data/docs/Cdss/Models/Analysis.html +397 -0
  17. data/docs/Cdss/Models/CallAnalysis.html +140 -0
  18. data/docs/Cdss/Models/ClimateStation.html +249 -0
  19. data/docs/Cdss/Models/DiversionRecord.html +248 -0
  20. data/docs/Cdss/Models/Reading.html +301 -0
  21. data/docs/Cdss/Models/ReferenceTable.html +339 -0
  22. data/docs/Cdss/Models/RouteAnalysis.html +140 -0
  23. data/docs/Cdss/Models/SourceRoute.html +140 -0
  24. data/docs/Cdss/Models/Station.html +248 -0
  25. data/docs/Cdss/Models/Structure.html +259 -0
  26. data/docs/Cdss/Models/WaterClass.html +249 -0
  27. data/docs/Cdss/Models/WaterRight.html +255 -0
  28. data/docs/Cdss/Models/Well.html +251 -0
  29. data/docs/Cdss/Models.html +117 -0
  30. data/docs/Cdss/Parser.html +2155 -0
  31. data/docs/Cdss/Parsers/AdminCallsParser.html +201 -0
  32. data/docs/Cdss/Parsers/AnalysisParser.html +296 -0
  33. data/docs/Cdss/Parsers/BaseParser.html +207 -0
  34. data/docs/Cdss/Parsers/ClimateParser.html +253 -0
  35. data/docs/Cdss/Parsers/ReadingParser.html +201 -0
  36. data/docs/Cdss/Parsers/ReferenceTablesParser.html +201 -0
  37. data/docs/Cdss/Parsers/StationParser.html +201 -0
  38. data/docs/Cdss/Parsers/StructuresParser.html +305 -0
  39. data/docs/Cdss/Parsers/WaterRightsParser.html +219 -0
  40. data/docs/Cdss/Parsers/WellParser.html +357 -0
  41. data/docs/Cdss/Parsers.html +117 -0
  42. data/docs/Cdss/ReferenceTables.html +332 -0
  43. data/docs/Cdss/Structures.html +1132 -0
  44. data/docs/Cdss/SurfaceWater.html +798 -0
  45. data/docs/Cdss/Telemetry.html +763 -0
  46. data/docs/Cdss/Utils.html +1276 -0
  47. data/docs/Cdss/WaterRights.html +634 -0
  48. data/docs/Cdss.html +292 -0
  49. data/docs/_index.html +493 -0
  50. data/docs/class_list.html +54 -0
  51. data/docs/css/common.css +1 -0
  52. data/docs/css/full_list.css +58 -0
  53. data/docs/css/style.css +503 -0
  54. data/docs/file.README.html +108 -0
  55. data/docs/file_list.html +59 -0
  56. data/docs/frames.html +22 -0
  57. data/docs/index.html +108 -0
  58. data/docs/js/app.js +344 -0
  59. data/docs/js/full_list.js +242 -0
  60. data/docs/js/jquery.js +4 -0
  61. data/docs/method_list.html +790 -0
  62. data/docs/top-level-namespace.html +110 -0
  63. data/lib/cdss/admin_calls.rb +44 -0
  64. data/lib/cdss/analysis.rb +183 -0
  65. data/lib/cdss/client.rb +121 -0
  66. data/lib/cdss/climate.rb +155 -0
  67. data/lib/cdss/concerns/log_reading_attributes.rb +48 -0
  68. data/lib/cdss/concerns/well_reading_attributes.rb +56 -0
  69. data/lib/cdss/ground_water.rb +112 -0
  70. data/lib/cdss/models/admin_call.rb +45 -0
  71. data/lib/cdss/models/analysis.rb +77 -0
  72. data/lib/cdss/models/climate_station.rb +40 -0
  73. data/lib/cdss/models/reading.rb +54 -0
  74. data/lib/cdss/models/reference_table.rb +56 -0
  75. data/lib/cdss/models/station.rb +40 -0
  76. data/lib/cdss/models/structure.rb +101 -0
  77. data/lib/cdss/models/water_right.rb +47 -0
  78. data/lib/cdss/models/well.rb +43 -0
  79. data/lib/cdss/parser.rb +172 -0
  80. data/lib/cdss/parsers/admin_calls_parser.rb +47 -0
  81. data/lib/cdss/parsers/analysis_parser.rb +124 -0
  82. data/lib/cdss/parsers/base_parser.rb +18 -0
  83. data/lib/cdss/parsers/climate_parser.rb +86 -0
  84. data/lib/cdss/parsers/reading_parser.rb +90 -0
  85. data/lib/cdss/parsers/reference_tables_parser.rb +55 -0
  86. data/lib/cdss/parsers/station_parser.rb +42 -0
  87. data/lib/cdss/parsers/structures_parser.rb +96 -0
  88. data/lib/cdss/parsers/water_rights_parser.rb +77 -0
  89. data/lib/cdss/parsers/well_parser.rb +107 -0
  90. data/lib/cdss/reference_tables.rb +147 -0
  91. data/lib/cdss/structures.rb +235 -0
  92. data/lib/cdss/surface_water.rb +186 -0
  93. data/lib/cdss/telemetry.rb +98 -0
  94. data/lib/cdss/utils.rb +152 -0
  95. data/lib/cdss/version.rb +5 -0
  96. data/lib/cdss/water_rights.rb +95 -0
  97. data/lib/cdss.rb +27 -0
  98. data/sig/cdss/ruby.rbs +6 -0
  99. metadata +272 -0
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cdss
4
+ # Provides methods for accessing surface water data from the CDSS API.
5
+ #
6
+ # This module includes functionality for retrieving surface water stations and their
7
+ # associated time series data at various time scales (daily, monthly, yearly).
8
+ module SurfaceWater
9
+ include Utils
10
+
11
+ # Fetches surface water stations based on various filtering criteria.
12
+ #
13
+ # @param [Hash, Array, nil] aoi Area of interest for spatial searches. If hash, must contain :latitude and :longitude keys.
14
+ # If array, must contain [longitude, latitude].
15
+ # @param [Integer, nil] radius Radius in miles for spatial search around aoi. Defaults to 20 if aoi is provided.
16
+ # @param [String, nil] abbrev Station abbreviation to filter by.
17
+ # @param [String, nil] county County name to filter stations.
18
+ # @param [Integer, nil] division Water division number to filter stations.
19
+ # @param [String, nil] station_name Name of the station to filter by.
20
+ # @param [String, nil] usgs_id USGS site ID to filter by.
21
+ # @param [Integer, nil] water_district Water district number to filter by.
22
+ # @param [String, nil] api_key Optional API key for authentication.
23
+ # @return [Array<Station>] Array of matching surface water station objects.
24
+ # @raise [ArgumentError] If aoi parameter is provided but invalid.
25
+ # @example Fetch stations in Denver county
26
+ # get_sw_stations(county: 'DENVER')
27
+ # @example Fetch stations within 10 miles of a point
28
+ # get_sw_stations(aoi: { latitude: 39.7392, longitude: -104.9903 }, radius: 10)
29
+ def get_sw_stations(aoi: nil, radius: nil, abbrev: nil, county: nil, division: nil, station_name: nil,
30
+ usgs_id: nil, water_district: nil, api_key: nil)
31
+ query = {
32
+ format: "json",
33
+ dateFormat: "spaceSepToSeconds",
34
+ abbrev: abbrev,
35
+ county: county,
36
+ division: division,
37
+ stationName: station_name,
38
+ usgsSiteId: usgs_id,
39
+ waterDistrict: water_district
40
+ }
41
+
42
+ if aoi
43
+ if aoi.is_a?(Hash) && aoi[:latitude] && aoi[:longitude]
44
+ query.merge!(longitude: aoi[:longitude], latitude: aoi[:latitude])
45
+ elsif aoi.is_a?(Array) && aoi.count == 2
46
+ query.merge!(longitude: aoi[0], latitude: aoi[1])
47
+ else
48
+ raise ArgumentError, "Invalid 'aoi' parameter"
49
+ end
50
+ query[:radius] = radius || 20
51
+ query[:units] = "miles"
52
+ end
53
+
54
+ fetch_paginated_data(
55
+ endpoint: "/surfacewater/surfacewaterstations/",
56
+ query: query
57
+ ) { |data| Parser.parse_stations(data) }
58
+ end
59
+
60
+ # Fetches surface water time series data for specified stations.
61
+ #
62
+ # @param [String, nil] abbrev Station abbreviation.
63
+ # @param [String, nil] station_number Station number.
64
+ # @param [String, nil] usgs_id USGS site ID.
65
+ # @param [Date, nil] start_date Start date for time series data.
66
+ # @param [Date, nil] end_date End date for time series data.
67
+ # @param [String, nil] timescale Time interval for data aggregation. Valid values:
68
+ # - day, days, daily, d
69
+ # - month, months, monthly, mon, m
70
+ # - wyear, water_year, wyears, water_years, wateryear, wateryears, wy, year, years, yearly, annual, annually, yr, y
71
+ # @param [String, nil] api_key Optional API key for authentication.
72
+ # @return [Array<Reading>] Array of time series reading objects.
73
+ # @raise [ArgumentError] If an invalid timescale is provided.
74
+ # @example Fetch daily readings for a station
75
+ # get_sw_ts(abbrev: 'PLAKERCO', timescale: 'day', start_date: Date.new(2023, 1, 1))
76
+ def get_sw_ts(abbrev: nil, station_number: nil, usgs_id: nil, start_date: nil, end_date: nil, timescale: nil,
77
+ api_key: nil)
78
+ timescale ||= "day"
79
+
80
+ day_lst = %w[day days daily d]
81
+ month_lst = %w[month months monthly mon m]
82
+ year_lst = %w[wyear water_year wyears water_years wateryear wateryears wy year years
83
+ yearly annual annually yr y]
84
+ timescale_lst = day_lst + month_lst + year_lst
85
+
86
+ unless timescale_lst.include?(timescale)
87
+ valid_timescales = timescale_lst.join(", ")
88
+ raise ArgumentError, "Invalid 'timescale' argument: '#{timescale}'. Valid values are: #{valid_timescales}"
89
+ end
90
+
91
+ case timescale
92
+ when *day_lst
93
+ get_sw_ts_day(abbrev: abbrev, station_number: station_number, usgs_id: usgs_id, start_date: start_date,
94
+ end_date: end_date, api_key: api_key)
95
+ when *month_lst
96
+ get_sw_ts_month(abbrev: abbrev, station_number: station_number, usgs_id: usgs_id, start_date: start_date,
97
+ end_date: end_date, api_key: api_key)
98
+ when *year_lst
99
+ get_sw_ts_wyear(abbrev: abbrev, station_number: station_number, usgs_id: usgs_id, start_date: start_date,
100
+ end_date: end_date, api_key: api_key)
101
+ else
102
+ raise ArgumentError, "Invalid 'timescale' argument: '#{timescale}'"
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ # Fetches daily surface water time series readings.
109
+ #
110
+ # @param [String, nil] abbrev Station abbreviation
111
+ # @param [String, nil] station_number Station number
112
+ # @param [String, nil] usgs_id USGS site ID
113
+ # @param [Date, nil] start_date Start date for readings
114
+ # @param [Date, nil] end_date End date for readings
115
+ # @param [String, nil] api_key Optional API key for authentication
116
+ # @return [Array<Reading>] Array of daily surface water readings
117
+ def get_sw_ts_day(abbrev: nil, station_number: nil, usgs_id: nil, start_date: nil, end_date: nil, api_key: nil)
118
+ query = {
119
+ format: "json",
120
+ dateFormat: "spaceSepToSeconds",
121
+ abbrev: abbrev,
122
+ stationNum: station_number,
123
+ usgsSiteId: usgs_id,
124
+ "min-measDate": start_date&.strftime("%m-%d-%Y"),
125
+ "max-measDate": end_date&.strftime("%m-%d-%Y")
126
+ }
127
+
128
+ fetch_paginated_data(
129
+ endpoint: "/surfacewater/surfacewatertsday/",
130
+ query: query
131
+ ) { |data| Parser.parse_readings(data, timescale: :day) }
132
+ end
133
+
134
+ # Fetches monthly surface water time series readings.
135
+ #
136
+ # @param [String, nil] abbrev Station abbreviation
137
+ # @param [String, nil] station_number Station number
138
+ # @param [String, nil] usgs_id USGS site ID
139
+ # @param [Date, nil] start_date Start date for readings (year only)
140
+ # @param [Date, nil] end_date End date for readings (year only)
141
+ # @param [String, nil] api_key Optional API key for authentication
142
+ # @return [Array<Reading>] Array of monthly surface water readings
143
+ def get_sw_ts_month(abbrev: nil, station_number: nil, usgs_id: nil, start_date: nil, end_date: nil, api_key: nil)
144
+ query = {
145
+ format: "json",
146
+ dateFormat: "spaceSepToSeconds",
147
+ abbrev: abbrev,
148
+ stationNum: station_number,
149
+ usgsSiteId: usgs_id,
150
+ "min-calYear": start_date&.strftime("%Y"),
151
+ "max-calYear": end_date&.strftime("%Y")
152
+ }
153
+
154
+ fetch_paginated_data(
155
+ endpoint: "/surfacewater/surfacewatertsmonth/",
156
+ query: query
157
+ ) { |data| Parser.parse_readings(data, timescale: :month) }
158
+ end
159
+
160
+ # Fetches water year time series readings.
161
+ #
162
+ # @param [String, nil] abbrev Station abbreviation
163
+ # @param [String, nil] station_number Station number
164
+ # @param [String, nil] usgs_id USGS site ID
165
+ # @param [Date, nil] start_date Start date for readings (year only)
166
+ # @param [Date, nil] end_date End date for readings (year only)
167
+ # @param [String, nil] api_key Optional API key for authentication
168
+ # @return [Array<Reading>] Array of water year readings
169
+ def get_sw_ts_wyear(abbrev: nil, station_number: nil, usgs_id: nil, start_date: nil, end_date: nil, api_key: nil)
170
+ query = {
171
+ format: "json",
172
+ dateFormat: "spaceSepToSeconds",
173
+ abbrev: abbrev,
174
+ stationNum: station_number,
175
+ usgsSiteId: usgs_id,
176
+ "min-waterYear": start_date&.strftime("%Y"),
177
+ "max-waterYear": end_date&.strftime("%Y")
178
+ }
179
+
180
+ fetch_paginated_data(
181
+ endpoint: "/surfacewater/surfacewatertswateryear/",
182
+ query: query
183
+ ) { |data| Parser.parse_readings(data, timescale: :year) }
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cdss
4
+ # Provides methods for accessing telemetry station data from the CDSS API.
5
+ #
6
+ # This module includes functionality for retrieving telemetry stations and their
7
+ # associated time series data.
8
+ module Telemetry
9
+ include Utils
10
+
11
+ # Fetches telemetry stations based on various filtering criteria.
12
+ #
13
+ # @param [Hash, Array, nil] aoi Area of interest for spatial searches. If hash, must contain :latitude and :longitude keys.
14
+ # If array, must contain [longitude, latitude].
15
+ # @param [Integer, nil] radius Radius in miles for spatial search around aoi. Defaults to 20 if aoi is provided.
16
+ # @param [String, nil] abbrev Station abbreviation to filter by.
17
+ # @param [String, nil] county County name to filter stations.
18
+ # @param [Integer, nil] division Water division number to filter stations.
19
+ # @param [String, nil] gnis_id GNIS ID to filter by.
20
+ # @param [String, nil] usgs_id USGS station ID to filter by.
21
+ # @param [Integer, nil] water_district Water district number to filter by.
22
+ # @param [String, nil] wdid WDID to filter by.
23
+ # @return [Array<Station>] Array of matching telemetry station objects.
24
+ # @raise [ArgumentError] If aoi parameter is provided but invalid.
25
+ # @example Fetch stations in Denver county
26
+ # get_telemetry_stations(county: 'Denver')
27
+ # @example Fetch stations within 10 miles of a point
28
+ # get_telemetry_stations(aoi: { latitude: 39.7392, longitude: -104.9903 }, radius: 10)
29
+ def get_telemetry_stations(aoi: nil, radius: nil, abbrev: nil, county: nil, division: nil, gnis_id: nil,
30
+ usgs_id: nil, water_district: nil, wdid: nil)
31
+ query = {
32
+ format: "json",
33
+ dateFormat: "spaceSepToSeconds",
34
+ includeThirdParty: true,
35
+ abbrev: abbrev,
36
+ county: county,
37
+ division: division,
38
+ gnisId: gnis_id,
39
+ usgsStationId: usgs_id,
40
+ waterDistrict: water_district,
41
+ wdid: wdid
42
+ }
43
+
44
+ if aoi
45
+ if aoi.is_a?(Hash) && aoi[:latitude] && aoi[:longitude]
46
+ query.merge!(longitude: aoi[:longitude], latitude: aoi[:latitude])
47
+ elsif aoi.is_a?(Array) && aoi.count == 2
48
+ query.merge!(longitude: aoi[0], latitude: aoi[1])
49
+ else
50
+ raise ArgumentError, "Invalid 'aoi' parameter"
51
+ end
52
+ query[:radius] = radius || 20
53
+ query[:units] = "miles"
54
+ end
55
+
56
+ fetch_paginated_data(
57
+ endpoint: "/telemetrystations/telemetrystation/",
58
+ query: query
59
+ ) { |data| Parser.parse_stations(data) }
60
+ end
61
+
62
+ # Fetches telemetry time series data for specified stations.
63
+ #
64
+ # @param [String] abbrev Station abbreviation.
65
+ # @param [String, nil] parameter Telemetry parameter to retrieve. Defaults to 'DISCHRG'.
66
+ # @param [Date, nil] start_date Start date for time series data.
67
+ # @param [Date, nil] end_date End date for time series data.
68
+ # @param [String, nil] timescale Time interval for data. Valid values: 'day', 'hour', 'raw'.
69
+ # @param [Boolean] include_third_party Whether to include third-party data. Defaults to true.
70
+ # @return [Array<Reading>] Array of time series reading objects.
71
+ # @raise [ArgumentError] If an invalid timescale is provided.
72
+ # @example Fetch daily discharge data for a station
73
+ # get_telemetry_ts(abbrev: 'PLACHECO', parameter: 'DISCHRG', start_date: Date.new(2023, 1, 1))
74
+ def get_telemetry_ts(abbrev:, parameter: "DISCHRG", start_date: nil, end_date: nil, timescale: "day",
75
+ include_third_party: true)
76
+ timescales = %w[day hour raw]
77
+ unless timescales.include?(timescale)
78
+ raise ArgumentError, "Invalid 'timescale' argument: '#{timescale}'. Valid values are: #{timescales.join(', ')}"
79
+ end
80
+
81
+ query = {
82
+ format: "json",
83
+ dateFormat: "spaceSepToSeconds",
84
+ abbrev: abbrev,
85
+ parameter: parameter,
86
+ includeThirdParty: include_third_party.to_s
87
+ }
88
+
89
+ query[:startDate] = start_date&.strftime("%m-%d-%Y") if start_date
90
+ query[:endDate] = end_date&.strftime("%m-%d-%Y") if end_date
91
+
92
+ fetch_paginated_data(
93
+ endpoint: "/telemetrystations/telemetrytimeseries#{timescale}/",
94
+ query: query
95
+ ) { |data| Parser.parse_readings(data, timescale: timescale.to_sym) }
96
+ end
97
+ end
98
+ end
data/lib/cdss/utils.rb ADDED
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cdss
4
+ # Provides utility methods for handling dates, query parameters, and API pagination.
5
+ #
6
+ # This module contains helper methods used across the CDSS API client for
7
+ # data formatting, safe type conversion, and managing paginated responses.
8
+ module Utils
9
+ # Splits a date range into yearly chunks.
10
+ #
11
+ # @param [Date, nil] start_date Beginning of the date range
12
+ # @param [Date, nil] end_date End of the date range
13
+ # @return [Array<Array<Date>>] Array of date pairs representing yearly chunks
14
+ # @example Split a multi-year range
15
+ # batch_dates(Date.new(2020,1,1), Date.new(2022,6,30))
16
+ # #=> [[2020-01-01, 2020-12-31], [2021-01-01, 2021-12-31], [2022-01-01, 2022-06-30]]
17
+ def batch_dates(start_date, end_date)
18
+ start_date ||= Date.new(1900, 1, 1)
19
+ end_date ||= Date.today
20
+ start_year = start_date.year
21
+ end_year = end_date.year
22
+ if start_year == end_year
23
+ [[start_date, end_date]]
24
+ else
25
+ dates = []
26
+ # First year
27
+ dates << [start_date, Date.new(start_year, 12, 31)]
28
+ # Middle years
29
+ ((start_year + 1)...end_year).each do |year|
30
+ dates << [Date.new(year, 1, 1), Date.new(year, 12, 31)]
31
+ end
32
+ # Last year
33
+ dates << [Date.new(end_year, 1, 1), end_date]
34
+ dates
35
+ end
36
+ end
37
+
38
+ # Formats a query parameter value for API requests.
39
+ #
40
+ # @param [Object] value The value to format
41
+ # @return [String, nil] Formatted value or nil if input is nil
42
+ # @example Format an array parameter
43
+ # format_query_param(['a', 'b']) #=> "a,b"
44
+ def format_query_param(value)
45
+ return nil if value.nil?
46
+ return value.join(",") if value.is_a?(Array)
47
+
48
+ value.to_s
49
+ end
50
+
51
+ # Builds a query hash for API requests.
52
+ #
53
+ # @param [Hash] params Query parameters to include
54
+ # @param [Boolean] encode Whether to URL encode parameter values
55
+ # @return [Hash] Query hash with formatted parameters
56
+ # @example Build a simple query
57
+ # build_query({ name: "test", id: 123 })
58
+ # #=> { format: "json", name: "test", id: "123" }
59
+ def build_query(params = {}, encode: false)
60
+ base_query = {
61
+ format: "json"
62
+ }
63
+ params.each do |key, value|
64
+ formatted_value = format_query_param(value)
65
+ if formatted_value
66
+ base_query[key] = encode ? URI.encode_www_form_component(formatted_value) : formatted_value
67
+ end
68
+ end
69
+ base_query
70
+ end
71
+
72
+ # Formats a date for API requests.
73
+ #
74
+ # @param [Date] date The date to format
75
+ # @param [Boolean] special_format Whether to use forward slashes instead of hyphens
76
+ # @return [String, nil] Formatted date string or nil if input is nil
77
+ # @example Format a date
78
+ # format_date(Date.new(2023,1,1)) #=> "01-01-2023"
79
+ def format_date(date, special_format: false)
80
+ return nil unless date
81
+
82
+ date = date.strftime("%m-%d-%Y")
83
+ special_format ? date.gsub("-", "%2F") : date
84
+ end
85
+
86
+ # Safely parses a timestamp string.
87
+ #
88
+ # @param [String] datetime_str Timestamp string to parse
89
+ # @return [DateTime, nil] Parsed DateTime object or nil if parsing fails
90
+ # @example Parse a timestamp
91
+ # parse_timestamp("2023-01-01 12:00:00")
92
+ def parse_timestamp(datetime_str)
93
+ DateTime.parse(datetime_str) if datetime_str
94
+ rescue ArgumentError
95
+ nil
96
+ end
97
+
98
+ # Safely converts a value to a float.
99
+ #
100
+ # @param [Object] value Value to convert
101
+ # @return [Float, nil] Converted float or nil if conversion fails
102
+ # @example Convert a string to float
103
+ # safe_float("123.45") #=> 123.45
104
+ def safe_float(value)
105
+ Float(value)
106
+ rescue TypeError, ArgumentError
107
+ nil
108
+ end
109
+
110
+ # Safely converts a value to an integer.
111
+ #
112
+ # @param [Object] value Value to convert
113
+ # @return [Integer, nil] Converted integer or nil if conversion fails
114
+ # @example Convert a string to integer
115
+ # safe_integer("123") #=> 123
116
+ def safe_integer(value)
117
+ Integer(value)
118
+ rescue TypeError, ArgumentError
119
+ nil
120
+ end
121
+
122
+ # Fetches all pages of data from a paginated API endpoint.
123
+ #
124
+ # @param [String] endpoint API endpoint path
125
+ # @param [Hash] query Query parameters for the request
126
+ # @yield [Hash] Block to process each page of response data
127
+ # @yieldreturn [Array] Processed records from the response
128
+ # @return [Array] Combined results from all pages
129
+ # @example Fetch paginated data
130
+ # fetch_paginated_data(endpoint: "/api/data", query: { type: "test" }) do |data|
131
+ # process_data(data)
132
+ # end
133
+ def fetch_paginated_data(endpoint:, query:)
134
+ page_size = 50_000
135
+ page_index = 1
136
+ results = []
137
+ loop do
138
+ query = query.merge(pageSize: page_size, pageIndex: page_index)
139
+ response = get(endpoint, query: query)
140
+ data = handle_response(response)
141
+ records = yield(data)
142
+ break if records.empty?
143
+
144
+ results.concat(records)
145
+ break if records.size < page_size
146
+
147
+ page_index += 1
148
+ end
149
+ results
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cdss
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cdss
4
+ # Provides methods for accessing water rights data from the CDSS API.
5
+ #
6
+ # This module includes functionality for retrieving water rights net amounts
7
+ # and transactions data based on various spatial and attribute-based searches.
8
+ module WaterRights
9
+ include Utils
10
+
11
+ # Fetches water rights net amounts data based on various filtering criteria.
12
+ #
13
+ # @param [Hash, Array, nil] aoi Area of interest for spatial searches. If hash, must contain :latitude and :longitude keys.
14
+ # If array, must contain [longitude, latitude].
15
+ # @param [Integer, nil] radius Radius in miles for spatial search around aoi. Defaults to 20 if aoi is provided.
16
+ # @param [String, nil] county County name to filter rights.
17
+ # @param [Integer, nil] division Water division number to filter rights.
18
+ # @param [Integer, nil] water_district Water district number to filter rights.
19
+ # @param [String, nil] wdid WDID code of water right.
20
+ # @return [Array<WaterRight>] Array of water right objects with net amounts.
21
+ # @raise [ArgumentError] If aoi parameter is provided but invalid.
22
+ def get_water_rights_net_amounts(aoi: nil, radius: nil, county: nil, division: nil, water_district: nil, wdid: nil)
23
+ query = {
24
+ format: "json",
25
+ dateFormat: "spaceSepToSeconds",
26
+ units: "miles",
27
+ county: county,
28
+ division: division,
29
+ waterDistrict: water_district,
30
+ wdid: wdid
31
+ }
32
+ query.merge!(process_aoi(aoi, radius)) if aoi
33
+ fetch_paginated_data(
34
+ endpoint: "/waterrights/netamount/",
35
+ query: query
36
+ ) { |data| Parser.parse_water_rights(data, type: :net_amount) }
37
+ end
38
+
39
+ # Fetches water rights transactions data based on various filtering criteria.
40
+ #
41
+ # @param [Hash, Array, nil] aoi Area of interest for spatial searches. If hash, must contain :latitude and :longitude keys.
42
+ # If array, must contain [longitude, latitude].
43
+ # @param [Integer, nil] radius Radius in miles for spatial search around aoi. Defaults to 20 if aoi is provided.
44
+ # @param [String, nil] county County name to filter transactions.
45
+ # @param [Integer, nil] division Water division number to filter transactions.
46
+ # @param [Integer, nil] water_district Water district number to filter transactions.
47
+ # @param [String, nil] wdid WDID code of water right.
48
+ # @return [Array<WaterRight>] Array of water right objects with transactions.
49
+ # @raise [ArgumentError] If aoi parameter is provided but invalid.
50
+ def get_water_rights_transactions(aoi: nil, radius: nil, county: nil, division: nil, water_district: nil, wdid: nil)
51
+ query = {
52
+ format: "json",
53
+ dateFormat: "spaceSepToSeconds",
54
+ units: "miles",
55
+ county: county,
56
+ division: division,
57
+ waterDistrict: water_district,
58
+ wdid: wdid
59
+ }
60
+ query.merge!(process_aoi(aoi, radius)) if aoi
61
+ fetch_paginated_data(
62
+ endpoint: "/waterrights/transaction/",
63
+ query: query
64
+ ) { |data| Parser.parse_water_rights(data, type: :transaction) }
65
+ end
66
+
67
+ private
68
+
69
+ # Processes the area of interest (AOI) parameter for spatial searches.
70
+ #
71
+ # @param [Hash, Array] aoi Area of interest with location data
72
+ # @param [Integer, nil] radius Search radius in miles
73
+ # @return [Hash] Processed location and radius query parameters
74
+ # @raise [ArgumentError] If AOI parameter is invalid
75
+ # @example Process a hash-based AOI
76
+ # process_aoi({ latitude: 39.7392, longitude: -104.9903 }, 20)
77
+ def process_aoi(aoi, radius)
78
+ if aoi.is_a?(Hash) && aoi[:latitude] && aoi[:longitude]
79
+ {
80
+ longitude: aoi[:longitude],
81
+ latitude: aoi[:latitude],
82
+ radius: radius || 20
83
+ }
84
+ elsif aoi.is_a?(Array) && aoi.count == 2
85
+ {
86
+ longitude: aoi[0],
87
+ latitude: aoi[1],
88
+ radius: radius || 20
89
+ }
90
+ else
91
+ raise ArgumentError, "Invalid 'aoi' parameter"
92
+ end
93
+ end
94
+ end
95
+ end
data/lib/cdss.rb ADDED
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zeitwerk"
4
+ require "dry-configurable"
5
+ require "httparty"
6
+ require "json"
7
+
8
+ module Cdss
9
+ @loader = Zeitwerk::Loader.for_gem
10
+ @loader.enable_reloading
11
+ @loader.setup
12
+
13
+ extend Dry::Configurable
14
+ setting :user_agent, default: -> { "Cdss Ruby Gem/#{VERSION}" }
15
+ setting :timeout, default: 30
16
+ setting :base_url, default: "https://dwr.state.co.us/Rest/GET/api/v2"
17
+ setting :default_parameter, default: "DISCHRG"
18
+ setting :debug, default: false
19
+
20
+ class << self
21
+ attr_reader :loader
22
+
23
+ def client(**options)
24
+ Cdss::Client.new(**options)
25
+ end
26
+ end
27
+ end
data/sig/cdss/ruby.rbs ADDED
@@ -0,0 +1,6 @@
1
+ module Cdss
2
+ module Ruby
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end