class AgroManager: """ A manager objects that provides a full interface to OWM Agro API. :param API_key: the OWM Weather API key :type API_key: str :param config: the configuration dictionary :type config: dict :returns: an `AgroManager` instance :raises: `AssertionError` when no API Key is provided """ def __init__(self, API_key, config): assert isinstance(API_key, str), 'You must provide a valid API Key' self.API_key = API_key assert isinstance(config, dict) self.http_client = HttpClient(API_key, config, ROOT_AGRO_API) self.geotiff_downloader_http_client = HttpClient( self.API_key, config, ROOT_DOWNLOAD_GEOTIFF_API) self.png_downloader_http_client = HttpClient(self.API_key, config, ROOT_DOWNLOAD_PNG_API) def agro_api_version(self): return AGRO_API_VERSION # POLYGON API subset methods def create_polygon(self, geopolygon, name=None): """ Create a new polygon on the Agro API with the given parameters :param geopolygon: the geopolygon representing the new polygon :type geopolygon: `pyowm.utils.geo.Polygon` instance :param name: optional mnemonic name for the new polygon :type name: str :return: a `pyowm.agro10.polygon.Polygon` instance """ assert geopolygon is not None assert isinstance(geopolygon, GeoPolygon) data = { 'geo_json': { "type": "Feature", "properties": {}, "geometry": geopolygon.to_dict(), } } if name is not None: data['name'] = name status, payload = self.http_client.post( POLYGONS_URI, params={'appid': self.API_key}, data=data, headers={'Content-Type': 'application/json'}) return Polygon.from_dict(payload) def get_polygons(self): """ Retrieves all of the user's polygons registered on the Agro API. :returns: list of `pyowm.agro10.polygon.Polygon` objects """ status, data = self.http_client.get_json( POLYGONS_URI, params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) return [Polygon.from_dict(item) for item in data] def get_polygon(self, polygon_id): """ Retrieves a named polygon registered on the Agro API. :param id: the ID of the polygon :type id: str :returns: a `pyowm.agro10.polygon.Polygon` object """ status, data = self.http_client.get_json( NAMED_POLYGON_URI % str(polygon_id), params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) return Polygon.from_dict(data) def update_polygon(self, polygon): """ Updates on the Agro API the Polygon identified by the ID of the provided polygon object. Currently this only changes the mnemonic name of the remote polygon :param polygon: the `pyowm.agro10.polygon.Polygon` object to be updated :type polygon: `pyowm.agro10.polygon.Polygon` instance :returns: `None` if update is successful, an exception otherwise """ assert polygon.id is not None status, _ = self.http_client.put( NAMED_POLYGON_URI % str(polygon.id), params={'appid': self.API_key}, data=dict(name=polygon.name), headers={'Content-Type': 'application/json'}) def delete_polygon(self, polygon): """ Deletes on the Agro API the Polygon identified by the ID of the provided polygon object. :param polygon: the `pyowm.agro10.polygon.Polygon` object to be deleted :type polygon: `pyowm.agro10.polygon.Polygon` instance :returns: `None` if deletion is successful, an exception otherwise """ assert polygon.id is not None status, _ = self.http_client.delete( NAMED_POLYGON_URI % str(polygon.id), params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) # SOIL API subset methods def soil_data(self, polygon): """ Retrieves the latest soil data on the specified polygon :param polygon: the reference polygon you want soil data for :type polygon: `pyowm.agro10.polygon.Polygon` instance :returns: a `pyowm.agro10.soil.Soil` instance """ assert polygon is not None assert isinstance(polygon, Polygon) polyd = polygon.id status, data = self.http_client.get_json( SOIL_URI, params={ 'appid': self.API_key, 'polyid': polyd }, headers={'Content-Type': 'application/json'}) the_dict = { 'reference_time': data['dt'], 'surface_temp': data['t0'], 'ten_cm_temp': data['t10'], 'moisture': data['moisture'], 'polygon_id': polyd, } return Soil.from_dict(the_dict) # Satellite Imagery subset methods def search_satellite_imagery(self, polygon_id, acquired_from, acquired_to, img_type=None, preset=None, min_resolution=None, max_resolution=None, acquired_by=None, min_cloud_coverage=None, max_cloud_coverage=None, min_valid_data_coverage=None, max_valid_data_coverage=None): """ Searches on the Agro API the metadata for all available satellite images that contain the specified polygon and acquired during the specified time interval; and optionally matching the specified set of filters: - image type (eg. GeoTIF) - image preset (eg. false color, NDVI, ...) - min/max acquisition resolution - acquiring satellite - min/max cloud coverage on acquired scene - min/max valid data coverage on acquired scene :param polygon_id: the ID of the reference polygon :type polygon_id: str :param acquired_from: lower edge of acquisition interval, UNIX timestamp :type acquired_from: int :param acquired_to: upper edge of acquisition interval, UNIX timestamp :type acquired_to: int :param img_type: the desired file format type of the images. Allowed values are given by `pyowm.commons.enums.ImageTypeEnum` :type img_type: `pyowm.commons.databoxes.ImageType` :param preset: the desired preset of the images. Allowed values are given by `pyowm.agroapi10.enums.PresetEnum` :type preset: str :param min_resolution: minimum resolution for images, px/meters :type min_resolution: int :param max_resolution: maximum resolution for images, px/meters :type max_resolution: int :param acquired_by: short symbol of the satellite that acquired the image (eg. "l8") :type acquired_by: str :param min_cloud_coverage: minimum cloud coverage percentage on acquired images :type min_cloud_coverage: int :param max_cloud_coverage: maximum cloud coverage percentage on acquired images :type max_cloud_coverage: int :param min_valid_data_coverage: minimum valid data coverage percentage on acquired images :type min_valid_data_coverage: int :param max_valid_data_coverage: maximum valid data coverage percentage on acquired images :type max_valid_data_coverage: int :return: a list of `pyowm.agro10.imagery.MetaImage` subtypes instances """ assert polygon_id is not None assert acquired_from is not None assert acquired_to is not None assert acquired_from <= acquired_to, 'Start timestamp of acquisition window must come before its end' if min_resolution is not None: assert min_resolution > 0, 'Minimum resolution must be positive' if max_resolution is not None: assert max_resolution > 0, 'Maximum resolution must be positive' if min_resolution is not None and max_resolution is not None: assert min_resolution <= max_resolution, 'Mininum resolution must be lower than maximum resolution' if min_cloud_coverage is not None: assert min_cloud_coverage >= 0, 'Minimum cloud coverage must be non negative' if max_cloud_coverage is not None: assert max_cloud_coverage >= 0, 'Maximum cloud coverage must be non negative' if min_cloud_coverage is not None and max_cloud_coverage is not None: assert min_cloud_coverage <= max_cloud_coverage, 'Minimum cloud coverage must be lower than maximum cloud coverage' if min_valid_data_coverage is not None: assert min_valid_data_coverage >= 0, 'Minimum valid data coverage must be non negative' if max_valid_data_coverage is not None: assert max_valid_data_coverage >= 0, 'Maximum valid data coverage must be non negative' if min_valid_data_coverage is not None and max_valid_data_coverage is not None: assert min_valid_data_coverage <= max_valid_data_coverage, 'Minimum valid data coverage must be lower than maximum valid data coverage' # prepare params params = dict(appid=self.API_key, polyid=polygon_id, start=acquired_from, end=acquired_to) if min_resolution is not None: params['resolution_min'] = min_resolution if max_resolution is not None: params['resolution_max'] = max_resolution if acquired_by is not None: params['type'] = acquired_by if min_cloud_coverage is not None: params['clouds_min'] = min_cloud_coverage if max_cloud_coverage is not None: params['clouds_max'] = max_cloud_coverage if min_valid_data_coverage is not None: params['coverage_min'] = min_valid_data_coverage if max_valid_data_coverage is not None: params['coverage_max'] = max_valid_data_coverage # call API status, data = self.http_client.get_json(SATELLITE_IMAGERY_SEARCH_URI, params=params) result_set = SatelliteImagerySearchResultSet( polygon_id, data, timestamps.now(timeformat='unix')) # further filter by img_type and/or preset (if specified) if img_type is not None and preset is not None: return result_set.with_img_type_and_preset(img_type, preset) elif img_type is not None: return result_set.with_img_type(img_type) elif preset is not None: return result_set.with_preset(preset) else: return result_set.all() def download_satellite_image(self, metaimage, x=None, y=None, zoom=None, palette=None): """ Downloads the satellite image described by the provided metadata. In case the satellite image is a tile, then tile coordinates and zoom must be provided. An optional palette ID can be provided, if supported by the downloaded preset (currently only NDVI is supported) :param metaimage: the satellite image's metadata, in the form of a `MetaImage` subtype instance :type metaimage: a `pyowm.agroapi10.imagery.MetaImage` subtype :param x: x tile coordinate (only needed in case you are downloading a tile image) :type x: int or `None` :param y: y tile coordinate (only needed in case you are downloading a tile image) :type y: int or `None` :param zoom: zoom level (only needed in case you are downloading a tile image) :type zoom: int or `None` :param palette: ID of the color palette of the downloaded images. Values are provided by `pyowm.agroapi10.enums.PaletteEnum` :type palette: str or `None` :return: a `pyowm.agroapi10.imagery.SatelliteImage` instance containing both image's metadata and data """ if palette is not None: assert isinstance(palette, str) params = dict(paletteid=palette) else: palette = PaletteEnum.GREEN params = {} # polygon PNG if isinstance(metaimage, MetaPNGImage): prepared_url = metaimage.url status, data = self.png_downloader_http_client.get_png( prepared_url, params=params) img = Image(data, metaimage.image_type) return SatelliteImage( metaimage, img, downloaded_on=timestamps.now(timeformat='unix'), palette=palette) # GeoTIF elif isinstance(metaimage, MetaGeoTiffImage): prepared_url = metaimage.url status, data = self.geotiff_downloader_http_client.get_geotiff( prepared_url, params=params) img = Image(data, metaimage.image_type) return SatelliteImage( metaimage, img, downloaded_on=timestamps.now(timeformat='unix'), palette=palette) # tile PNG elif isinstance(metaimage, MetaTile): assert x is not None assert y is not None assert zoom is not None prepared_url = self._fill_url(metaimage.url, x, y, zoom) status, data = self.http_client.get_png(prepared_url, params=params) img = Image(data, metaimage.image_type) tile = Tile(x, y, zoom, None, img) return SatelliteImage( metaimage, tile, downloaded_on=timestamps.now(timeformat='unix'), palette=palette) else: raise ValueError("Cannot download: unsupported MetaImage subtype") def stats_for_satellite_image(self, metaimage): """ Retrieves statistics for the satellite image described by the provided metadata. This is currently only supported 'EVI' and 'NDVI' presets :param metaimage: the satellite image's metadata, in the form of a `MetaImage` subtype instance :type metaimage: a `pyowm.agroapi10.imagery.MetaImage` subtype :return: dict """ if metaimage.preset not in [PresetEnum.EVI, PresetEnum.NDVI]: raise ValueError("Unsupported image preset: should be EVI or NDVI") if metaimage.stats_url is None: raise ValueError("URL for image statistics is not defined") status, data = self.http_client.get_json(metaimage.stats_url, params={}) return data # Utilities def _fill_url(self, url_template, x, y, zoom): return url_template.replace('{x}', str(x)).replace('{y}', str(y)).replace( '{z}', str(zoom)) def __repr__(self): return '<%s.%s>' % (__name__, self.__class__.__name__)
class StationsManager: """ A manager objects that provides a full interface to OWM Stations API. Mainly it implements CRUD methods on Station entities and the corresponding measured datapoints. :param API_key: the OWM Weather API key :type API_key: str :param config: the configuration dictionary :type config: dict :returns: a *StationsManager* instance :raises: *AssertionError* when no API Key is provided """ def __init__(self, API_key, config): assert API_key is not None, 'You must provide a valid API Key' self.API_key = API_key assert isinstance(config, dict) self.http_client = HttpClient(API_key, config, ROOT_STATIONS_API_URL) def stations_api_version(self): return STATIONS_API_VERSION # STATIONS Methods def get_stations(self): """ Retrieves all of the user's stations registered on the Stations API. :returns: list of *pyowm.stationsapi30.station.Station* objects """ status, data = self.http_client.get_json( STATIONS_URI, params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) return [Station.from_dict(item) for item in data] def get_station(self, id): """ Retrieves a named station registered on the Stations API. :param id: the ID of the station :type id: str :returns: a *pyowm.stationsapi30.station.Station* object """ status, data = self.http_client.get_json( NAMED_STATION_URI % str(id), params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) return Station.from_dict(data) def create_station(self, external_id, name, lat, lon, alt=None): """ Create a new station on the Station API with the given parameters :param external_id: the user-given ID of the station :type external_id: str :param name: the name of the station :type name: str :param lat: latitude of the station :type lat: float :param lon: longitude of the station :type lon: float :param alt: altitude of the station :type alt: float :returns: the new *pyowm.stationsapi30.station.Station* object """ assert external_id is not None assert name is not None assert lon is not None assert lat is not None if lon < -180.0 or lon > 180.0: raise ValueError("'lon' value must be between -180 and 180") if lat < -90.0 or lat > 90.0: raise ValueError("'lat' value must be between -90 and 90") if alt is not None and alt < 0.0: raise ValueError("'alt' value must not be negative") status, payload = self.http_client.post( STATIONS_URI, params={'appid': self.API_key}, data=dict(external_id=external_id, name=name, lat=lat, lon=lon, alt=alt), headers={'Content-Type': 'application/json'}) return Station.from_dict(payload) def update_station(self, station): """ Updates the Station API record identified by the ID of the provided *pyowm.stationsapi30.station.Station* object with all of its fields :param station: the *pyowm.stationsapi30.station.Station* object to be updated :type station: *pyowm.stationsapi30.station.Station* :returns: `None` if update is successful, an exception otherwise """ assert station.id is not None status, _ = self.http_client.put( NAMED_STATION_URI % str(station.id), params={'appid': self.API_key}, data=dict(external_id=station.external_id, name=station.name, lat=station.lat, lon=station.lon, alt=station.alt), headers={'Content-Type': 'application/json'}) def delete_station(self, station): """ Deletes the Station API record identified by the ID of the provided *pyowm.stationsapi30.station.Station*, along with all its related measurements :param station: the *pyowm.stationsapi30.station.Station* object to be deleted :type station: *pyowm.stationsapi30.station.Station* :returns: `None` if deletion is successful, an exception otherwise """ assert station.id is not None status, _ = self.http_client.delete( NAMED_STATION_URI % str(station.id), params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) # Measurements-related methods def send_measurement(self, measurement): """ Posts the provided Measurement object's data to the Station API. :param measurement: the *pyowm.stationsapi30.measurement.Measurement* object to be posted :type measurement: *pyowm.stationsapi30.measurement.Measurement* instance :returns: `None` if creation is successful, an exception otherwise """ assert measurement is not None assert measurement.station_id is not None status, _ = self.http_client.post( MEASUREMENTS_URI, params={'appid': self.API_key}, data=[self._structure_dict(measurement)], headers={'Content-Type': 'application/json'}) def send_measurements(self, list_of_measurements): """ Posts data about the provided list of Measurement objects to the Station API. The objects may be related to different station IDs. :param list_of_measurements: list of *pyowm.stationsapi30.measurement.Measurement* objects to be posted :type list_of_measurements: list of *pyowm.stationsapi30.measurement.Measurement* instances :returns: `None` if creation is successful, an exception otherwise """ assert list_of_measurements is not None assert all(m.station_id is not None for m in list_of_measurements) msmts = [self._structure_dict(m) for m in list_of_measurements] status, _ = self.http_client.post( MEASUREMENTS_URI, params={'appid': self.API_key}, data=msmts, headers={'Content-Type': 'application/json'}) def get_measurements(self, station_id, aggregated_on, from_timestamp, to_timestamp, limit=100): """ Reads measurements of a specified station recorded in the specified time window and aggregated on minute, hour or day. Optionally, the number of resulting measurements can be limited. :param station_id: unique station identifier :type station_id: str :param aggregated_on: aggregation time-frame for this measurement :type aggregated_on: string between 'm','h' and 'd' :param from_timestamp: Unix timestamp corresponding to the beginning of the time window :type from_timestamp: int :param to_timestamp: Unix timestamp corresponding to the end of the time window :type to_timestamp: int :param limit: max number of items to be returned. Defaults to 100 :type limit: int :returns: list of *pyowm.stationsapi30.measurement.AggregatedMeasurement* objects """ assert station_id is not None assert aggregated_on is not None assert from_timestamp is not None assert from_timestamp > 0 assert to_timestamp is not None assert to_timestamp > 0 if to_timestamp < from_timestamp: raise ValueError( "End timestamp can't be earlier than begin timestamp") assert isinstance(limit, int) assert limit >= 0 query = { 'appid': self.API_key, 'station_id': station_id, 'type': aggregated_on, 'from': from_timestamp, 'to': to_timestamp, 'limit': limit } status, data = self.http_client.get_json( MEASUREMENTS_URI, params=query, headers={'Content-Type': 'application/json'}) return [AggregatedMeasurement.from_dict(item) for item in data] def send_buffer(self, buffer): """ Posts to the Stations API data about the Measurement objects contained into the provided Buffer instance. :param buffer: the *pyowm.stationsapi30.buffer.Buffer* instance whose measurements are to be posted :type buffer: *pyowm.stationsapi30.buffer.Buffer* instance :returns: `None` if creation is successful, an exception otherwise """ assert buffer is not None msmts = [self._structure_dict(m) for m in buffer.measurements] status, _ = self.http_client.post( MEASUREMENTS_URI, params={'appid': self.API_key}, data=msmts, headers={'Content-Type': 'application/json'}) def _structure_dict(self, measurement): d = measurement.to_dict() return { 'station_id': d['station_id'], 'dt': d['timestamp'], 'temperature': d['temperature'], 'wind_speed': d['wind_speed'], 'wind_gust': d['wind_gust'], 'wind_deg': d['wind_deg'], 'pressure': d['pressure'], 'humidity': d['humidity'], 'rain_1h': d['rain_1h'], 'rain_6h': d['rain_6h'], 'rain_24h': d['rain_24h'], 'snow_1h': d['snow_1h'], 'snow_6h': d['snow_6h'], 'snow_24h': d['snow_24h'], 'dew_point': d['dew_point'], 'humidex': d['humidex'], 'heat_index': d['heat_index'], 'visibility_distance': d['visibility_distance'], 'visibility_prefix': d['visibility_prefix'], 'clouds': [ dict(distance=d['clouds_distance']), dict(condition=d['clouds_condition']), dict(cumulus=d['clouds_cumulus']), ], 'weather': [ dict(precipitation=d['weather_precipitation']), dict(descriptor=d['weather_descriptor']), dict(intensity=d['weather_intensity']), dict(proximity=d['weather_proximity']), dict(obscuration=d['weather_obscuration']), dict(other=d['weather_other']), ], } def __repr__(self): return '<%s.%s>' % (__name__, self.__class__.__name__)
class AlertManager: """ A manager objects that provides a full interface to OWM Alert API. It implements CRUD methods on Trigger entities and read/deletion of related Alert objects :param API_key: the OWM web API key :type API_key: str :returns: an *AlertManager* instance :raises: *AssertionError* when no API Key is provided """ def __init__(self, API_key): assert API_key is not None, 'You must provide a valid API Key' self.API_key = API_key self.trigger_parser = TriggerParser() self.alert_parser = AlertParser() self.http_client = HttpClient() def alert_api_version(self): return ALERT_API_VERSION # TRIGGER methods def create_trigger(self, start, end, conditions, area, alert_channels=None): """ Create a new trigger on the Alert API with the given parameters :param start: time object representing the time when the trigger begins to be checked :type start: int, ``datetime.datetime`` or ISO8601-formatted string :param end: time object representing the time when the trigger ends to be checked :type end: int, ``datetime.datetime`` or ISO8601-formatted string :param conditions: the `Condition` objects representing the set of checks to be done on weather variables :type conditions: list of `pyowm.utils.alertapi30.Condition` instances :param area: the geographic are over which conditions are checked: it can be composed by multiple geoJSON types :type area: list of geoJSON types :param alert_channels: the alert channels through which alerts originating from this `Trigger` can be consumed. Defaults to OWM API polling :type alert_channels: list of `pyowm.utils.alertapi30.AlertChannel` instances :returns: a *Trigger* instance :raises: *ValueError* when start or end epochs are `None` or when end precedes start or when conditions or area are empty collections """ assert start is not None assert end is not None # prepare time period unix_start = timeformatutils.to_UNIXtime(start) unix_end = timeformatutils.to_UNIXtime(end) unix_current = timeutils.now(timeformat='unix') if unix_start >= unix_end: raise ValueError( "The start timestamp must precede the end timestamp") delta_millis_start = timeutils.millis_offset_between_epochs( unix_current, unix_start) delta_millis_end = timeutils.millis_offset_between_epochs( unix_current, unix_end) the_time_period = { "start": { "expression": "after", "amount": delta_millis_start }, "end": { "expression": "after", "amount": delta_millis_end } } assert conditions is not None if len(conditions) == 0: raise ValueError( 'A trigger must contain at least one condition: you provided none' ) the_conditions = [ dict(name=c.weather_param, expression=c.operator, amount=c.amount) for c in conditions ] assert area is not None if len(area) == 0: raise ValueError( 'The area for a trigger must contain at least one geoJSON type: you provided none' ) the_area = [a.as_dict() for a in area] # >>> for the moment, no specific handling for alert channels status, payload = self.http_client.post( TRIGGERS_URI, params={'appid': self.API_key}, data=dict(time_period=the_time_period, conditions=the_conditions, area=the_area), headers={'Content-Type': 'application/json'}) return self.trigger_parser.parse_dict(payload) def get_triggers(self): """ Retrieves all of the user's triggers that are set on the Weather Alert API. :returns: list of `pyowm.alertapi30.trigger.Trigger` objects """ status, data = self.http_client.get_json( TRIGGERS_URI, params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) return [self.trigger_parser.parse_dict(item) for item in data] def get_trigger(self, trigger_id): """ Retrieves the named trigger from the Weather Alert API. :param trigger_id: the ID of the trigger :type trigger_id: str :return: a `pyowm.alertapi30.trigger.Trigger` instance """ stringutils.assert_is_string_or_unicode(trigger_id) status, data = self.http_client.get_json( NAMED_TRIGGER_URI % trigger_id, params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) return self.trigger_parser.parse_dict(data) def update_trigger(self, trigger): """ Updates on the Alert API the trigger record having the ID of the specified Trigger object: the remote record is updated with data from the local Trigger object. :param trigger: the Trigger with updated data :type trigger: `pyowm.alertapi30.trigger.Trigger` :return: ``None`` if update is successful, an error otherwise """ assert trigger is not None stringutils.assert_is_string_or_unicode(trigger.id) the_time_period = { "start": { "expression": "after", "amount": trigger.start_after_millis }, "end": { "expression": "after", "amount": trigger.end_after_millis } } the_conditions = [ dict(name=c.weather_param, expression=c.operator, amount=c.amount) for c in trigger.conditions ] the_area = [a.as_dict() for a in trigger.area] status, _ = self.http_client.put( NAMED_TRIGGER_URI % trigger.id, params={'appid': self.API_key}, data=dict(time_period=the_time_period, conditions=the_conditions, area=the_area), headers={'Content-Type': 'application/json'}) def delete_trigger(self, trigger): """ Deletes from the Alert API the trigger record identified by the ID of the provided `pyowm.alertapi30.trigger.Trigger`, along with all related alerts :param trigger: the `pyowm.alertapi30.trigger.Trigger` object to be deleted :type trigger: `pyowm.alertapi30.trigger.Trigger` :returns: `None` if deletion is successful, an exception otherwise """ assert trigger is not None stringutils.assert_is_string_or_unicode(trigger.id) status, _ = self.http_client.delete( NAMED_TRIGGER_URI % trigger.id, params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) # ALERTS methods def get_alerts_for(self, trigger): """ Retrieves all of the alerts that were fired for the specified Trigger :param trigger: the trigger :type trigger: `pyowm.alertapi30.trigger.Trigger` :return: list of `pyowm.alertapi30.alert.Alert` objects """ assert trigger is not None stringutils.assert_is_string_or_unicode(trigger.id) status, data = self.http_client.get_json( ALERTS_URI % trigger.id, params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) return [self.alert_parser.parse_dict(item) for item in data] def get_alert(self, alert_id, trigger): """ Retrieves info about the alert record on the Alert API that has the specified ID and belongs to the specified parent Trigger object :param trigger: the parent trigger :type trigger: `pyowm.alertapi30.trigger.Trigger` :param alert_id: the ID of the alert :type alert_id `pyowm.alertapi30.alert.Alert` :return: an `pyowm.alertapi30.alert.Alert` instance """ assert trigger is not None assert alert_id is not None stringutils.assert_is_string_or_unicode(alert_id) stringutils.assert_is_string_or_unicode(trigger.id) status, data = self.http_client.get_json( NAMED_ALERT_URI % (trigger.id, alert_id), params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) return self.alert_parser.parse_dict(data) def delete_all_alerts_for(self, trigger): """ Deletes all of the alert that were fired for the specified Trigger :param trigger: the trigger whose alerts are to be cleared :type trigger: `pyowm.alertapi30.trigger.Trigger` :return: `None` if deletion is successful, an exception otherwise """ assert trigger is not None stringutils.assert_is_string_or_unicode(trigger.id) status, _ = self.http_client.delete( ALERTS_URI % trigger.id, params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) def delete_alert(self, alert): """ Deletes the specified alert from the Alert API :param alert: the alert to be deleted :type alert: pyowm.alertapi30.alert.Alert` :return: ``None`` if the deletion was successful, an error otherwise """ assert alert is not None stringutils.assert_is_string_or_unicode(alert.id) stringutils.assert_is_string_or_unicode(alert.trigger_id) status, _ = self.http_client.delete( NAMED_ALERT_URI % (alert.trigger_id, alert.id), params={'appid': self.API_key}, headers={'Content-Type': 'application/json'})
class AlertManager: """ A manager objects that provides a full interface to OWM Alert API. It implements CRUD methods on Trigger entities and read/deletion of related Alert objects :param API_key: the OWM Weather API key :type API_key: str :returns: an *AlertManager* instance :raises: *AssertionError* when no API Key is provided """ def __init__(self, API_key): assert API_key is not None, 'You must provide a valid API Key' self.API_key = API_key self.trigger_parser = TriggerParser() self.alert_parser = AlertParser() self.http_client = HttpClient() def alert_api_version(self): return ALERT_API_VERSION # TRIGGER methods def create_trigger(self, start, end, conditions, area, alert_channels=None): """ Create a new trigger on the Alert API with the given parameters :param start: time object representing the time when the trigger begins to be checked :type start: int, ``datetime.datetime`` or ISO8601-formatted string :param end: time object representing the time when the trigger ends to be checked :type end: int, ``datetime.datetime`` or ISO8601-formatted string :param conditions: the `Condition` objects representing the set of checks to be done on weather variables :type conditions: list of `pyowm.utils.alertapi30.Condition` instances :param area: the geographic are over which conditions are checked: it can be composed by multiple geoJSON types :type area: list of geoJSON types :param alert_channels: the alert channels through which alerts originating from this `Trigger` can be consumed. Defaults to OWM API polling :type alert_channels: list of `pyowm.utils.alertapi30.AlertChannel` instances :returns: a *Trigger* instance :raises: *ValueError* when start or end epochs are `None` or when end precedes start or when conditions or area are empty collections """ assert start is not None assert end is not None # prepare time period unix_start = timeformatutils.to_UNIXtime(start) unix_end = timeformatutils.to_UNIXtime(end) unix_current = timeutils.now(timeformat='unix') if unix_start >= unix_end: raise ValueError("The start timestamp must precede the end timestamp") delta_millis_start = timeutils.millis_offset_between_epochs(unix_current, unix_start) delta_millis_end = timeutils.millis_offset_between_epochs(unix_current, unix_end) the_time_period = { "start": { "expression": "after", "amount": delta_millis_start }, "end": { "expression": "after", "amount": delta_millis_end } } assert conditions is not None if len(conditions) == 0: raise ValueError('A trigger must contain at least one condition: you provided none') the_conditions = [dict(name=c.weather_param, expression=c.operator, amount=c.amount) for c in conditions] assert area is not None if len(area) == 0: raise ValueError('The area for a trigger must contain at least one geoJSON type: you provided none') the_area = [a.as_dict() for a in area] # >>> for the moment, no specific handling for alert channels status, payload = self.http_client.post( TRIGGERS_URI, params={'appid': self.API_key}, data=dict(time_period=the_time_period, conditions=the_conditions, area=the_area), headers={'Content-Type': 'application/json'}) return self.trigger_parser.parse_dict(payload) def get_triggers(self): """ Retrieves all of the user's triggers that are set on the Weather Alert API. :returns: list of `pyowm.alertapi30.trigger.Trigger` objects """ status, data = self.http_client.get_json( TRIGGERS_URI, params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) return [self.trigger_parser.parse_dict(item) for item in data] def get_trigger(self, trigger_id): """ Retrieves the named trigger from the Weather Alert API. :param trigger_id: the ID of the trigger :type trigger_id: str :return: a `pyowm.alertapi30.trigger.Trigger` instance """ assert isinstance(trigger_id, str), "Value must be a string" status, data = self.http_client.get_json( NAMED_TRIGGER_URI % trigger_id, params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) return self.trigger_parser.parse_dict(data) def update_trigger(self, trigger): """ Updates on the Alert API the trigger record having the ID of the specified Trigger object: the remote record is updated with data from the local Trigger object. :param trigger: the Trigger with updated data :type trigger: `pyowm.alertapi30.trigger.Trigger` :return: ``None`` if update is successful, an error otherwise """ assert trigger is not None assert isinstance(trigger.id, str), "Value must be a string" the_time_period = { "start": { "expression": "after", "amount": trigger.start_after_millis }, "end": { "expression": "after", "amount": trigger.end_after_millis } } the_conditions = [dict(name=c.weather_param, expression=c.operator, amount=c.amount) for c in trigger.conditions] the_area = [a.as_dict() for a in trigger.area] status, _ = self.http_client.put( NAMED_TRIGGER_URI % trigger.id, params={'appid': self.API_key}, data=dict(time_period=the_time_period, conditions=the_conditions, area=the_area), headers={'Content-Type': 'application/json'}) def delete_trigger(self, trigger): """ Deletes from the Alert API the trigger record identified by the ID of the provided `pyowm.alertapi30.trigger.Trigger`, along with all related alerts :param trigger: the `pyowm.alertapi30.trigger.Trigger` object to be deleted :type trigger: `pyowm.alertapi30.trigger.Trigger` :returns: `None` if deletion is successful, an exception otherwise """ assert trigger is not None assert isinstance(trigger.id, str), "Value must be a string" status, _ = self.http_client.delete( NAMED_TRIGGER_URI % trigger.id, params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) # ALERTS methods def get_alerts_for(self, trigger): """ Retrieves all of the alerts that were fired for the specified Trigger :param trigger: the trigger :type trigger: `pyowm.alertapi30.trigger.Trigger` :return: list of `pyowm.alertapi30.alert.Alert` objects """ assert trigger is not None assert isinstance(trigger.id, str), "Value must be a string" status, data = self.http_client.get_json( ALERTS_URI % trigger.id, params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) return [self.alert_parser.parse_dict(item) for item in data] def get_alert(self, alert_id, trigger): """ Retrieves info about the alert record on the Alert API that has the specified ID and belongs to the specified parent Trigger object :param trigger: the parent trigger :type trigger: `pyowm.alertapi30.trigger.Trigger` :param alert_id: the ID of the alert :type alert_id `pyowm.alertapi30.alert.Alert` :return: an `pyowm.alertapi30.alert.Alert` instance """ assert trigger is not None assert alert_id is not None assert isinstance(alert_id, str), "Value must be a string" assert isinstance(trigger.id, str), "Value must be a string" status, data = self.http_client.get_json( NAMED_ALERT_URI % (trigger.id, alert_id), params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) return self.alert_parser.parse_dict(data) def delete_all_alerts_for(self, trigger): """ Deletes all of the alert that were fired for the specified Trigger :param trigger: the trigger whose alerts are to be cleared :type trigger: `pyowm.alertapi30.trigger.Trigger` :return: `None` if deletion is successful, an exception otherwise """ assert trigger is not None assert isinstance(trigger.id, str), "Value must be a string" status, _ = self.http_client.delete( ALERTS_URI % trigger.id, params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) def delete_alert(self, alert): """ Deletes the specified alert from the Alert API :param alert: the alert to be deleted :type alert: pyowm.alertapi30.alert.Alert` :return: ``None`` if the deletion was successful, an error otherwise """ assert alert is not None assert isinstance(alert.id, str), "Value must be a string" assert isinstance(alert.trigger_id, str), "Value must be a string" status, _ = self.http_client.delete( NAMED_ALERT_URI % (alert.trigger_id, alert.id), params={'appid': self.API_key}, headers={'Content-Type': 'application/json'})
class AgroManager(object): """ A manager objects that provides a full interface to OWM Agro API. :param API_key: the OWM Weather API key :type API_key: str :returns: an `AgroManager` instance :raises: `AssertionError` when no API Key is provided """ def __init__(self, API_key): assert API_key is not None, 'You must provide a valid API Key' self.API_key = API_key self.http_client = HttpClient() def agro_api_version(self): return AGRO_API_VERSION # POLYGON API subset methods def create_polygon(self, geopolygon, name=None): """ Create a new polygon on the Agro API with the given parameters :param geopolygon: the geopolygon representing the new polygon :type geopolygon: `pyowm.utils.geo.Polygon` instance :param name: optional mnemonic name for the new polygon :type name: str :return: a `pyowm.agro10.polygon.Polygon` instance """ assert geopolygon is not None assert isinstance(geopolygon, GeoPolygon) data = dict() data['geo_json'] = { "type": "Feature", "properties": {}, "geometry": geopolygon.as_dict() } if name is not None: data['name'] = name status, payload = self.http_client.post( POLYGONS_URI, params={'appid': self.API_key}, data=data, headers={'Content-Type': 'application/json'}) return Polygon.from_dict(payload) def get_polygons(self): """ Retrieves all of the user's polygons registered on the Agro API. :returns: list of `pyowm.agro10.polygon.Polygon` objects """ status, data = self.http_client.get_json( POLYGONS_URI, params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) return [Polygon.from_dict(item) for item in data] def get_polygon(self, polygon_id): """ Retrieves a named polygon registered on the Agro API. :param id: the ID of the polygon :type id: str :returns: a `pyowm.agro10.polygon.Polygon` object """ status, data = self.http_client.get_json( NAMED_POLYGON_URI % str(polygon_id), params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) return Polygon.from_dict(data) def update_polygon(self, polygon): """ Updates on the Agro API the Polygon identified by the ID of the provided polygon object. Currently this only changes the mnemonic name of the remote polygon :param polygon: the `pyowm.agro10.polygon.Polygon` object to be updated :type polygon: `pyowm.agro10.polygon.Polygon` instance :returns: `None` if update is successful, an exception otherwise """ assert polygon.id is not None status, _ = self.http_client.put( NAMED_POLYGON_URI % str(polygon.id), params={'appid': self.API_key}, data=dict(name=polygon.name), headers={'Content-Type': 'application/json'}) def delete_polygon(self, polygon): """ Deletes on the Agro API the Polygon identified by the ID of the provided polygon object. :param polygon: the `pyowm.agro10.polygon.Polygon` object to be deleted :type polygon: `pyowm.agro10.polygon.Polygon` instance :returns: `None` if deletion is successful, an exception otherwise """ assert polygon.id is not None status, _ = self.http_client.delete( NAMED_POLYGON_URI % str(polygon.id), params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) # SOIL API subset methods def soil_data(self, polygon): """ Retrieves the latest soil data on the specified polygon :param polygon: the reference polygon you want soil data for :type polygon: `pyowm.agro10.polygon.Polygon` instance :returns: a `pyowm.agro10.soil.Soil` instance """ assert polygon is not None assert isinstance(polygon, Polygon) polyd = polygon.id status, data = self.http_client.get_json( SOIL_URI, params={'appid': self.API_key, 'polyid': polyd}, headers={'Content-Type': 'application/json'}) the_dict = dict() the_dict['reference_time'] = data['dt'] the_dict['surface_temp'] = data['t0'] the_dict['ten_cm_temp'] = data['t10'] the_dict['moisture'] = data['moisture'] the_dict['polygon_id'] = polyd return Soil.from_dict(the_dict) # Satellite Imagery subset methods def search_satellite_imagery(self, polygon_id, acquired_from, acquired_to, img_type=None, preset=None, min_resolution=None, max_resolution=None, acquired_by=None, min_cloud_coverage=None, max_cloud_coverage=None, min_valid_data_coverage=None, max_valid_data_coverage=None): """ Searches on the Agro API the metadata for all available satellite images that contain the specified polygon and acquired during the specified time interval; and optionally matching the specified set of filters: - image type (eg. GeoTIF) - image preset (eg. false color, NDVI, ...) - min/max acquisition resolution - acquiring satellite - min/max cloud coverage on acquired scene - min/max valid data coverage on acquired scene :param polygon_id: the ID of the reference polygon :type polygon_id: str :param acquired_from: lower edge of acquisition interval, UNIX timestamp :type acquired_from: int :param acquired_to: upper edge of acquisition interval, UNIX timestamp :type acquired_to: int :param img_type: the desired file format type of the images. Allowed values are given by `pyowm.commons.enums.ImageTypeEnum` :type img_type: `pyowm.commons.databoxes.ImageType` :param preset: the desired preset of the images. Allowed values are given by `pyowm.agroapi10.enums.PresetEnum` :type preset: str :param min_resolution: minimum resolution for images, px/meters :type min_resolution: int :param max_resolution: maximum resolution for images, px/meters :type max_resolution: int :param acquired_by: short symbol of the satellite that acquired the image (eg. "l8") :type acquired_by: str :param min_cloud_coverage: minimum cloud coverage percentage on acquired images :type min_cloud_coverage: int :param max_cloud_coverage: maximum cloud coverage percentage on acquired images :type max_cloud_coverage: int :param min_valid_data_coverage: minimum valid data coverage percentage on acquired images :type min_valid_data_coverage: int :param max_valid_data_coverage: maximum valid data coverage percentage on acquired images :type max_valid_data_coverage: int :return: a list of `pyowm.agro10.imagery.MetaImage` subtypes instances """ assert polygon_id is not None assert acquired_from is not None assert acquired_to is not None assert acquired_from <= acquired_to, 'Start timestamp of acquisition window must come before its end' if min_resolution is not None: assert min_resolution > 0, 'Minimum resolution must be positive' if max_resolution is not None: assert max_resolution > 0, 'Maximum resolution must be positive' if min_resolution is not None and max_resolution is not None: assert min_resolution <= max_resolution, 'Mininum resolution must be lower than maximum resolution' if min_cloud_coverage is not None: assert min_cloud_coverage >= 0, 'Minimum cloud coverage must be non negative' if max_cloud_coverage is not None: assert max_cloud_coverage >= 0, 'Maximum cloud coverage must be non negative' if min_cloud_coverage is not None and max_cloud_coverage is not None: assert min_cloud_coverage <= max_cloud_coverage, 'Minimum cloud coverage must be lower than maximum cloud coverage' if min_valid_data_coverage is not None: assert min_valid_data_coverage >= 0, 'Minimum valid data coverage must be non negative' if max_valid_data_coverage is not None: assert max_valid_data_coverage >= 0, 'Maximum valid data coverage must be non negative' if min_valid_data_coverage is not None and max_valid_data_coverage is not None: assert min_valid_data_coverage <= max_valid_data_coverage, 'Minimum valid data coverage must be lower than maximum valid data coverage' # prepare params params = dict(appid=self.API_key, polyid=polygon_id, start=acquired_from, end=acquired_to) if min_resolution is not None: params['resolution_min'] = min_resolution if max_resolution is not None: params['resolution_max'] = max_resolution if acquired_by is not None: params['type'] = acquired_by if min_cloud_coverage is not None: params['clouds_min'] = min_cloud_coverage if max_cloud_coverage is not None: params['clouds_max'] = max_cloud_coverage if min_valid_data_coverage is not None: params['coverage_min'] = min_valid_data_coverage if max_valid_data_coverage is not None: params['coverage_max'] = max_valid_data_coverage # call API status, data = self.http_client.get_json(SATELLITE_IMAGERY_SEARCH_URI, params=params) result_set = SatelliteImagerySearchResultSet(polygon_id, data, timeutils.now(timeformat='unix')) # further filter by img_type and/or preset (if specified) if img_type is not None and preset is not None: return result_set.with_img_type_and_preset(img_type, preset) elif img_type is not None: return result_set.with_img_type(img_type) elif preset is not None: return result_set.with_preset(preset) else: return result_set.all() def download_satellite_image(self, metaimage, x=None, y=None, zoom=None, palette=None): """ Downloads the satellite image described by the provided metadata. In case the satellite image is a tile, then tile coordinates and zoom must be provided. An optional palette ID can be provided, if supported by the downloaded preset (currently only NDVI is supported) :param metaimage: the satellite image's metadata, in the form of a `MetaImage` subtype instance :type metaimage: a `pyowm.agroapi10.imagery.MetaImage` subtype :param x: x tile coordinate (only needed in case you are downloading a tile image) :type x: int or `None` :param y: y tile coordinate (only needed in case you are downloading a tile image) :type y: int or `None` :param zoom: zoom level (only needed in case you are downloading a tile image) :type zoom: int or `None` :param palette: ID of the color palette of the downloaded images. Values are provided by `pyowm.agroapi10.enums.PaletteEnum` :type palette: str or `None` :return: a `pyowm.agroapi10.imagery.SatelliteImage` instance containing both image's metadata and data """ if palette is not None: assert isinstance(palette, str) params = dict(paletteid=palette) else: palette = PaletteEnum.GREEN params = dict() # polygon PNG if isinstance(metaimage, MetaPNGImage): prepared_url = metaimage.url status, data = self.http_client.get_png( prepared_url, params=params) img = Image(data, metaimage.image_type) return SatelliteImage(metaimage, img, downloaded_on=timeutils.now(timeformat='unix'), palette=palette) # GeoTIF elif isinstance(metaimage, MetaGeoTiffImage): prepared_url = metaimage.url status, data = self.http_client.get_geotiff( prepared_url, params=params) img = Image(data, metaimage.image_type) return SatelliteImage(metaimage, img, downloaded_on=timeutils.now(timeformat='unix'), palette=palette) # tile PNG elif isinstance(metaimage, MetaTile): assert x is not None assert y is not None assert zoom is not None prepared_url = self._fill_url(metaimage.url, x, y, zoom) status, data = self.http_client.get_png( prepared_url, params=params) img = Image(data, metaimage.image_type) tile = Tile(x, y, zoom, None, img) return SatelliteImage(metaimage, tile, downloaded_on=timeutils.now(timeformat='unix'), palette=palette) else: raise ValueError("Cannot download: unsupported MetaImage subtype") def stats_for_satellite_image(self, metaimage): """ Retrieves statistics for the satellite image described by the provided metadata. This is currently only supported 'EVI' and 'NDVI' presets :param metaimage: the satellite image's metadata, in the form of a `MetaImage` subtype instance :type metaimage: a `pyowm.agroapi10.imagery.MetaImage` subtype :return: dict """ if metaimage.preset != PresetEnum.EVI and metaimage.preset != PresetEnum.NDVI: raise ValueError("Unsupported image preset: should be EVI or NDVI") if metaimage.stats_url is None: raise ValueError("URL for image statistics is not defined") status, data = self.http_client.get_json(metaimage.stats_url, params={}) return data # Utilities def _fill_url(self, url_template, x, y, zoom): return url_template.replace('{x}', str(x)).replace('{y}', str(y)).replace('{z}', str(zoom)) def __repr__(self): return '<%s.%s>' % (__name__, self.__class__.__name__)
class StationsManager(object): """ A manager objects that provides a full interface to OWM Stations API. Mainly it implements CRUD methods on Station entities and the corresponding measured datapoints. :param API_key: the OWM web API key (defaults to ``None``) :type API_key: str :returns: a *StationsManager* instance :raises: *AssertionError* when no API Key is provided """ def __init__(self, API_key): assert API_key is not None, 'You must provide a valid API Key' self.API_key = API_key self.stations_parser = StationParser() self.aggregated_measurements_parser = AggregatedMeasurementParser() self.http_client = HttpClient() def stations_api_version(self): return STATIONS_API_VERSION # STATIONS Methods def get_stations(self): """ Retrieves all of the user's stations registered on the Stations API. :returns: list of *pyowm.stationsapi30.station.Station* objects """ status, data = self.http_client.get_json( 'http://api.openweathermap.org/data/3.0/stations', params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) return [self.stations_parser.parse_dict(item) for item in data] def get_station(self, id): """ Retrieves a named station registered on the Stations API. :param id: the ID of the station :type id: str :returns: a *pyowm.stationsapi30.station.Station* object """ status, data = self.http_client.get_json( 'http://api.openweathermap.org/data/3.0/stations/%s' % str(id), params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) return self.stations_parser.parse_dict(data) def create_station(self, external_id, name, lat, lon, alt=None): """ Create a new station on the Station API with the given parameters :param external_id: the user-given ID of the station :type external_id: str :param name: the name of the station :type name: str :param lat: latitude of the station :type lat: float :param lon: longitude of the station :type lon: float :param alt: altitude of the station :type alt: float :returns: the new *pyowm.stationsapi30.station.Station* object """ assert external_id is not None assert name is not None assert lon is not None assert lat is not None if lon < -180.0 or lon > 180.0: raise ValueError("'lon' value must be between -180 and 180") if lat < -90.0 or lat > 90.0: raise ValueError("'lat' value must be between -90 and 90") if alt is not None: if alt < 0.0: raise ValueError("'alt' value must not be negative") status, payload = self.http_client.post( 'http://api.openweathermap.org/data/3.0/stations', params={'appid': self.API_key}, data=dict(external_id=external_id, name=name, lat=lat, lon=lon, alt=alt), headers={'Content-Type': 'application/json'}) return self.stations_parser.parse_dict(payload) def update_station(self, station): """ Updates the Station API record identified by the ID of the provided *pyowm.stationsapi30.station.Station* object with all of its fields :param station: the *pyowm.stationsapi30.station.Station* object to be updated :type station: *pyowm.stationsapi30.station.Station* :returns: `None` if update is successful, an exception otherwise """ assert station.id is not None status, _ = self.http_client.put( 'http://api.openweathermap.org/data/3.0/stations/%s' % str(station.id), params={'appid': self.API_key}, data=dict(external_id=station.external_id, name=station.name, lat=station.lat, lon=station.lon, alt=station.alt), headers={'Content-Type': 'application/json'}) def delete_station(self, station): """ Deletes the Station API record identified by the ID of the provided *pyowm.stationsapi30.station.Station*, along with all its related measurements :param station: the *pyowm.stationsapi30.station.Station* object to be deleted :type station: *pyowm.stationsapi30.station.Station* :returns: `None` if deletion is successful, an exception otherwise """ assert station.id is not None status, _ = self.http_client.delete( 'http://api.openweathermap.org/data/3.0/stations/%s' % str(station.id), params={'appid': self.API_key}, headers={'Content-Type': 'application/json'}) # Measurements-related methods def send_measurement(self, measurement): """ Posts the provided Measurement object's data to the Station API. :param measurement: the *pyowm.stationsapi30.measurement.Measurement* object to be posted :type measurement: *pyowm.stationsapi30.measurement.Measurement* instance :returns: `None` if creation is successful, an exception otherwise """ assert measurement is not None assert measurement.station_id is not None status, _ = self.http_client.post( 'http://api.openweathermap.org/data/3.0/measurements', params={'appid': self.API_key}, data=[measurement.to_dict()], headers={'Content-Type': 'application/json'}) def send_measurements(self, list_of_measurements): """ Posts data about the provided list of Measurement objects to the Station API. The objects may be related to different station IDs. :param list_of_measurements: list of *pyowm.stationsapi30.measurement.Measurement* objects to be posted :type list_of_measurements: list of *pyowm.stationsapi30.measurement.Measurement* instances :returns: `None` if creation is successful, an exception otherwise """ assert list_of_measurements is not None assert all([m.station_id is not None for m in list_of_measurements]) msmts = [m.to_dict() for m in list_of_measurements] status, _ = self.http_client.post( 'http://api.openweathermap.org/data/3.0/measurements', params={'appid': self.API_key}, data=msmts, headers={'Content-Type': 'application/json'}) def get_measurements(self, station_id, aggregated_on, from_timestamp, to_timestamp, limit=100): """ Reads measurements of a specified station recorded in the specified time window and aggregated on minute, hour or day. Optionally, the number of resulting measurements can be limited. :param station_id: unique station identifier :type station_id: str :param aggregated_on: aggregation time-frame for this measurement :type aggregated_on: string between 'm','h' and 'd' :param from_timestamp: Unix timestamp corresponding to the beginning of the time window :type from_timestamp: int :param to_timestamp: Unix timestamp corresponding to the end of the time window :type to_timestamp: int :param limit: max number of items to be returned. Defaults to 100 :type limit: int :returns: list of *pyowm.stationsapi30.measurement.AggregatedMeasurement* objects """ assert station_id is not None assert aggregated_on is not None assert from_timestamp is not None assert from_timestamp > 0 assert to_timestamp is not None assert to_timestamp > 0 if to_timestamp < from_timestamp: raise ValueError("End timestamp can't be earlier than begin timestamp") assert isinstance(limit, int) assert limit >= 0 query = {'appid': self.API_key, 'station_id': station_id, 'type': aggregated_on, 'from': from_timestamp, 'to': to_timestamp, 'limit': limit} status, data = self.http_client.get_json( 'http://api.openweathermap.org/data/3.0/measurements', params=query, headers={'Content-Type': 'application/json'}) return [self.aggregated_measurements_parser.parse_dict(item) for item in data] def send_buffer(self, buffer): """ Posts to the Stations API data about the Measurement objects contained into the provided Buffer instance. :param buffer: the *pyowm.stationsapi30.buffer.Buffer* instance whose measurements are to be posted :type buffer: *pyowm.stationsapi30.buffer.Buffer* instance :returns: `None` if creation is successful, an exception otherwise """ assert buffer is not None msmts = [] for x in buffer.measurements: m = x.to_dict() item = dict() item['station_id'] = m['station_id'] item['dt'] = m['timestamp'] item['temperature'] = m['temperature'] item['wind_speed'] = m['wind_speed'] item['wind_gust'] = m['wind_gust'] item['wind_deg'] = m['wind_deg'] item['pressure'] = m['pressure'] item['humidity'] = m['humidity'] item['rain_1h'] = m['rain_1h'] item['rain_6h'] = m['rain_6h'] item['rain_24h'] = m['rain_24h'] item['snow_1h'] = m['snow_1h'] item['snow_6h'] = m['snow_6h'] item['snow_24h'] = m['snow_24h'] item['dew_point'] = m['dew_point'] item['humidex'] = m['humidex'] item['heat_index'] = m['heat_index'] item['visibility_distance'] = m['visibility_distance'] item['visibility_prefix'] = m['visibility_prefix'] item['clouds'] = [dict(distance=m['clouds_distance']), dict(condition=m['clouds_condition']), dict(cumulus=m['clouds_cumulus'])] item['weather'] = [ dict(precipitation=m['weather_precipitation']), dict(descriptor=m['weather_descriptor']), dict(intensity=m['weather_intensity']), dict(proximity=m['weather_proximity']), dict(obscuration=m['weather_obscuration']), dict(other=m['weather_other'])] msmts.append(item) status, _ = self.http_client.post( 'http://api.openweathermap.org/data/3.0/measurements', params={'appid': self.API_key}, data=msmts, headers={'Content-Type': 'application/json'})