@@ -141,6 +141,21 @@ def date_range(
141141 )
142142
143143
144+ def boolean_span_formatter (tf : bool ) -> str :
145+ """Format a boolean with html span element that colors green/red for true/false"""
146+ return f"<span style='color: { 'green' if tf else 'red' } '>{ tf } </span>"
147+
148+
149+ RATING_CSS_MAP = {
150+ "G" : "green" ,
151+ "Y" : "yellow" ,
152+ "R" : "red" ,
153+ "X" : "black" ,
154+ "N" : "grey" ,
155+ }
156+ """Mapping between the QA letter codes and css color name"""
157+
158+
144159@dataclass
145160class ResultAggregator :
146161 """Dataclass which iterates though all the stations their tests and aggregates their results and generates the "info blocks".
@@ -151,6 +166,100 @@ class ResultAggregator:
151166
152167 breakout : Breakout
153168
169+ def geo_breakout_feature (self ):
170+ """If the breakout has a valid bounding box, generate the GeoJSON feature to plot on a map"""
171+ if self .breakout .bbox is None :
172+ return None
173+
174+ breakout_geometry = self .breakout .bbox .__geo_interface__
175+ date_start = (
176+ f"{ self .breakout .temporal_bounds .dtstart :%Y-%m-%d} "
177+ if self .breakout .temporal_bounds is not None
178+ else ""
179+ )
180+ date_end = (
181+ f"{ self .breakout .temporal_bounds .dtend :%Y-%m-%d} "
182+ if self .breakout .temporal_bounds is not None
183+ else ""
184+ )
185+ not_on_map = (
186+ f"<li>{ station .r2r .name } </li>"
187+ for station in self .breakout
188+ if None in (station .r2r .longitude , station .r2r .latitude )
189+ )
190+ return {
191+ "type" : "FeatureCollection" ,
192+ "features" : [
193+ {
194+ "type" : "Feature" ,
195+ "geometry" : breakout_geometry ,
196+ "properties" : {
197+ "cruise_id" : self .breakout .cruise_id ,
198+ "fileset_id" : self .breakout .fileset_id ,
199+ "rating" : RATING_CSS_MAP [self .rating ],
200+ "manifest_ok" : boolean_span_formatter (
201+ self .breakout .manifest_ok
202+ ),
203+ "start_date" : date_start ,
204+ "end_date" : date_end ,
205+ "stations_not_on_map" : f"<ul>{ '' .join (not_on_map ) or '<li>All QAed stations on map</li>' } </ul>" ,
206+ },
207+ }
208+ ],
209+ }
210+
211+ def geo_station_feature (self ):
212+ """Generate the GeoJSON feature collection with a feature for each station that has lon/lat coordinates to plot on a map"""
213+ features = []
214+ for station in self .breakout :
215+ if None in (station .r2r .longitude , station .r2r .latitude ):
216+ continue
217+ station_geometry = station .r2r .__geo_interface__
218+ station_time = (
219+ f"{ station .r2r .time :%Y-%m-%d %H:%M:%S} " if station .r2r .time else "None"
220+ )
221+
222+ marker_color = (
223+ "green"
224+ if (
225+ station .r2r .all_three_files
226+ and station .r2r .lon_lat_valid
227+ and station .r2r .time_valid
228+ and station .r2r .lon_lat_in (self .breakout .bbox )
229+ and station .r2r .time_in (self .breakout .temporal_bounds )
230+ )
231+ else "red"
232+ )
233+
234+ features .append (
235+ {
236+ "type" : "Feature" ,
237+ "geometry" : station_geometry ,
238+ "properties" : {
239+ "name" : station .r2r .name ,
240+ "time" : station_time ,
241+ "all_three_files" : boolean_span_formatter (
242+ station .r2r .all_three_files
243+ ),
244+ "lon_lat_valid" : boolean_span_formatter (
245+ station .r2r .lon_lat_valid
246+ ),
247+ "time_valid" : boolean_span_formatter (station .r2r .time_valid ),
248+ "lon_lat_in" : boolean_span_formatter (
249+ station .r2r .lon_lat_in (self .breakout .bbox )
250+ ),
251+ "time_in" : boolean_span_formatter (
252+ station .r2r .time_in (self .breakout .temporal_bounds )
253+ ),
254+ "bottles_fired" : boolean_span_formatter (
255+ station .r2r .bottles_fired
256+ ),
257+ "marker_color" : marker_color ,
258+ },
259+ }
260+ )
261+ return {"type" : "FeatureCollection" , "features" : features }
262+
154263 @cached_property
155264 def presence_of_all_files (self ) -> int :
156265 """Iterate though the stations and count how many have :py:meth:`~r2r_ctd.accessors.R2RAccessor.all_three_files`"""
0 commit comments