def addHiLo(self, val, ts): """Include a vector value in my highs and lows. val: A vector value. It is a 2-way tuple (mag, dir). ts: The timestamp. """ speed, dirN = val # If this is a string, try to convert it to a float. if isinstance(speed, (six.string_types, six.text_type)): # Fail hard if unable to do the conversion: speed = to_float(speed) if isinstance(dirN, (six.string_types, six.text_type)): # Fail hard if unable to do the conversion: dirN = to_float(dirN) # Check for None and NaN: if speed is not None and speed == speed: if self.min is None or speed < self.min: self.min = speed self.mintime = ts if self.max is None or speed > self.max: self.max = speed self.maxtime = ts self.max_dir = dirN if self.lasttime is None or ts >= self.lasttime: self.last = (speed, dirN) self.lasttime = ts
def addSum(self, val, weight=1): """Add a vector value to my sum and squaresum. val: A vector value. It is a 2-way tuple (mag, dir) """ speed, dirN = val # If necessary, convert to float. Be prepared to catch an exception if not possible. try: speed = to_float(speed) except ValueError: speed = None try: dirN = to_float(dirN) except ValueError: dirN = None # Check for None and NaN: if speed is not None and speed == speed: self.sum += speed self.count += 1 self.wsum += weight * speed self.sumtime += weight self.squaresum += speed**2 self.wsquaresum += weight * speed**2 if dirN is not None: self.xsum += weight * speed * math.cos( math.radians(90.0 - dirN)) self.ysum += weight * speed * math.sin( math.radians(90.0 - dirN)) # It's OK for direction to be None, provided speed is zero: if dirN is not None or speed == 0: self.dirsumtime += weight
def addSum(self, val, weight=1): """Add a vector value to my sum and squaresum. val: A vector value. It is a 2-way tuple (mag, dir) """ speed, dirN = val # If this is a string, try to convert it to a float. if isinstance(speed, (six.string_types, six.text_type)): # Fail hard if unable to do the conversion: speed = to_float(speed) if isinstance(dirN, (six.string_types, six.text_type)): # Fail hard if unable to do the conversion: dirN = to_float(dirN) # Check for None and NaN: if speed is not None and speed == speed: self.sum += speed self.count += 1 self.wsum += weight * speed self.sumtime += weight self.squaresum += speed ** 2 self.wsquaresum += weight * speed ** 2 if dirN is not None: self.xsum += weight * speed * math.cos(math.radians(90.0 - dirN)) self.ysum += weight * speed * math.sin(math.radians(90.0 - dirN)) self.dirsumtime += weight
def __init__(self, svc_dict, altitude_vt, latitude, longitude): """Initialize an instance of WXXTypes Args: svc_dict: ConfigDict structure with configuration info altitude_vt: The altitude of the station as a ValueTuple latitude: Its latitude longitude: Its longitude """ self.svc_dict = svc_dict self.altitude_vt = altitude_vt self.latitude = latitude self.longitude = longitude # window of time for evapotranspiration calculation, in seconds self.et_period = to_int(svc_dict.get('et_period', 3600)) # atmospheric transmission coefficient [0.7-0.91] self.atc = to_float(svc_dict.get('atc', 0.8)) # Fail hard if out of range: if not 0.7 <= self.atc <= 0.91: raise weewx.ViolatedPrecondition("Atmospheric transmission " "coefficient (%f) out of " "range [.7-.91]" % self.atc) # atmospheric turbidity (2=clear, 4-5=smoggy) self.nfac = to_float(svc_dict.get('nfac', 2)) # height above ground at which wind is measured, in meters self.wind_height = to_float(svc_dict.get('wind_height', 2.0))
def addHiLo(self, val, ts): """Include a vector value in my highs and lows. val: A vector value. It is a 2-way tuple (mag, dir). ts: The timestamp. """ speed, dirN = val # If necessary, convert to float. Be prepared to catch an exception if not possible. try: speed = to_float(speed) except ValueError: speed = None try: dirN = to_float(dirN) except ValueError: dirN = None # Check for None and NaN: if speed is not None and speed == speed: if self.min is None or speed < self.min: self.min = speed self.mintime = ts if self.max is None or speed > self.max: self.max = speed self.maxtime = ts self.max_dir = dirN if self.lasttime is None or ts >= self.lasttime: self.last = (speed, dirN) self.lasttime = ts
def __init__(self, engine, config_dict): """Initialize an instance of StdWXXTypes""" super(StdWXXTypes, self).__init__(engine, config_dict) altitude_vt = engine.stn_info.altitude_vt latitude_f = engine.stn_info.latitude_f longitude_f = engine.stn_info.longitude_f try: calc_dict = config_dict['StdWXCalculate'] except KeyError: calc_dict = {} # window of time for evapotranspiration calculation, in seconds et_period = to_int(calc_dict.get('et_period', 3600)) # atmospheric transmission coefficient [0.7-0.91] atc = to_float(calc_dict.get('atc', 0.8)) # atmospheric turbidity (2=clear, 4-5=smoggy) nfac = to_float(calc_dict.get('nfac', 2)) # height above ground at which wind is measured, in meters wind_height = to_float(calc_dict.get('wind_height', 2.0)) # Adjust wind direction to null, if the wind speed is zero: ignore_zero_wind = to_bool(calc_dict.get('ignore_zero_wind', False)) maxSolarRad_algo = calc_dict.get('Algorithms', { 'maxSolarRad': 'rs' }).get('maxSolarRad', 'rs').lower() self.wxxtypes = WXXTypes(altitude_vt, latitude_f, longitude_f, et_period, atc, nfac, wind_height, ignore_zero_wind, maxSolarRad_algo) # Add to the xtypes system weewx.xtypes.xtypes.append(self.wxxtypes)
def chkcalib(self, calibdata): stcalib = self.obshardware.getcalibration() for i in calibdata: if to_float(calibdata[i]) != to_float(stcalib[i]): logerr("calibration error: %s is expexted to be %f but is %f" % (i, to_float(calibdata[i]), to_float(stcalib[i]))) return True return False
def new_archive_record(self, event): new_record_data = {} try: with open(self.filename, 'r') as fd: for line in fd: eq_index = line.find('=') # Ignore all lines that do not have an equal sign if eq_index == -1: continue name = line[:eq_index].strip() value = line[eq_index + 1:].strip() # Values which are empty strings are interpreted as None if value == '': value_f = None else: try: value_f = to_float(value) except ValueError as e: if self.ignore_value_error: continue logerr("Could not convert to float: %s" % value) raise new_record_data[self.label_map.get(name, name)] = value_f # Supply a unit system if one wasn't included in the file if 'usUnits' not in new_record_data: new_record_data['usUnits'] = self.unit_system # Convert the new values to the same unit system as the record target_data = weewx.units.to_std_system( new_record_data, event.record['usUnits']) # Add the converted values to the record: event.record.update(target_data) except IOError as e: logerr("Cannot open file. Reason: %s" % e)
def update_field(topic_dict, fieldinfo, field, value, unit_system): """ Update field. """ # pylint: disable=too-many-locals name = fieldinfo.get('name', field) append_unit_label = fieldinfo.get('append_unit_label', topic_dict.get('append_unit_label')) if append_unit_label: (unit_type, _) = weewx.units.getStandardUnitType(unit_system, name) unit_type = AbstractPublishThread.UNIT_REDUCTIONS.get( unit_type, unit_type) if unit_type is not None: name = "%s_%s" % (name, unit_type) unit = fieldinfo.get('unit', None) if unit is not None: (from_unit, from_group) = weewx.units.getStandardUnitType(unit_system, field) from_tuple = (value, from_unit, from_group) converted_value = weewx.units.convert(from_tuple, unit)[0] else: converted_value = value conversion_type = fieldinfo.get('conversion_type', topic_dict.get('conversion_type')) format_string = fieldinfo.get('format', topic_dict.get('format')) if conversion_type == 'integer': formatted_value = to_int(converted_value) else: formatted_value = format_string % converted_value if conversion_type == 'float': formatted_value = to_float(formatted_value) return name, formatted_value
def skip_this_post(self, time_ts): endupdate = time.mktime( time.strptime(time.strftime('%a %b %d 07:00:00 %Y'))) if (endupdate == self.lastupdate) | \ (to_float(time.time()) < endupdate): return True else: return False
def filter_data(upload_all, templates, inputs, append_units_label, conversion_type, record): """ Filter and format data for publishing. """ # pylint: disable=invalid-name # if uploading everything, we must check the upload variables list # every time since variables may come and go in a record. use the # inputs to override any generic template generation. if upload_all: for f in record: if f not in templates: templates[f] = _get_template(f, inputs.get(f, {}), append_units_label, record['usUnits']) # otherwise, create the list of upload variables once, based on the # user-specified list of inputs. elif not templates: for f in inputs: templates[f] = _get_template(f, inputs[f], append_units_label, record['usUnits']) # loop through the templates, populating them with data from the record data = dict() for k in templates: try: v = float(record.get(k)) name = templates[k].get('name', k) fmt = templates[k].get('format', '%s') to_units = templates[k].get('units') if to_units is not None: (from_unit, from_group) = weewx.units.getStandardUnitType( record['usUnits'], k) from_t = (v, from_unit, from_group) v = weewx.units.convert(from_t, to_units)[0] if conversion_type == 'integer': s = to_int(v) else: s = fmt % v if conversion_type == 'float': s = to_float(s) data[name] = s except (TypeError, ValueError): pass # FIXME: generalize this if 'latitude' in data and 'longitude' in data: parts = [str(data['latitude']), str(data['longitude'])] if 'altitude_meter' in data: parts.append(str(data['altitude_meter'])) elif 'altitude_foot' in data: parts.append(str(data['altitude_foot'])) data['position'] = ','.join(parts) return data
def addSum(self, val, weight=1): """Add a scalar value to my running sum and count.""" # If this is a string, try to convert it to a float. if isinstance(val, (six.string_types, six.text_type)): # Fail hard if unable to do the conversion: val = to_float(val) # Check for None and NaN: if val is not None and val == val: self.sum += val self.count += 1 self.wsum += val * weight self.sumtime += weight
def __init__(self, mm_dict, log_failure=True): """ Initialize Args: mm_dict: A dictionary containing the limits. The key is an observation type, the value is a 2- or 3-way tuple. If a 2-way tuple, then the values are (min, max) acceptable value in a record for that observation type. If a 3-way tuple, then the values are (min, max, unit), where min and max are as before, but the value 'unit' is the unit the min and max values are in. If 'unit' is not specified, then the values must be in the same unit as the incoming record (a risky supposition!). log_failure: True to log values outside of their limits. False otherwise. """ self.mm_dict = {} for obs_type in mm_dict: self.mm_dict[obs_type] = list(mm_dict[obs_type]) # The incoming min, max values may be from a ConfigObj, which are typically strings. # Convert to floats. self.mm_dict[obs_type][0] = to_float(self.mm_dict[obs_type][0]) self.mm_dict[obs_type][1] = to_float(self.mm_dict[obs_type][1]) self.log_failure = log_failure
def addSum(self, val, weight=1): """Add a scalar value to my running sum and count.""" # If necessary, convert to float. Be prepared to catch an exception if not possible. try: val = to_float(val) except ValueError: val = None # Check for None and NaN: if val is not None and val == val: self.sum += val self.count += 1 self.wsum += val * weight self.sumtime += weight
def __init__(self, queue, username, password, manager_dict, WEEWX_ROOT, lastpath='archive/rainlog.last', protocol_name='Rainlog', skip_upload=False, post_interval=None, max_backlog=1, stale=None, log_success=True, log_failure=True, timeout=60, max_tries=3, retry_wait=5): super(RainlogThread, self).__init__(queue, protocol_name=protocol_name, manager_dict=manager_dict, post_interval=post_interval, max_backlog=max_backlog, stale=stale, log_success=log_success, log_failure=log_failure, timeout=timeout, max_tries=max_tries, retry_wait=retry_wait) self.protocol_name = protocol_name self.username = username self.password = password if (lastpath[0] == '/'): self.lastfile = lastpath else: self.lastfile = WEEWX_ROOT + lastpath try: with open(self.lastfile, "r") as lastfile: self.lastupdate = to_float(lastfile.read()) lastfile.close() except IOError as e: self.create_lastfile() self.lastupdate = 0.0
def __init__(self, engine, config_dict): """Initialize the PressureCooker. """ super(StdPressureCooker, self).__init__(engine, config_dict) try: calc_dict = config_dict['StdWXCalculate'] except KeyError: calc_dict = {} max_delta_12h = to_float(calc_dict.get('max_delta_12h', 1800)) altimeter_algorithm = calc_dict.get('Algorithms', { 'altimeter': 'aaASOS' }).get('altimeter', 'aaASOS') self.pressure_cooker = PressureCooker(engine.stn_info.altitude_vt, max_delta_12h, altimeter_algorithm) # Add pressure_cooker to the XTypes system weewx.xtypes.xtypes.append(self.pressure_cooker)
def __init__(self, engine, config_dict): super(FieldCache, self).__init__(engine, config_dict) fieldcache_dict = config_dict.get('FieldCache', {}) unit_system_name = fieldcache_dict.get('unit_system', 'US').strip().upper() if unit_system_name not in weewx.units.unit_constants: raise ValueError("FieldCache: Unknown unit system: %s" % unit_system_name) unit_system = weewx.units.unit_constants[unit_system_name] self.fields = {} fields_dict = fieldcache_dict.get('fields', {}) for field in fieldcache_dict.get('fields', {}): self.fields[field] = {} self.fields[field]['expires_after'] = to_float(fields_dict[field].get('expires_after', None)) loginf(fieldcache_dict) self.cache = Cache(unit_system) self.bind(weewx.NEW_ARCHIVE_RECORD, self.new_archive_record)
def addHiLo(self, val, ts): """Include a scalar value in my highs and lows. val: A scalar value ts: The timestamp. """ # If this is a string, try to convert it to a float. if isinstance(val, (six.string_types, six.text_type)): # Fail hard if unable to do the conversion: val = to_float(val) # Check for None and NaN: if val is not None and val == val: if self.min is None or val < self.min: self.min = val self.mintime = ts if self.max is None or val > self.max: self.max = val self.maxtime = ts if self.lasttime is None or ts >= self.lasttime: self.last = val self.lasttime = ts
def addHiLo(self, val, ts): """Include a scalar value in my highs and lows. val: A scalar value ts: The timestamp. """ # If necessary, convert to float. Be prepared to catch an exception if not possible. try: val = to_float(val) except ValueError: val = None # Check for None and NaN: if val is not None and val == val: if self.min is None or val < self.min: self.min = val self.mintime = ts if self.max is None or val > self.max: self.max = val self.maxtime = ts if self.lasttime is None or ts >= self.lasttime: self.last = val self.lasttime = ts
def __init__(self, engine, config_dict): """Initialize the PressureCooker. """ super(StdPressureCooker, self).__init__(engine, config_dict) try: override_dict = config_dict['StdWXCalculate']['PressureCooker'] except KeyError: override_dict = {} # Get the default values, then merge the user overrides into it option_dict = weeutil.config.deep_copy(defaults_dict['StdWXCalculate']['PressureCooker']) option_dict.merge(override_dict) max_delta_12h = to_float(option_dict.get('max_delta_12h', 1800)) altimeter_algorithm = option_dict['altimeter'].get('algorithm', 'aaASOS') self.pressure_cooker = PressureCooker(engine.stn_info.altitude_vt, max_delta_12h, altimeter_algorithm) # Add pressure_cooker to the XTypes system weewx.xtypes.xtypes.append(self.pressure_cooker)
def _on_message(self, client, userdata, msg): # (match callback signature) pylint: disable=unused-argument # Wrap all the processing in a try, so it doesn't crash and burn on any error try: self.logger.debug("MessageCallbackProvider For %s received: %s" % (msg.topic, msg.payload)) fields = msg.payload.split(self.keyword_delimiter) data = {} for field in fields: eq_index = field.find(self.keyword_separator) # Ignore all fields that do not have the separator if eq_index == -1: self.logger.error( "MessageCallbackProvider on_message_keyword failed to find separator: %s" % self.keyword_separator) self.logger.error( "**** MessageCallbackProvider Ignoring field=%s " % field) continue name = field[:eq_index].strip() value = field[eq_index + 1:].strip() data[self.label_map.get(name, name)] = to_float(value) if data: self.topic_manager.append_data(msg.topic, data) else: self.logger.error( "MessageCallbackProvider on_message_keyword failed to find data in: topic=%s and payload=%s" % (msg.topic, msg.payload)) except Exception as exception: # (want to catch all) pylint: disable=broad-except self.logger.error( "MessageCallbackProvider on_message_keyword failed with: %s" % exception) self.logger.error( "**** MessageCallbackProvider Ignoring topic=%s and payload=%s" % (msg.topic, msg.payload))
def new_archive_record(self, event): new_record_data = {} try: with open(self.filename, 'r') as fd: for line in fd: eq_index = line.find('=') # Ignore all lines that do not have an equal sign if eq_index == -1: continue name = line[:eq_index].strip() value = line[eq_index + 1:].strip() new_record_data[self.label_map.get(name, name)] = to_float(value) # Supply a unit system if one wasn't included in the file if 'usUnits' not in new_record_data: new_record_data['usUnits'] = self.unit_system # Convert the new values to the same unit system as the record target_data = weewx.units.to_std_system( new_record_data, event.record['usUnits']) # Add the converted values to the record: event.record.update(target_data) except IOError as e: syslog.syslog(syslog.LOG_ERR, "FilePile: Cannot open file. Reason: %s" % e)
def get_concentrations(cfg: Configuration): for source in cfg.sources: if source.enable: record = collect_data(source.hostname, source.port, source.timeout, cfg.archive_interval, source.is_proxy) if record is not None: log.debug('get_concentrations: source: %s' % record) reading_ts = to_int(record['dateTime']) age_of_reading = time.time() - reading_ts if age_of_reading > cfg.archive_interval: log.info('Reading from %s:%d is old: %d seconds.' % (source.hostname, source.port, age_of_reading)) continue concentrations = Concentrations( timestamp=reading_ts, pm1_0=to_float(record['pm1_0_atm']), pm10_0=to_float(record['pm10_0_atm']), pm2_5_cf_1=to_float(record['pm2_5_cf_1']), pm2_5_cf_1_b= None, # If there is a second sensor, this will be updated below. current_temp_f=to_int(record['current_temp_f']), current_humidity=to_int(record['current_humidity']), ) # If there is a 'b' sensor, add it in and average the readings log.debug( 'get_concentrations: concentrations BEFORE averaing in b reading: %s' % concentrations) if 'pm1_0_atm_b' in record: concentrations.pm1_0 = (concentrations.pm1_0 + to_float( record['pm1_0_atm_b'])) / 2.0 concentrations.pm2_5_cf_1_b = to_float( record['pm2_5_cf_1_b']) concentrations.pm10_0 = (concentrations.pm10_0 + to_float( record['pm10_0_atm_b'])) / 2.0 log.debug('get_concentrations: concentrations: %s' % concentrations) return concentrations log.error('Could not get concentrations from any source.') return None
def genImages(self, gen_ts): """Generate the images. The time scales will be chosen to include the given timestamp, with nice beginning and ending times. gen_ts: The time around which plots are to be generated. This will also be used as the bottom label in the plots. [optional. Default is to use the time of the last record in the database.] """ t1 = time.time() ngen = 0 # Loop over each time span class (day, week, month, etc.): for timespan in self.image_dict.sections : # Now, loop over all plot names in this time span class: for plotname in self.image_dict[timespan].sections : # Accumulate all options from parent nodes: plot_options = weeutil.weeutil.accumulateLeaves( self.image_dict[timespan][plotname]) plotgen_ts = gen_ts if not plotgen_ts: binding = plot_options['data_binding'] archive = self.db_binder.get_manager(binding) plotgen_ts = archive.lastGoodStamp() if not plotgen_ts: plotgen_ts = time.time() image_root = os.path.join(self.config_dict['WEEWX_ROOT'], plot_options['HTML_ROOT']) # Get the path that the image is going to be saved to: img_file = os.path.join(image_root, '%s.png' % plotname) # Check whether this plot needs to be done at all: ai = plot_options.as_int('aggregate_interval') if plot_options.has_key('aggregate_interval') else None if skipThisPlot(plotgen_ts, ai, img_file) : continue # Create the subdirectory that the image is to be put in. # Wrap in a try block in case it already exists. try: os.makedirs(os.path.dirname(img_file)) except OSError: pass # Create a new instance of a time plot and start adding to it plot = weeplot.genplot.TimePlot(plot_options) # Calculate a suitable min, max time for the requested time # span and set it (minstamp, maxstamp, timeinc) = weeplot.utilities.scaletime(plotgen_ts - int(plot_options.get('time_length', 86400)), plotgen_ts) plot.setXScaling((minstamp, maxstamp, timeinc)) # Set the y-scaling, using any user-supplied hints: plot.setYScaling(weeutil.weeutil.convertToFloat(plot_options.get('yscale', ['None', 'None', 'None']))) # Get a suitable bottom label: bottom_label_format = plot_options.get('bottom_label_format', '%m/%d/%y %H:%M') bottom_label = time.strftime(bottom_label_format, time.localtime(plotgen_ts)) plot.setBottomLabel(bottom_label) # Set day/night display plot.setLocation(self.stn_info.latitude_f, self.stn_info.longitude_f) plot.setDayNight(to_bool(plot_options.get('show_daynight', False)), weeplot.utilities.tobgr(plot_options.get('daynight_day_color', '0xffffff')), weeplot.utilities.tobgr(plot_options.get('daynight_night_color', '0xf0f0f0')), weeplot.utilities.tobgr(plot_options.get('daynight_edge_color', '0xefefef'))) # Loop over each line to be added to the plot. for line_name in self.image_dict[timespan][plotname].sections: # Accumulate options from parent nodes. line_options = weeutil.weeutil.accumulateLeaves(self.image_dict[timespan][plotname][line_name]) # See what SQL variable type to use for this line. By # default, use the section name. var_type = line_options.get('data_type', line_name) # Look for aggregation type: aggregate_type = line_options.get('aggregate_type') if aggregate_type in (None, '', 'None', 'none'): # No aggregation specified. aggregate_type = aggregate_interval = None else : try: # Aggregation specified. Get the interval. aggregate_interval = line_options.as_int('aggregate_interval') except KeyError: syslog.syslog(syslog.LOG_ERR, "imagegenerator: aggregate interval required for aggregate type %s" % aggregate_type) syslog.syslog(syslog.LOG_ERR, "imagegenerator: line type %s skipped" % var_type) continue # Now its time to find and hit the database: binding = line_options['data_binding'] archive = self.db_binder.get_manager(binding) (start_vec_t, stop_vec_t, data_vec_t) = \ archive.getSqlVectors((minstamp, maxstamp), var_type, aggregate_type=aggregate_type, aggregate_interval=aggregate_interval) if weewx.debug: assert(len(start_vec_t) == len(stop_vec_t)) # Do any necessary unit conversions: new_start_vec_t = self.converter.convert(start_vec_t) new_stop_vec_t = self.converter.convert(stop_vec_t) new_data_vec_t = self.converter.convert(data_vec_t) # Add a unit label. NB: all will get overwritten except the # last. Get the label from the configuration dictionary. # TODO: Allow multiple unit labels, one for each plot line? unit_label = line_options.get('y_label', weewx.units.get_label_string(self.formatter, self.converter, var_type)) # Strip off any leading and trailing whitespace so it's # easy to center plot.setUnitLabel(unit_label.strip()) # See if a line label has been explicitly requested: label = line_options.get('label') if not label: # No explicit label. Is there a generic one? # If not, then the SQL type will be used instead label = self.title_dict.get(var_type, var_type) # See if a color has been explicitly requested. color = line_options.get('color') if color is not None: color = weeplot.utilities.tobgr(color) # Get the line width, if explicitly requested. width = to_int(line_options.get('width')) # Get the type of plot ("bar', 'line', or 'vector') plot_type = line_options.get('plot_type', 'line') interval_vec = None # Some plot types require special treatments: if plot_type == 'vector': vector_rotate_str = line_options.get('vector_rotate') vector_rotate = -float(vector_rotate_str) if vector_rotate_str is not None else None else: vector_rotate = None gap_fraction = None if plot_type == 'bar': interval_vec = [x[1] - x[0]for x in zip(new_start_vec_t.value, new_stop_vec_t.value)] elif plot_type == 'line': gap_fraction = to_float(line_options.get('line_gap_fraction')) if gap_fraction is not None: if not 0 < gap_fraction < 1: syslog.syslog(syslog.LOG_ERR, "imagegenerator: Gap fraction %5.3f outside range 0 to 1. Ignored." % gap_fraction) gap_fraction = None # Get the type of line (only 'solid' or 'none' for now) line_type = line_options.get('line_type', 'solid') if line_type.strip().lower() in ['', 'none']: line_type = None marker_type = line_options.get('marker_type') marker_size = to_int(line_options.get('marker_size', 8)) # Add the line to the emerging plot: plot.addLine(weeplot.genplot.PlotLine( new_stop_vec_t[0], new_data_vec_t[0], label = label, color = color, width = width, plot_type = plot_type, line_type = line_type, marker_type = marker_type, marker_size = marker_size, bar_width = interval_vec, vector_rotate = vector_rotate, gap_fraction = gap_fraction)) # OK, the plot is ready. Render it onto an image image = plot.render() try: # Now save the image image.save(img_file) ngen += 1 except IOError, e: syslog.syslog(syslog.LOG_CRIT, "imagegenerator: Unable to save to file '%s' %s:" % (img_file, e))
def genImages(self, gen_ts): """Generate the images. The time scales will be chosen to include the given timestamp, with nice beginning and ending times. gen_ts: The time around which plots are to be generated. This will also be used as the bottom label in the plots. [optional. Default is to use the time of the last record in the database.] """ t1 = time.time() ngen = 0 # determine how much logging is desired log_success = to_bool(search_up(self.image_dict, 'log_success', True)) # Loop over each time span class (day, week, month, etc.): for timespan in self.image_dict.sections: # Now, loop over all plot names in this time span class: for plotname in self.image_dict[timespan].sections: # Accumulate all options from parent nodes: plot_options = weeutil.weeutil.accumulateLeaves( self.image_dict[timespan][plotname]) plotgen_ts = gen_ts if not plotgen_ts: binding = plot_options['data_binding'] archive = self.db_binder.get_manager(binding) plotgen_ts = archive.lastGoodStamp() if not plotgen_ts: plotgen_ts = time.time() image_root = os.path.join(self.config_dict['WEEWX_ROOT'], plot_options['HTML_ROOT']) # Get the path that the image is going to be saved to: img_file = os.path.join(image_root, '%s.png' % plotname) ai = to_int(plot_options.get('aggregate_interval')) # Check whether this plot needs to be done at all: if skipThisPlot(plotgen_ts, ai, img_file): continue # skip image files that are fresh, but only if staleness is defined stale = to_int(plot_options.get('stale_age')) if stale is not None: t_now = time.time() try: last_mod = os.path.getmtime(img_file) if t_now - last_mod < stale: syslog.syslog( syslog.LOG_DEBUG, "imagegenerator: Skip '%s': last_mod=%s age=%s stale=%s" % (img_file, last_mod, t_now - last_mod, stale)) continue except os.error: pass # Create the subdirectory that the image is to be put in. # Wrap in a try block in case it already exists. try: os.makedirs(os.path.dirname(img_file)) except OSError: pass # Create a new instance of a time plot and start adding to it plot = weeplot.genplot.TimePlot(plot_options) # Calculate a suitable min, max time for the requested time. (minstamp, maxstamp, timeinc) = weeplot.utilities.scaletime( plotgen_ts - int(plot_options.get('time_length', 86400)), plotgen_ts) # Override the x interval if the user has given an explicit interval: timeinc_user = to_int(plot_options.get('x_interval')) if timeinc_user is not None: timeinc = timeinc_user plot.setXScaling((minstamp, maxstamp, timeinc)) # Set the y-scaling, using any user-supplied hints: plot.setYScaling( weeutil.weeutil.convertToFloat( plot_options.get('yscale', ['None', 'None', 'None']))) # Get a suitable bottom label: bottom_label_format = plot_options.get('bottom_label_format', '%m/%d/%y %H:%M') bottom_label = time.strftime(bottom_label_format, time.localtime(plotgen_ts)) plot.setBottomLabel(bottom_label) # Set day/night display plot.setLocation(self.stn_info.latitude_f, self.stn_info.longitude_f) plot.setDayNight( to_bool(plot_options.get('show_daynight', False)), weeplot.utilities.tobgr( plot_options.get('daynight_day_color', '0xffffff')), weeplot.utilities.tobgr( plot_options.get('daynight_night_color', '0xf0f0f0')), weeplot.utilities.tobgr( plot_options.get('daynight_edge_color', '0xefefef'))) # Loop over each line to be added to the plot. for line_name in self.image_dict[timespan][plotname].sections: # Accumulate options from parent nodes. line_options = weeutil.weeutil.accumulateLeaves( self.image_dict[timespan][plotname][line_name]) # See what SQL variable type to use for this line. By # default, use the section name. var_type = line_options.get('data_type', line_name) # Look for aggregation type: aggregate_type = line_options.get('aggregate_type') if aggregate_type in (None, '', 'None', 'none'): # No aggregation specified. aggregate_type = aggregate_interval = None else: try: # Aggregation specified. Get the interval. aggregate_interval = line_options.as_int( 'aggregate_interval') except KeyError: syslog.syslog( syslog.LOG_ERR, "imagegenerator: aggregate interval required for aggregate type %s" % aggregate_type) syslog.syslog( syslog.LOG_ERR, "imagegenerator: line type %s skipped" % var_type) continue # Now its time to find and hit the database: binding = line_options['data_binding'] archive = self.db_binder.get_manager(binding) (start_vec_t, stop_vec_t, data_vec_t) = \ archive.getSqlVectors((minstamp, maxstamp), var_type, aggregate_type=aggregate_type, aggregate_interval=aggregate_interval) if weewx.debug: assert (len(start_vec_t) == len(stop_vec_t)) # Get the type of plot ("bar', 'line', or 'vector') plot_type = line_options.get('plot_type', 'line') if aggregate_type and aggregate_type.lower() in ( 'avg', 'max', 'min') and plot_type != 'bar': # Put the point in the middle of the aggregate_interval for these aggregation types start_vec_t = ValueTuple([ x - aggregate_interval / 2.0 for x in start_vec_t[0] ], start_vec_t[1], start_vec_t[2]) stop_vec_t = ValueTuple([ x - aggregate_interval / 2.0 for x in stop_vec_t[0] ], stop_vec_t[1], stop_vec_t[2]) # Do any necessary unit conversions: new_start_vec_t = self.converter.convert(start_vec_t) new_stop_vec_t = self.converter.convert(stop_vec_t) new_data_vec_t = self.converter.convert(data_vec_t) # Add a unit label. NB: all will get overwritten except the # last. Get the label from the configuration dictionary. unit_label = line_options.get( 'y_label', weewx.units.get_label_string(self.formatter, self.converter, var_type)) # Strip off any leading and trailing whitespace so it's # easy to center plot.setUnitLabel(unit_label.strip()) # See if a line label has been explicitly requested: label = line_options.get('label') if not label: # No explicit label. Look up a generic one. NB: title_dict is a KeyDict which # will substitute the key if the value is not in the dictionary. label = self.title_dict[var_type] # See if a color has been explicitly requested. color = line_options.get('color') if color is not None: color = weeplot.utilities.tobgr(color) fill_color = line_options.get('fill_color') if fill_color is not None: fill_color = weeplot.utilities.tobgr(fill_color) # Get the line width, if explicitly requested. width = to_int(line_options.get('width')) interval_vec = None gap_fraction = None # Some plot types require special treatments: if plot_type == 'vector': vector_rotate_str = line_options.get('vector_rotate') vector_rotate = -float( vector_rotate_str ) if vector_rotate_str is not None else None else: vector_rotate = None if plot_type == 'bar': interval_vec = [ x[1] - x[0] for x in zip(new_start_vec_t.value, new_stop_vec_t.value) ] elif plot_type == 'line': gap_fraction = to_float( line_options.get('line_gap_fraction')) if gap_fraction is not None: if not 0 < gap_fraction < 1: syslog.syslog( syslog.LOG_ERR, "imagegenerator: Gap fraction %5.3f outside range 0 to 1. Ignored." % gap_fraction) gap_fraction = None # Get the type of line (only 'solid' or 'none' for now) line_type = line_options.get('line_type', 'solid') if line_type.strip().lower() in ['', 'none']: line_type = None marker_type = line_options.get('marker_type') marker_size = to_int(line_options.get('marker_size', 8)) # Add the line to the emerging plot: plot.addLine( weeplot.genplot.PlotLine(new_stop_vec_t[0], new_data_vec_t[0], label=label, color=color, fill_color=fill_color, width=width, plot_type=plot_type, line_type=line_type, marker_type=marker_type, marker_size=marker_size, bar_width=interval_vec, vector_rotate=vector_rotate, gap_fraction=gap_fraction)) # OK, the plot is ready. Render it onto an image image = plot.render() try: # Now save the image image.save(img_file) ngen += 1 except IOError as e: syslog.syslog( syslog.LOG_CRIT, "imagegenerator: Unable to save to file '%s' %s:" % (img_file, e)) t2 = time.time() if log_success: syslog.syslog( syslog.LOG_INFO, "imagegenerator: Generated %d images for %s in %.2f seconds" % (ngen, self.skin_dict['REPORT_NAME'], t2 - t1))
def __init__(self, engine, config_dict): """Initialize an instance of StdWXXTypes""" super(StdWXXTypes, self).__init__(engine, config_dict) altitude_vt = engine.stn_info.altitude_vt latitude_f = engine.stn_info.latitude_f longitude_f = engine.stn_info.longitude_f # These options were never documented. They have moved. Fail hard if they are present. if 'StdWXCalculate' in config_dict \ and any(key in config_dict['StdWXCalculate'] for key in ['rain_period', 'et_period', 'wind_height', 'atc', 'nfac', 'max_delta_12h']): raise ValueError( "Undocumented options for [StdWXCalculate] have moved. " "See User's Guide.") # Get any user-defined overrides try: override_dict = config_dict['StdWXCalculate']['WXXTypes'] except KeyError: override_dict = {} # Get the default values, then merge the user overrides into it option_dict = weeutil.config.deep_copy( defaults_dict['StdWXCalculate']['WXXTypes']) option_dict.merge(override_dict) # Get force_null from the option dictionary force_null = to_bool(option_dict['windDir'].get('force_null', True)) # Option ignore_zero_wind has also moved, but we will support it in a backwards-compatible # way, provided that it doesn't conflict with any setting of force_null. try: # Is there a value for ignore_zero_wind as well? ignore_zero_wind = to_bool( config_dict['StdWXCalculate']['ignore_zero_wind']) except KeyError: # No. We're done pass else: # No exception, so there must be a value for ignore_zero_wind. # Is there an explicit value for 'force_null'? That is, a default was not used? if 'force_null' in override_dict: # Yes. Make sure they match if ignore_zero_wind != to_bool(override_dict['force_null']): raise ValueError( "Conflicting values for " "ignore_zero_wind (%s) and force_null (%s)" % (ignore_zero_wind, force_null)) else: # No explicit value for 'force_null'. Use 'ignore_zero_wind' in its place force_null = ignore_zero_wind # maxSolarRad-related options maxSolarRad_algo = option_dict['maxSolarRad'].get('algorithm', 'rs').lower() # atmospheric transmission coefficient [0.7-0.91] atc = to_float(option_dict['maxSolarRad'].get('atc', 0.8)) # atmospheric turbidity (2=clear, 4-5=smoggy) nfac = to_float(option_dict['maxSolarRad'].get('nfac', 2)) # heatindex-related options heatindex_algo = option_dict['heatindex'].get('algorithm', 'new').lower() # Instantiate an instance of WXXTypes and register it with the XTypes system self.wxxtypes = WXXTypes(altitude_vt, latitude_f, longitude_f, atc=atc, nfac=nfac, force_null=force_null, maxSolarRad_algo=maxSolarRad_algo, heatindex_algo=heatindex_algo) weewx.xtypes.xtypes.append(self.wxxtypes) # ET-related options # height above ground at which wind is measured, in meters wind_height = to_float( weeutil.config.search_up(option_dict['ET'], 'wind_height', 2.0)) # window of time for evapotranspiration calculation, in seconds et_period = to_int(option_dict['ET'].get('et_period', 3600)) # The albedo to use albedo = to_float(option_dict['ET'].get('albedo', 0.23)) # The numerator constant for the reference crop type and time step. cn = to_float(option_dict['ET'].get('cn', 37)) # The denominator constant for the reference crop type and time step. cd = to_float(option_dict['ET'].get('cd', 0.34)) # Instantiate an instance of ETXType and register it with the XTypes system self.etxtype = ETXType(altitude_vt, latitude_f, longitude_f, et_period=et_period, wind_height=wind_height, albedo=albedo, cn=cn, cd=cd) weewx.xtypes.xtypes.append(self.etxtype)
def __init__(self, **stn_dict): loginf("version is %s" % DRIVER_VERSION) self.directmap = { 'wh2600USA_v2.2.0' : { 'dateTime' : ('epoch', to_int), 'inTemp' : ('inTemp', to_float), 'inHumidity' : ('inHumi', to_float), 'pressure' : ('AbsPress', to_float), 'outTemp' : ('outTemp',to_float), 'outHumidity' : ('outHumi', to_float), 'windDir' : ('windir', to_float), 'windSpeed' : ('avgwind', to_float), 'windGust' : ('gustspeed', to_float), 'radiation' : ('solarrad', to_float), 'UV' : ('uvi', to_float), 'rain' : ('rainofyearly', to_float), 'inTempBatteryStatus' : ('inBattSta', self.norm), 'outTempBatteryStatus' : ('outBattSta1', self.norm) }, 'default' : { 'dateTime' : ('epoch', to_int), 'inTemp' : ('inTemp', to_float), 'inHumidity' : ('inHumi', to_float), 'pressure' : ('AbsPress', to_float), 'outTemp' : ('outTemp',to_float), 'outHumidity' : ('outHumi', to_float), 'windDir' : ('windir', to_float), 'windSpeed' : ('avgwind', to_float), 'windGust' : ('gustspeed', to_float), 'radiation' : ('solarrad', to_float), 'UV' : ('uvi', to_float), 'rain' : ('rainofyearly', to_float), }, 'wu' : { 'dateTime' : ('epoch', to_int), 'outTemp' : ('tempf',to_float), 'outHumidity' : ('humidity', to_float), 'dewpoint' : ('dewptf', to_float), 'windchill' : ('windchillf', to_float), 'windDir' : ('winddir', to_float), 'windSpeed' : ('windspeedmph', to_float), 'windGust' : ('windgustmph', to_float), 'rain' : ('yearlyrainin', to_float), 'radiation' : ('solarradiation', to_float), 'UV' : ('UV', to_float), 'inTemp' : ('indoortempf', to_float), 'inHumidity' : ('indoorhumidity', to_float), 'pressure' : ('baromin', to_float), 'txBatteryStatus' : ('lowbatt', to_float), } } self.xferfile = stn_dict['xferfile'] self.poll_interval = float(stn_dict.get('poll_interval', 10)) self.dup_interval = float(stn_dict.get('dup_interval', 5)) self.max_tries = int(stn_dict.get('max_tries', 5)) self.retry_wait = int(stn_dict.get('retry_wait', 2)) self.directtx = to_bool(stn_dict.get('direct', False)) self.mode = stn_dict.get('mode', 'direct') self.check_calibration = to_bool(stn_dict.get('check_calibration',False)) self.set_calibration = to_bool(stn_dict.get('set_calibration', False)) self.lastrain = None self.lastpacket = 0 if self.mode == 'direct': self.obshardware = OpserverIPHardware(**stn_dict) if self.chkunits(self.expected_units): logerr("calibration error: %s is expexted to be %f but is %f" % (i, to_float(calibdata[i]), to_float(stcalib[i]))) raise Exception("Station units not set correctly") if self.obshardware.version() in self.directmap: self.map = self.directmap[self.obshardware.version()] else: loginf("Unknown firmware version: %s" % self.obshardware.version()) self.map = self.directmap['default'] else: self.map = self.directmap['wu'] if self.check_calibration: self.obshardware = OpserverIPHardware(**stn_dict) if self.chkunits(self.expected_units): raise Exception("Station units not set correctly") if 'calibration' in stn_dict and self.check_calibration: if self.chkcalib(stn_dict['calibration']): if(self.set_calibration): self.obshardware.setcalibration(stn_dict['calibration']) if self.chkcalib(stn_dict['calibration']): raise Exception("Setting calibration unsuccessful") else: raise Exception("calibration error") loginf("polling interval is %s" % self.poll_interval)
def gen_plot(self, plotgen_ts, plot_options, plot_dict): """Generate a single plot image. Args: plotgen_ts: A timestamp for which the plot will be valid. This is generally the last datum to be plotted. plot_options: A dictionary of plot options. plot_dict: A section in a ConfigObj. Each subsection will contain data about plots to be generated Returns: An instance of weeplot.genplot.TimePlot or None. If the former, it will be ready to render. If None, then skip_if_empty was truthy and no valid data were found. """ # Create a new instance of a time plot and start adding to it plot = weeplot.genplot.TimePlot(plot_options) # Calculate a suitable min, max time for the requested time. minstamp, maxstamp, timeinc = weeplot.utilities.scaletime( plotgen_ts - int(plot_options.get('time_length', 86400)), plotgen_ts) x_domain = weeutil.weeutil.TimeSpan(minstamp, maxstamp) # Override the x interval if the user has given an explicit interval: timeinc_user = to_int(plot_options.get('x_interval')) if timeinc_user is not None: timeinc = timeinc_user plot.setXScaling((x_domain.start, x_domain.stop, timeinc)) # Set the y-scaling, using any user-supplied hints: yscale = plot_options.get('yscale', ['None', 'None', 'None']) plot.setYScaling(weeutil.weeutil.convertToFloat(yscale)) # Get a suitable bottom label: bottom_label_format = plot_options.get('bottom_label_format', '%m/%d/%y %H:%M') bottom_label = time.strftime(bottom_label_format, time.localtime(plotgen_ts)) plot.setBottomLabel(bottom_label) # Set day/night display plot.setLocation(self.stn_info.latitude_f, self.stn_info.longitude_f) plot.setDayNight( to_bool(plot_options.get('show_daynight', False)), weeplot.utilities.tobgr( plot_options.get('daynight_day_color', '0xffffff')), weeplot.utilities.tobgr( plot_options.get('daynight_night_color', '0xf0f0f0')), weeplot.utilities.tobgr( plot_options.get('daynight_edge_color', '0xefefef'))) # Calculate the domain over which we should check for non-null data. It will be # 'None' if we are not to do the check at all. check_domain = _get_check_domain( plot_options.get('skip_if_empty', False), x_domain) # Set to True if we have _any_ data for the plot have_data = False # Loop over each line to be added to the plot. for line_name in plot_dict.sections: # Accumulate options from parent nodes. line_options = accumulateLeaves(plot_dict[line_name]) # See what observation type to use for this line. By default, use the section # name. var_type = line_options.get('data_type', line_name) # Find the database binding = line_options['data_binding'] db_manager = self.db_binder.get_manager(binding) # If we were asked, see if there is any non-null data in the plot skip = _skip_if_empty(db_manager, var_type, check_domain) if skip: # Nothing but null data. Skip this line and keep going continue # Either we found some non-null data, or skip_if_empty was false, and we don't care. have_data = True # Look for aggregation type: aggregate_type = line_options.get('aggregate_type') if aggregate_type in (None, '', 'None', 'none'): # No aggregation specified. aggregate_type = aggregate_interval = None else: try: # Aggregation specified. Get the interval. aggregate_interval = weeutil.weeutil.nominal_spans( line_options['aggregate_interval']) except KeyError: log.error( "Aggregate interval required for aggregate type %s", aggregate_type) log.error("Line type %s skipped", var_type) continue # we need to pass the line options and plotgen_ts to our xtype # first get a copy of line_options option_dict = dict(line_options) # but we need to pop off aggregate_type and # aggregate_interval as they are used as explicit arguments # in our xtypes call option_dict.pop('aggregate_type', None) option_dict.pop('aggregate_interval', None) # then add plotgen_ts option_dict['plotgen_ts'] = plotgen_ts try: start_vec_t, stop_vec_t, data_vec_t = weewx.xtypes.get_series( var_type, x_domain, db_manager, aggregate_type=aggregate_type, aggregate_interval=aggregate_interval, **option_dict) except weewx.UnknownType: # If skip_if_empty is set, it's OK if a type is unknown. if not skip: raise # Get the type of plot ('bar', 'line', or 'vector') plot_type = line_options.get('plot_type', 'line').lower() if aggregate_type and plot_type != 'bar': # If aggregating, put the point in the middle of the interval start_vec_t = ValueTuple( [x - aggregate_interval / 2.0 for x in start_vec_t[0]], # Value start_vec_t[1], # Unit start_vec_t[2]) # Unit group stop_vec_t = ValueTuple( [x - aggregate_interval / 2.0 for x in stop_vec_t[0]], # Velue stop_vec_t[1], # Unit stop_vec_t[2]) # Unit group # Convert the data to the requested units new_data_vec_t = self.converter.convert(data_vec_t) # Add a unit label. NB: all will get overwritten except the last. Get the label # from the configuration dictionary. unit_label = line_options.get( 'y_label', self.formatter.get_label_string(new_data_vec_t[1])) # Strip off any leading and trailing whitespace so it's easy to center plot.setUnitLabel(unit_label.strip()) # See if a line label has been explicitly requested: label = line_options.get('label') if label: # Yes. Get the text translation. Use the untranslated version if no translation # is available. label = self.text_dict.get(label, label) else: # No explicit label. Look up a generic one. Use the variable type itself if # there is no generic label. label = self.generic_dict.get(var_type, var_type) # See if a color has been explicitly requested. color = line_options.get('color') if color is not None: color = weeplot.utilities.tobgr(color) fill_color = line_options.get('fill_color') if fill_color is not None: fill_color = weeplot.utilities.tobgr(fill_color) # Get the line width, if explicitly requested. width = to_int(line_options.get('width')) interval_vec = None gap_fraction = None vector_rotate = None # Some plot types require special treatments: if plot_type == 'vector': vector_rotate_str = line_options.get('vector_rotate') vector_rotate = -float(vector_rotate_str) \ if vector_rotate_str is not None else None elif plot_type == 'bar': interval_vec = [ x[1] - x[0] for x in zip(start_vec_t.value, stop_vec_t.value) ] elif plot_type == 'line': gap_fraction = to_float(line_options.get('line_gap_fraction')) if gap_fraction is not None and not 0 < gap_fraction < 1: log.error( "Gap fraction %5.3f outside range 0 to 1. Ignored.", gap_fraction) gap_fraction = None else: log.error("Unknown plot type '%s'. Ignored", plot_type) continue # Get the type of line (only 'solid' or 'none' for now) line_type = line_options.get('line_type', 'solid') if line_type.strip().lower() in ['', 'none']: line_type = None marker_type = line_options.get('marker_type') marker_size = to_int(line_options.get('marker_size', 8)) # Add the line to the emerging plot: plot.addLine( weeplot.genplot.PlotLine(stop_vec_t[0], new_data_vec_t[0], label=label, color=color, fill_color=fill_color, width=width, plot_type=plot_type, line_type=line_type, marker_type=marker_type, marker_size=marker_size, bar_width=interval_vec, vector_rotate=vector_rotate, gap_fraction=gap_fraction)) # Return the constructed plot if it has any non-null data, otherwise return None return plot if have_data else None
def plot_image( self, species_name, plot_options, plotgen_ts, stamps, vectors, ): line_options = plot_options (minstamp, maxstamp, timeinc) = stamps # Create a new instance of a time plot and start adding to it result = TimeHorizonPlot(plot_options) # Override the x interval if the user has given an explicit interval: timeinc_user = to_int(plot_options.get('x_interval')) if timeinc_user is not None: timeinc = timeinc_user result.setXScaling((minstamp, maxstamp, timeinc)) # Set the y-scaling, using any user-supplied hints: result.setYScaling( weeutil.weeutil.convertToFloat( plot_options.get('yscale', ['None', 'None', 'None']))) # Get a suitable bottom label: ti_t = time.time() bottom_label_format = plot_options.get('bottom_label_format', '%d.%m.%Y') bottom_label = time.strftime(bottom_label_format, time.localtime(ti_t)) result.setBottomLabel(bottom_label) # This generator acts on only one variable type: var_type = 'heatdeg' # Get the type of plot ("bar', 'line', or 'vector') plot_type = line_options.get('plot_type', 'line') # Add a unit label. unit_label = plot_options.get( 'y_label', weewx.units.get_label_string(self.formatter, self.converter, var_type)) # Strip off any leading and trailing whitespace so it's easy to center result.setUnitLabel(unit_label.strip()) # See if a line label has been explicitly requested: label = line_options.get('label') if not label: # No explicit label. Is there a generic one? # If not, then the SQL type will be used instead label = self.title_dict.get(var_type, var_type) # Insert horizon lines. horizons = [] biofix = get_float_t(line_options.get('biofix_actual'), 'group_degree_day') if biofix: biofix_label = 'Biofix' else: biofix = get_float_t( line_options.get('biofix_estimated', [79.4, 'degree_C_day']), 'group_degree_day') biofix_label = 'Biofix (Prognose/ Estimated)' horizons.append([biofix, biofix_label]) offsets = self.cydia_dict[species_name].get('Offsets_from_Biofix') if offsets: for (horizon_label, offset) in list(offsets.iteritems()): horizon_val = offset.get('offset') if horizon_val: horizon = get_float_t(horizon_val, 'group_degree_day') if horizon: horizons.append([biofix + horizon, horizon_label]) result.horizons = [(self.converter.convert(horizon), horizon_label) for (horizon, horizon_label) in horizons] result.horizon_min = None result.horizon_max = None for ((horizon, horizon_units, horizon_group), horizon_label) in result.horizons[:1]: result.horizon_min = horizon result.horizon_max = horizon for ((horizon, horizon_units, horizon_group), horizon_label) in result.horizons[1:]: result.horizon_min = min(result.horizon_min, horizon) result.horizon_max = max(result.horizon_max, horizon) # See if a color has been explicitly requested. color = line_options.get('color') if color is not None: color = weeplot.utilities.tobgr(color) fill_color = line_options.get('fill_color') if fill_color is not None: fill_color = weeplot.utilities.tobgr(fill_color) result.horizon_top_color = weeplot.utilities.tobgr( weeplot.utilities.tobgr( line_options.get('horizon_top_color', '0xffffff'))) result.horizon_bottom_color = weeplot.utilities.tobgr( weeplot.utilities.tobgr( line_options.get('horizon_bottom_color', '0xf0f0f0'))) result.horizon_edge_color = weeplot.utilities.tobgr( weeplot.utilities.tobgr( line_options.get('horizon_edge_color', '0xefefef'))) result.horizon_gradient = int(line_options.get('horizon_gradient', 20)) result.horizon_label_font_path = line_options.get( 'horizon_label_font_path', '/usr/share/fonts/truetype/freefont/FreeMonoBold.ttf') result.horizon_label_font_size = int( line_options.get('horizon_label_font_size', 12)) result.horizon_label_font_color = weeplot.utilities.tobgr( line_options.get('horizon_label_font_color', '0x000000')) result.horizon_label_offset = int( line_options.get('horizon_label_offset', 3)) # Get the line width, if explicitly requested. width = to_int(line_options.get('width')) # Some plot types require special treatments: interval_vec = None vector_rotate = None gap_fraction = None if plot_type == 'bar': interval_vec = [ x[1] - x[ZERO] for x in zip(vectors['date'].value, vectors['date'].value) ] elif plot_type == 'line': gap_fraction = to_float(line_options.get('line_gap_fraction')) if gap_fraction is not None: if not ZERO < gap_fraction < 1: log.error("Gap fraction %5.3f outside range 0 to 1. Ignored.", gap_fraction) gap_fraction = None # Get the type of line (only 'solid' or 'none' for now) line_type = line_options.get('line_type', 'solid') if line_type.strip().lower() in ['', 'none']: line_type = None marker_type = line_options.get('marker_type') marker_size = to_int(line_options.get('marker_size', 8)) # Get the spacings between labels, i.e. every how many lines a label is drawn x_label_spacing = plot_options.get('x_label_spacing', 2) y_label_spacing = plot_options.get('y_label_spacing', 2) # Add the line to the emerging plot: result.addLine( weeplot.genplot.PlotLine( vectors['date'][ZERO], vectors['dd_cumulative'][ZERO], label=label, color=color, fill_color=fill_color, width=width, plot_type=plot_type, line_type=line_type, marker_type=marker_type, marker_size=marker_size, bar_width=interval_vec, vector_rotate=vector_rotate, gap_fraction=gap_fraction, )) return result