diff --git a/floenavi/polarstern.py b/floenavi/polarstern.py index d04006e77d373e39775f071e96e748cbb6ccc0ba..332477dc43ee63f86747df83cba6fb6f1c9a197d 100644 --- a/floenavi/polarstern.py +++ b/floenavi/polarstern.py @@ -6,7 +6,8 @@ __author__ = "Stefan Hendricks" import requests -from datetime import datetime +from dateutil.parser import parse as dtparse +from datetime import datetime, timedelta from pathlib import Path from loguru import logger import numpy as np @@ -16,26 +17,28 @@ from icedrift import GeoReferenceStation class PolarsternAWIDashboardPos(object): - def __init__(self, begin_date=None, end_date=None, output="csv"): + def __init__(self, begin_date, end_date, time_pad_minutes=10): """ + Get Polarstern position and heading from the Polarstern HYDRINS1 sensor + NOTE: The URL etc can be checked using the web interface at + https://dashboard.awi.de/data-xxl/index.html + :param begin_date: (datetime.datetime) Start of the requested temporal coverage + :param end_date: (datetime.datetime)Start of the requested temporal coverage + :param time_pad_minutes: (integer) The number of minutes the period will be extended + on both side to ensure the data coverage of Polarstern position and heading + data always fully comprised the requested data period - :param begin_date: - :param end_date: - :param output: """ - # Save input arguments - self.begin_date = begin_date - self.end_date = end_date - self.output = output - + # Save input arguments and automatically extend data period + # using a short time window to ensure that there is always + # Polarstern data before the requested data period. + self.begin_date = begin_date - timedelta(minutes=time_pad_minutes) + self.end_date = end_date + timedelta(minutes=time_pad_minutes) # --- Properties --- # The format of the REST output. - # NOTE: The (default) value is specified in the `output` keyword to this method - self.format_dict = {"csv": "text/csv", - "json": "application/json", - "tab": "text/tab-separated-values"} + self.requested_format = "text/csv", # Output format for the time stamp # NOTE: Most sensors do not report faster than 1 second, therefore there is no need @@ -45,36 +48,70 @@ class PolarsternAWIDashboardPos(object): # The main URL to the dashboard REST interface self.base_url = "https://dashboard.awi.de/data-xxl/rest/data" - # A list of sensors + # Parameter to be pulled from the Polarstern Inertial Navigation Unit + # 1. Latitude + # 2. Longitude + # 3. True Heading self.sensors = ["vessel:polarstern:hydrins_1:latitude$NRT", "sensors=vessel:polarstern:hydrins_1:longitude$NRT", "sensors=vessel:polarstern:hydrins_1:heading$NRT"] - # Temporal resolution: + # Temporal resolution (full) self.aggregate_value = "second" - def save(self, filename=None): - """ + # Load the data + self.response = None + self.download() - :param filename: + self._variables = {} + self.decode_response() + + def download(self): + """ + Pulls the data for the requested period from dashboard.awi.de :return: """ # Construct the URL url = self._construct_url() - logger.debug("url=%s" % url) + logger.info("data request url: {}".format(url)) # Download the websites response # TODO: Error management r = requests.get(url) # Post-process the content - ascii_content = "\n".join(r.text.splitlines()) - if self.output == "csv": - ascii_content = ascii_content.replace(";", ",") + self.response = "\n".join(r.text.splitlines()) + self.response = self.response.replace(";", ",") - # Write output file - open(filename, 'w').write(ascii_content) + def decode_response(self): + """ + Parses the response from the data service into arrays of time, longitude, latitude + and true heading. + :return: + """ + + # Init the variables + # NOTE: the first line is the header line and has to be ignored. + lines = self.response.split("\n")[1:] + n_lines = len(lines) + self._variables["time"] = np.full(n_lines, object) + for parameter_name in ["longitude", "latitude", "heading"]: + self._variables[parameter_name] = np.full(n_lines, np.nan) + + for i, line in enumerate(lines): + string_array = line.split(",") + self._variables["time"][i] = dtparse(string_array[0]) + self._variables["latitude"][i] = np.float(string_array[1]) + self._variables["longitude"][i] = np.float(string_array[2]) + self._variables["heading"][i] = np.float(string_array[3]) + + def save(self, filename): + """ + :param filename: + :return: + """ + raise NotImplementedError() def _construct_url(self): """ @@ -89,18 +126,18 @@ class PolarsternAWIDashboardPos(object): url += self.sensor_def # add output format - url += "&format=%s" % self.format_dict[self.output] + url += "&format=%s" % self.requested_format # add temporal resolution url += "&aggregate={}".format(self.aggregate_value) # add begin date (optional) if self.begin_date is not None: - url += "&beginDate=%s" % self.begin_date.strftime(self.datetime_format) + url += "&beginDate={}".format(self.begin_date.strftime(self.datetime_format)) # add end date (optional) if self.end_date is not None: - url += "&endDate=%s" % self.end_date.strftime(self.datetime_format) + url += "&endDate={}".format(self.end_date.strftime(self.datetime_format)) return url @@ -113,6 +150,26 @@ class PolarsternAWIDashboardPos(object): sensor_def = "sensors="+"&".join(self.sensors) return sensor_def + @property + def reference_station(self): + """ + Return a icedrift.GeoReferenceStation instance + :return: + """ + refstat = GeoReferenceStation(self.time, self.longitude, self.latitude, self.heading) + return refstat + + def __getattr__(self, attr): + """ + Modify the attribute getter to provide a shortcut to the data content + :param attr: + :return: + """ + if attr in list(self._variables.keys()): + return self._variables[attr] + else: + raise AttributeError() + class PolarsternPOSDashboardData(object): """ @@ -180,5 +237,3 @@ class PolarsternPOSDashboardData(object): data = GeoReferenceStation(content["time"], content["longitude"], content["latitude"], content["heading"]) return data - -