Пример #1
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 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
Пример #2
0
    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
Пример #3
0
    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
Пример #4
0
    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))
Пример #5
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
Пример #6
0
    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)
Пример #7
0
 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
Пример #8
0
 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)
Пример #9
0
    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
Пример #10
0
 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
Пример #12
0
    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
Пример #13
0
    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
Пример #14
0
    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
Пример #15
0
    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
Пример #16
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)
Пример #17
0
    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)
Пример #18
0
    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
Пример #19
0
    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
Пример #20
0
    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)
Пример #21
0
    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))
Пример #22
0
 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)
Пример #23
0
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
Пример #24
0
    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))
Пример #25
0
    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))
Пример #26
0
    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)
Пример #27
0
    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)
Пример #28
0
    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
Пример #29
0
    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