From d7031cf11134974031bda1b0f64ce9b870e092de Mon Sep 17 00:00:00 2001 From: Martin Paces Date: Sun, 12 May 2019 09:40:08 +0200 Subject: [PATCH 1/3] Token based authentication + re-worked configuration handling + minor cleanup --- viresclient/__init__.py | 3 +- viresclient/_client.py | 184 +++++++++++++++++----------------- viresclient/_client_aeolus.py | 46 +++++---- viresclient/_client_swarm.py | 42 ++++---- viresclient/_config.py | 134 +++++++++++++++++++++++++ viresclient/_wps/http_util.py | 17 +++- 6 files changed, 292 insertions(+), 134 deletions(-) create mode 100644 viresclient/_config.py diff --git a/viresclient/__init__.py b/viresclient/__init__.py index 84ca8fd..aace53c 100644 --- a/viresclient/__init__.py +++ b/viresclient/__init__.py @@ -27,9 +27,10 @@ # THE SOFTWARE. #------------------------------------------------------------------------------- +from ._client import ClientConfig from ._client_swarm import SwarmRequest from ._client_aeolus import AeolusRequest from ._data_handling import ReturnedData from ._data_handling import ReturnedDataFile -__version__ = "0.2.6" +__version__ = "0.3.0" diff --git a/viresclient/_client.py b/viresclient/_client.py index cbb83c7..2bb2d53 100644 --- a/viresclient/_client.py +++ b/viresclient/_client.py @@ -27,22 +27,23 @@ # THE SOFTWARE. #------------------------------------------------------------------------------- -from tqdm import tqdm -from datetime import datetime, timedelta -from math import ceil -import configparser import os +from datetime import timedelta +from logging import getLogger, DEBUG, INFO, WARNING, ERROR, CRITICAL +from tqdm import tqdm from ._wps.wps_vires import ViresWPS10Service from ._wps.time_util import parse_duration, parse_datetime -from ._wps.http_util import encode_basic_auth -from logging import getLogger, DEBUG, INFO, WARNING, ERROR, CRITICAL +from ._wps.http_util import ( + encode_basic_auth, encode_token_auth, encode_no_auth, +) from ._wps.log_util import set_stream_handler # from jinja2 import Environment, FileSystemLoader from ._wps.environment import JINJA2_ENVIRONMENT from ._wps.wps import WPSError from ._data_handling import ReturnedData +from ._config import ClientConfig # Logging levels LEVELS = { @@ -71,14 +72,16 @@ def get_log_level(level): + """ Translate log-level string to an actual log level number accepted by + the python logging. """ if isinstance(level, str): - return LEVELS[level] - else: - return level + level = LEVELS[level] + return level class WPSInputs(object): - """Holds the set of inputs to be passed to the request template + """ Base WPS inputs class holding the set of inputs to be passed + to the request template. Properties of this class are the set of valid inputs to a WPS request. See SwarmWPSInputs and AeolusWPSInputs. @@ -86,22 +89,17 @@ class WPSInputs(object): dictionary to be passed as kwargs to as_xml() which fills the xml template. """ - - def __init__(self): - self.names = () + NAMES = [] # to be overridden in the sub-classes def __str__(self): - if len(self.names) == 0: - return None - else: - return "Request details:\n{}".format('\n'.join( - ['{}: {}'.format(key, value) for (key, value) in - self.as_dict.items() - ])) + return "Request details:\n{}".format('\n'.join([ + '{}: {}'.format(key, value) + for (key, value) in self.as_dict.items() + ])) @property def as_dict(self): - return {key: self.__dict__['_{}'.format(key)] for key in self.names} + return {key: self.__dict__['_{}'.format(key)] for key in self.NAMES} def as_xml(self, templatefile): """Renders a WPS request template (xml) that can later be executed @@ -205,71 +203,28 @@ def update(self, percentCompleted): self.refresh_tqdm() -def load_config(): - config = configparser.ConfigParser() - config.read(CONFIG_FILE_PATH) - return config - - -def save_config(url, username, password): - config = load_config() - # Replace the username and password for an already stored url - if url in config: - config.set(url, "username", username) - config.set(url, "password", password) - # Store an extra config section for a new url - else: - config[url] = {"username": username, - "password": password} - with open(CONFIG_FILE_PATH, 'w') as configfile: - config.write(configfile) - - class ClientRequest(object): - """Handles the requests to and downloads from the server. - - See SwarmClientRequest and AeolusClientRequest + """Base class handling the requests to and downloads from the server. """ - def __init__(self, url=None, username=None, password=None, - logging_level="NO_LOGGING", server_type="Swarm" - ): - - url = "" if url is None else url - username = "" if username is None else username - password = "" if password is None else password - - for i in [url, username, password]: - if not isinstance(i, str): - raise TypeError( - "url, username, and password must all be strings" - ) - - # Try to load a previously stored username and password that match the - # url, if none have been provided - if (url != "") & (username == "") & (password == ""): - config = load_config() - if url in config: - username = config[url].get("username", "") - password = config[url].get("password", "") - # If they have been provided, update the config file with them - elif "" not in [url, username, password]: - save_config(url, username, password) + def __init__(self, url=None, username=None, password=None, token=None, + config=None, logging_level="NO_LOGGING", server_type=None): self._server_type = server_type + self._available = {} + self._collection = None self._request_inputs = None - self._templatefiles = None - self._supported_filetypes = None + self._request = None + self._templatefiles = {} + self._supported_filetypes = () logging_level = get_log_level(logging_level) self._logger = getLogger() set_stream_handler(self._logger, logging_level) # service proxy with basic HTTP authentication - self._wps_service = ViresWPS10Service( - url, - encode_basic_auth(username, password), - logger=self._logger + self._wps_service = self._create_service_proxy_( + config, url, username, password, token ) # self.files_dir = files_dir @@ -279,6 +234,52 @@ def __init__(self, url=None, username=None, password=None, # "Set directory for saving files locally: ".format(self.files_dir) # ) + def _create_service_proxy_(self, config, url, username, password, token): + + if not isinstance(config, ClientConfig): + config = ClientConfig(config) + + url = self._check_input(url, "url") or config.default_url + username = self._check_input(username, "username") + password = self._check_input(password, "password") + token = self._check_input(token, "token") + + if not url: + raise ValueError( + "The URL must be provided when no default URL is " + "configured." + ) + + if token: + credentials = {"token": token} + encode_headers = encode_token_auth + elif username and password: + credentials = {"username": username, "password": password} + encode_headers = encode_basic_auth + else: + credentials = config.get_site_config(url) + if 'token' in credentials: + encode_headers = encode_token_auth + elif 'username' in credentials and 'password' in credentials: + encode_headers = encode_basic_auth + else: + encode_headers = encode_no_auth + + # service proxy with authentication + return ViresWPS10Service( + url, encode_headers(**credentials), logger=self._logger + ) + + @staticmethod + def _check_input(value, label): + if not value: + return None + if not isinstance(value, str): + raise TypeError("%s must be strings" % label) + return value + + + def __str__(self): if self._request_inputs is None: return "No request set" @@ -333,9 +334,7 @@ def write_response_without_reporting(file_obj): return write_response_without_reporting @staticmethod - def _chunkify_request( - start_time, end_time, sampling_step, nrecords_limit - ): + def _chunkify_request(start_time, end_time, sampling_step, nrecords_limit): """Split the start and end times into several as necessary, as specified by the NRECORDS_LIMIT @@ -368,7 +367,7 @@ def _chunkify_request( next_time = last_time + chunk_duration request_intervals.append((last_time, min(next_time, end_time))) if next_time >= end_time: - break; + break last_time = next_time return request_intervals @@ -406,7 +405,8 @@ def _get(self, request=None, asynchronous=None, response_handler=None, ) except WPSError: raise RuntimeError( - "Server error - perhaps you are requesting a period outside of product availability?" + "Server error - perhaps you are requesting a period outside of " + "product availability?" ) def get_between(self, start_time=None, end_time=None, @@ -437,7 +437,7 @@ def get_between(self, start_time=None, end_time=None, "date/time strings" ) - if (end_time < start_time): + if end_time < start_time: raise ValueError("Invalid time selection! end_time < start_time") if (end_time - start_time) > MAX_TIME_SELECTION: @@ -450,9 +450,9 @@ def get_between(self, start_time=None, end_time=None, retdatagroup = ReturnedData(filetype=filetype) if retdatagroup.filetype not in self._supported_filetypes: - raise TypeError("filetype: {} not supported by server" - .format(filetype) - ) + raise TypeError( + "filetype: {} not supported by server".format(filetype) + ) self._request_inputs.response_type = RESPONSE_TYPES[retdatagroup.filetype] if asynchronous: @@ -511,14 +511,14 @@ def get_between(self, start_time=None, end_time=None, # Make the request, as either asynchronous or synchronous # The response handler streams the data to the ReturnedData object response_handler = self._response_handler( - retdata.file, - show_progress=show_progress - ) - self._get(request=self._request, - asynchronous=asynchronous, - response_handler=response_handler, - message=message, - show_progress=show_progress - ) + retdata.file, show_progress=show_progress + ) + self._get( + request=self._request, + asynchronous=asynchronous, + response_handler=response_handler, + message=message, + show_progress=show_progress + ) return retdatagroup diff --git a/viresclient/_client_aeolus.py b/viresclient/_client_aeolus.py index 841e93f..4aef384 100644 --- a/viresclient/_client_aeolus.py +++ b/viresclient/_client_aeolus.py @@ -10,6 +10,23 @@ class AeolusWPSInputs(WPSInputs): + NAMES = [ + 'processId', + 'collection_ids', + 'begin_time', + 'end_time', + 'response_type', + 'fields', + 'filters', + 'aux_type', + 'observation_fields', + 'mie_profile_fields', + 'rayleigh_profile_fields', + 'mie_wind_fields', + 'rayleigh_wind_fields', + 'bbox', + ] + def __init__(self, processId=None, collection_ids=None, @@ -43,22 +60,6 @@ def __init__(self, self.rayleigh_wind_fields = rayleigh_wind_fields self.bbox = bbox - self.names = ('processId', - 'collection_ids', - 'begin_time', - 'end_time', - 'response_type', - 'fields', - 'filters', - 'aux_type', - 'observation_fields', - 'mie_profile_fields', - 'rayleigh_profile_fields', - 'mie_wind_fields', - 'rayleigh_wind_fields', - 'bbox' - ) - @property def as_dict(self): # Add these as properties later: @@ -164,15 +165,18 @@ class AeolusRequest(ClientRequest): url (str): username (str): password (str): + token (str): + config (str or ClientConfig): logging_level (str): """ - def __init__(self, url=None, username=None, password=None, - logging_level="NO_LOGGING"): - super().__init__(url, username, password, logging_level, - server_type="Aeolus" - ) + def __init__(self, url=None, username=None, password=None, token=None, + config=None, logging_level="NO_LOGGING"): + super().__init__( + url, username, password, token, config, logging_level, + server_type="Aeolus" + ) # self._available = self._set_available_data() self._request_inputs = AeolusWPSInputs() self._request_inputs.processId = 'aeolus:level1B:AUX' diff --git a/viresclient/_client_swarm.py b/viresclient/_client_swarm.py index c69e516..8533347 100644 --- a/viresclient/_client_swarm.py +++ b/viresclient/_client_swarm.py @@ -101,6 +101,18 @@ class SwarmWPSInputs(WPSInputs): """Holds the set of inputs to be passed to the request template for Swarm """ + NAMES = [ + 'collection_ids', + 'model_expression', + 'begin_time', + 'end_time', + 'variables', + 'filters', + 'sampling_step', + 'response_type', + 'custom_shc', + ] + def __init__(self, collection_ids=None, model_expression=None, @@ -125,17 +137,6 @@ def __init__(self, self.sampling_step = None if sampling_step is None else sampling_step self.custom_shc = None if custom_shc is None else custom_shc - self.names = ('collection_ids', - 'model_expression', - 'begin_time', - 'end_time', - 'variables', - 'filters', - 'sampling_step', - 'response_type', - 'custom_shc' - ) - @property def collection_ids(self): return self._collection_ids @@ -263,15 +264,19 @@ class SwarmRequest(ClientRequest): url (str): username (str): password (str): + token (str): + config (str or ClientConfig): logging_level (str): """ - def __init__(self, url=None, username=None, password=None, - logging_level="NO_LOGGING"): - super().__init__(url, username, password, logging_level, - server_type="Swarm" - ) + def __init__(self, url=None, username=None, password=None, token=None, + config=None, logging_level="NO_LOGGING"): + super().__init__( + url, username, password, token, config, logging_level, + server_type="Swarm" + ) + self._available = self._set_available_data() self._request_inputs = SwarmWPSInputs() self._templatefiles = TEMPLATE_FILES @@ -697,9 +702,8 @@ def get_orbit_number(self, spacecraft, input_time): self._wps_service.retrieve(request, handler=response_handler) return retdata.as_dataframe()["OrbitNumber"][0] - def get_model_info( - self, models=None, custom_model=None, original_response=False - ): + def get_model_info(self, models=None, custom_model=None, + original_response=False): """Get model info from server. Handles the same models input as .set_products(), and returns a dict diff --git a/viresclient/_config.py b/viresclient/_config.py new file mode 100644 index 0000000..83d9d03 --- /dev/null +++ b/viresclient/_config.py @@ -0,0 +1,134 @@ +#------------------------------------------------------------------------------- +# +# Configuration file handling. +# +# Authors: Ashley Smith +# Martin Paces +# +#------------------------------------------------------------------------------- +# Copyright (C) 2018 EOX IT Services GmbH +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies of this Software or works derived from this Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +#------------------------------------------------------------------------------- + +from io import StringIO +from os import name as os_name, chmod +from os.path import expanduser, join +from configparser import ConfigParser + +DEFAULT_CONFIG_PATH = join(expanduser("~"), ".viresclient.ini") + + +class ClientConfig(): + """ Client configuration. + + Example usage: + + >> cc = ClientConfig() # use default configuration file + >> cc = ClientConfig("./viresconf.ini") # use custom configuration file + + >> print(cc.path) # print path + >> print(cc) # print whole configuration + + >> cc.default_url = "https://foo.bar/ows" # set default server + + # access credentials configuration ... + >> cc.set_site_config("https://foo.bar/ows", username="...", password="...") + >> cc.set_site_config("https://foo2.bar/ows", token="...") + + >> cc.save() # save configuration + + """ + + def __init__(self, path=None): + self._path = path or DEFAULT_CONFIG_PATH + self._config = ConfigParser() + self._config.read(self._path) + + @property + def path(self): + """ Get path of the configuration file. """ + return self._path + + @property + def default_url(self): + """ Get default URL or None if not set. """ + return self._get_section('default').get('url') + + @default_url.setter + def default_url(self, value): + """ Set default URL. """ + self._update_section('default', url=value) + + @default_url.deleter + def default_url(self): + """ Unset the default URL. """ + self._update_section('default', url=None) + + def set_site_config(self, url, **options): + """ Set configuration for the given URL. """ + self._set_section(url, **options) + + def get_site_config(self, url): + """ Get configuration for the given URL. """ + return dict(self._get_section(url)) + + def _update_section(self, section, **options): + """ Update configuration file section. """ + all_options = dict(self._get_section(section)) + all_options.update(options) + self._set_section(section, **all_options) + + def _get_section(self, section): + try: + return self._config[section] + except KeyError: + return {} + + def _set_section(self, section, **options): + """ Set configuration file section. """ + options = { + key: value + for key, value in options.items() if value is not None + } + if options: + self._config[section] = options + else: + self._delete_section(section) + + def _delete_section(self, section): + """ Delete configuration file section. """ + try: + del self._config[section] + except KeyError: + pass + + def save(self): + """ Save the configuration file. """ + with open(self._path, 'w') as file_: + self._config.write(file_) + # make file private + if os_name == 'posix': + chmod(file_.fileno(), 0o0600) + + def __str__(self): + """ Dump configuration to a string. """ + fobj = StringIO() + self._config.write(fobj) + return fobj.getvalue() diff --git a/viresclient/_wps/http_util.py b/viresclient/_wps/http_util.py index 97a9653..6b22fc1 100644 --- a/viresclient/_wps/http_util.py +++ b/viresclient/_wps/http_util.py @@ -28,7 +28,13 @@ from base64 import standard_b64encode -def encode_basic_auth(username, password): + +def encode_no_auth(**kwargs): + """ Dummy encoder. """ + return {} + + +def encode_basic_auth(username, password, **kwargs): """ Encode username and password as the basic HTTP access authentication header. """ @@ -37,3 +43,12 @@ def encode_basic_auth(username, password): ("%s:%s" % (username, password)).encode("UTF-8") ) } + + +def encode_token_auth(token, **kwargs): + """ Encode token as the bearer authentication header. + """ + # NOTE: Only ASCII characters are allowed in HTTP headers. + return { + b"Authorization": b"Bearer " + token.encode("ascii") + } From 65c937012ba2b4f184c94514b8af8c72a938668e Mon Sep 17 00:00:00 2001 From: Ashley Smith Date: Mon, 13 May 2019 20:46:55 +0100 Subject: [PATCH 2/3] Fix test not completing. --- test/test_ClientRequest.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_ClientRequest.py b/test/test_ClientRequest.py index 4e3005f..3576675 100644 --- a/test/test_ClientRequest.py +++ b/test/test_ClientRequest.py @@ -8,15 +8,15 @@ def test_ClientRequest(): """Test that a ClientRequest gets set up correctly. """ - request = ClientRequest('', '', '') + request = ClientRequest('dummy_url') assert isinstance(request._wps_service, viresclient._wps.wps_vires.ViresWPS10Service ) - request = SwarmRequest('', '', '') + request = SwarmRequest('dummy_url') assert isinstance(request._wps_service, viresclient._wps.wps_vires.ViresWPS10Service ) - request = AeolusRequest('', '', '') + request = AeolusRequest('dummy_url') assert isinstance(request._wps_service, viresclient._wps.wps_vires.ViresWPS10Service ) From 05da69bf9a38d96c8eef60d6472016cb0092cb91 Mon Sep 17 00:00:00 2001 From: Ashley Smith Date: Mon, 13 May 2019 20:47:15 +0100 Subject: [PATCH 3/3] Update docs. --- docs/about.rst | 22 -- docs/index.rst | 2 +- docs/installation.rst | 146 ++++++++++++ docs/notebooks/simple_example.ipynb | 334 ++++++++++++++-------------- docs/readme.rst | 110 ++------- docs/release_notes.rst | 6 + 6 files changed, 334 insertions(+), 286 deletions(-) delete mode 100644 docs/about.rst create mode 100644 docs/installation.rst diff --git a/docs/about.rst b/docs/about.rst deleted file mode 100644 index 40510ce..0000000 --- a/docs/about.rst +++ /dev/null @@ -1,22 +0,0 @@ -About VirES and viresclient -=========================== - -Some links where you can read more about VirES: - - - `VirES web service`_ - - `Swarm DQW slides about viresclient`_ - - `EOX blog posts`_ - - `Swarm mission`_ - -If you have encountered a bug or have a feature request, the preferred option is to raise an issue on GitHub (https://github.com/ESA-VirES/VirES-Python-Client/issues). Otherwise please send an email to info@vires.services - -How to acknowledge VirES ------------------------- - -You can reference ``viresclient`` directly using the DOI of our zenodo_ record. VirES uses data from a number of different sources so please also acknowledge these appropriately. - -.. _`VirES web service`: https://vires.services/ -.. _`Swarm DQW slides about viresclient`: https://github.com/smithara/viresclient_examples/blob/master/viresclient_SwarmDQW8.pdf -.. _`EOX blog posts`: https://eox.at/category/vires/ -.. _zenodo: https://doi.org/10.5281/zenodo.2554163 -.. _`Swarm mission`: https://earth.esa.int/web/guest/missions/esa-operational-eo-missions/swarm diff --git a/docs/index.rst b/docs/index.rst index 0021072..92ff02e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,7 +6,7 @@ Welcome to VirES-Python-Client's documentation! :caption: Overview readme - about + installation available_parameters release_notes diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000..8e15563 --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,146 @@ +Installation and first usage +============================ + +Installation +------------ + +Linux/Unix and Python ≥ 3.5 is required for full support (since cdflib requires ≥ 3.5). + +Python 3.4 can also be used, but conversion from CDF to pandas/xarray is not supported - you can still download and save CDF files - :meth:`viresclient.ReturnedData.to_file`, or download as CSV files and convert to pandas - :meth:`viresclient.ReturnedData.as_dataframe`. + +It can currently be installed with:: + + pip install viresclient + +Dependencies:: + + Jinja2 ≥ 2.10.0 + pandas ≥ 0.18.0 + cdflib = 0.3.9 + tables ≥ 3.4.4 + tqdm ≥ 4.23.0 + xarray ≥ 0.10.0 + +(pip will fetch these automatically - if you are using conda, it may be better to install these first using conda instead) + +There is an unresolved bug with Windows support - see here_. + +.. _here: https://github.com/ESA-VirES/VirES-Python-Client/issues/1 + +First usage / Configuration +--------------------------- + +Access to the service is through the same user account as on the web interface (https://vires.services/) and is enabled through a token. To get a token, log in to the website and click on your name on the top right to access the settings. From here, click on "Manage access tokens" and follow the instructions to create a new token. + +Use the following code to store the token in the ``viresclient`` configuration (it will be saved as a file at ``~/.viresclient.ini``): + +.. code-block:: python + + from viresclient import ClientConfig + + cc = ClientConfig() + cc.set_site_config("https://vires.services/ows", token="r-8-mlkP_RBx4mDv0di5Bzt3UZ52NGg-") + cc.default_url = "https://vires.services/ows" + cc.save() + +Import the package and create a ``SwarmRequest`` object, which will use the stored configuration above: + +.. code-block:: python + + from viresclient import SwarmRequest + + request = SwarmRequest() + +.. note:: For DISC users / developers: + + The user account for the DISC server is separate. A token can be generated in the same way and stored in the configuration alongside the token for other site: + + .. code-block:: python + + from viresclient import ClientConfig + + cc = ClientConfig() + cc.set_site_config("https://vires.services/ows", token="r-8-mlkP_RBx4mDv0di5Bzt3UZ52NGg-") + cc.set_site_config("https://staging.viresdisc.vires.services/ows", token="VymMHhWjZ-9nSVs-FuPC27ca8C6cOyij") + cc.default_url = "https://vires.services/ows" + cc.save() + + Using ``SwarmRequest()`` will use the default url set above. Alternatively access a specific server with a specific token, or use the stored token: + + .. code-block:: python + + from viresclient import SwarmRequest + + request = SwarmRequest(url="https://vires.services/ows", token="r-8-mlkP_RBx4mDv0di5Bzt3UZ52NGg-") + request = SwarmRequest(url="https://staging.viresdisc.vires.services/ows", token="VymMHhWjZ-9nSVs-FuPC27ca8C6cOyij") + + request = SwarmRequest(url="https://vires.services/ows") + request = SwarmRequest(url="https://staging.viresdisc.vires.services/ows") + +Example use +----------- + +Choose which collection to access (see :doc:`available_parameters` for more options): + +.. code-block:: python + + from viresclient import SwarmRequest + + request = SwarmRequest() + request.set_collection("SW_OPER_MAGA_LR_1B") + +Next, use ``.set_products()`` to choose a combination of variables to retrieve, specified by keywords. + +- ``measurements`` are measured by the satellite and members of the specified ``collection`` +- ``models`` are evaluated on the server at the positions of the satellite +- ``auxiliaries`` are additional parameters not unique to the ``collection`` +- if ``residuals`` is set to ``True`` then only data-model residuals are returned +- optionally use ``sampling_step`` to specify a resampling of the original time series (an `ISO-8601 duration `_). + +.. code-block:: python + + request.set_products(measurements=["F","B_NEC"], + models=["MCO_SHA_2C", "MMA_SHA_2C-Primary", "MMA_SHA_2C-Secondary"], + auxiliaries=["QDLat", "QDLon", "MLT", "OrbitNumber", "SunZenithAngle"], + residuals=False, + sampling_step="PT10S") + +Set a parameter range filter to apply. You can add multiple filters in sequence + +.. code-block:: python + + request.set_range_filter(parameter="Latitude", + minimum=0, + maximum=90) + + request.set_range_filter("Longitude", 0, 90) + +Specify the time range from which to retrieve data, make the request to the server (specifying the output file format, currently either csv or cdf): + +.. code-block:: python + + data = request.get_between(start_time=dt.datetime(2016,1,1), + end_time=dt.datetime(2016,1,2), + filetype="cdf", + asynchronous=True) + +Transfer your data to a pandas.DataFrame_, or a xarray.Dataset_, or just save it as is: + +.. _pandas.DataFrame: https://pandas.pydata.org/pandas-docs/stable/dsintro.html#dataframe + +.. _xarray.Dataset: http://xarray.pydata.org/en/stable/data-structures.html#dataset + +.. code-block:: python + + df = data.as_dataframe() + ds = data.as_xarray() + data.to_file('outfile.cdf', overwrite=False) + +The returned data has columns for: + - ``Spacecraft, Timestamp, Latitude, Longitude, Radius`` + - those specified by ``measurements`` and ``auxiliaries`` +... and model values and residuals, named as: + - ``F_`` -- scalar field + - ``B_NEC_`` -- vector field + - ``F_res_`` -- scalar field residual (``F - F_``) + - ``B_NEC_res_`` -- vector field residual (``B_NEC - B_NEC_``) diff --git a/docs/notebooks/simple_example.ipynb b/docs/notebooks/simple_example.ipynb index a30da16..c208178 100644 --- a/docs/notebooks/simple_example.ipynb +++ b/docs/notebooks/simple_example.ipynb @@ -7,35 +7,6 @@ "# General example" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Connect to the server\n", - "\n", - "You need a username and password to connect to the server. You can enter these in the code directly:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from viresclient import SwarmRequest\n", - "\n", - "request = SwarmRequest(url=\"https://staging.viresdisc.vires.services/openows\",\n", - " username=\"your username\",\n", - " password=\"your password\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When these credentials have already been entered, you don't need to supply them again. Your username and password have now been stored (unencrypted!) in a configuration file in your home directory: ``~/.viresclient.ini``. You can update them simply by calling ``SwarmRequest()`` again with new inputs." - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -49,15 +20,15 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "[1/1] Processing: 100%|██████████| [ Elapsed: 00:02, Remaining: 00:00 ]\n", - " Downloading: 100%|██████████| [ Elapsed: 00:00, Remaining: 00:00 ] (0.766MB)\n" + "[1/1] Processing: 100%|██████████| [ Elapsed: 00:01, Remaining: 00:00 ]\n", + " Downloading: 100%|██████████| [ Elapsed: 00:01, Remaining: 00:00 ] (0.766MB)\n" ] } ], @@ -65,7 +36,7 @@ "from viresclient import SwarmRequest\n", "import datetime as dt\n", "\n", - "request = SwarmRequest(url=\"https://staging.viresdisc.vires.services/openows\")\n", + "request = SwarmRequest()\n", "\n", "request.set_collection(\"SW_OPER_MAGA_LR_1B\")\n", "\n", @@ -94,7 +65,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -127,24 +98,37 @@ "data": { "text/html": [ "
\n", + "\n", "\n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", " \n", " \n", " \n", @@ -167,149 +151,157 @@ " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", " \n", " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", " \n", " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", " \n", " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", " \n", " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", " \n", " \n", "
B_NECB_NEC_MCO_SHA_2CB_NEC_MMA_SHA_2C-PrimaryB_NEC_MMA_SHA_2C-SecondarySpacecraftLatitudeLongitudeRadiusFF_MCO_SHA_2CF_MMA_SHA_2C-PrimaryF_MMA_SHA_2C-SecondaryLatitudeLongitudeB_NECB_NEC_MCO_SHA_2CB_NEC_MMA_SHA_2C-PrimaryB_NEC_MMA_SHA_2C-SecondaryQDLatQDLonRadiusSpacecraft
Timestamp
2016-01-01 00:28:00[23432.0698, 2929.1195000000002, 7001.0706][23605.90473830581, 2931.6376573093767, 7003.2...[-126.91532798907662, -5.6198964252082355, 17....[-33.09016927969169, -1.7093996086641592, -6.8...24630.629824796.748750128.25001533.843027A0.197610-102.6818416828390.6024630.631224795.742996127.13428733.517576[23432.3208, 2928.6274000000003, 7000.6948][23604.765693069177, 2930.2688321265728, 7004....[-125.78890509740265, -5.302191425378982, 17.6...[-32.5031562033331, -1.386212193226803, -8.065...8.425159-30.8127506828390.61A
2016-01-01 00:28:10[23450.916100000002, 2900.0733, 7554.7711][23624.47275153368, 2902.82041629198, 7556.563...[-126.72131580143507, -5.5793404901198125, 19....[-33.089430547649556, -1.6811466971580775, -7....24808.106824972.859780128.30509034.0259300.838157A0.838158-102.6939466828288.8724808.107324971.853392127.19731533.718966[23451.1801, 2899.534, 7554.4035][23623.327008268625, 2901.392633804825, 7557.3...[-125.60166979171441, -5.252627485291866, 19.3...[-32.494418615582255, -1.3566476193699097, -8....9.068963-30.9022566828288.87A
2016-01-01 00:28:20[23465.5579, 2872.2875000000004, 8110.08480000...[23638.81245985172, 2874.59744914569, 8111.724...[-126.50108249657657, -5.538713843992417, 21.0...[-33.08040682359359, -1.653116949429096, -8.60...24993.369025156.646166128.35750734.221683A1.478724-102.7060416828186.3224993.370925155.643730127.25771033.932493[23465.812100000003, 2871.8025000000002, 8109....[23637.6657342565, 2873.1207904780367, 8112.48...[-125.38835978948227, -5.203014637815361, 21.1...[-32.47777846302375, -1.3273711242288924, -9.7...9.713511-30.9907846828186.32A
2016-01-01 00:28:30[23476.096100000002, 2844.6195000000002, 8666....[23648.85230986529, 2846.960888233475, 8668.59...[-126.2546612277731, -5.4980200665725025, 22.7...[-33.06303949938177, -1.6253091491888805, -9.4...25186.222225347.936846128.40726034.430033A2.119308-102.7181186828082.9725186.220725346.943757127.31546234.157878[23476.3596, 2844.1066, 8666.6296][23647.710917671076, 2845.447078036548, 8669.2...[-125.14900320901313, -5.153357531132542, 22.8...[-32.45318157347463, -1.2983807863969918, -10....10.358768-31.0783636828082.97A
2016-01-01 00:28:40[23482.2611, 2817.4788000000003, 9225.42960000...[23654.524148541142, 2819.9042242487117, 9227....[-125.98209353284206, -5.457262630766026, 24.4...[-33.03727353768437, -1.5977218864183431, -10....25386.475425546.550658128.45434334.650704A2.759911-102.7301696827978.8325386.475525545.573072127.37056634.394821[23482.5073, 2817.0387, 9225.1501][23653.3949273992, 2818.3663458182045, 9227.67...[-124.88363654458195, -5.103660714780139, 24.5...[-32.42057710492788, -1.2696744882122784, -11....11.004701-31.1650126827978.83A
\n", "
" ], "text/plain": [ + " Spacecraft Latitude Longitude Radius F \\\n", + "Timestamp \n", + "2016-01-01 00:28:00 A 0.197610 -102.681841 6828390.60 24630.6312 \n", + "2016-01-01 00:28:10 A 0.838158 -102.693946 6828288.87 24808.1073 \n", + "2016-01-01 00:28:20 A 1.478724 -102.706041 6828186.32 24993.3709 \n", + "2016-01-01 00:28:30 A 2.119308 -102.718118 6828082.97 25186.2207 \n", + "2016-01-01 00:28:40 A 2.759911 -102.730169 6827978.83 25386.4755 \n", + "\n", + " F_MCO_SHA_2C F_MMA_SHA_2C-Primary \\\n", + "Timestamp \n", + "2016-01-01 00:28:00 24795.742996 127.134287 \n", + "2016-01-01 00:28:10 24971.853392 127.197315 \n", + "2016-01-01 00:28:20 25155.643730 127.257710 \n", + "2016-01-01 00:28:30 25346.943757 127.315462 \n", + "2016-01-01 00:28:40 25545.573072 127.370566 \n", + "\n", + " F_MMA_SHA_2C-Secondary \\\n", + "Timestamp \n", + "2016-01-01 00:28:00 33.517576 \n", + "2016-01-01 00:28:10 33.718966 \n", + "2016-01-01 00:28:20 33.932493 \n", + "2016-01-01 00:28:30 34.157878 \n", + "2016-01-01 00:28:40 34.394821 \n", + "\n", " B_NEC \\\n", "Timestamp \n", - "2016-01-01 00:28:00 [23432.0698, 2929.1195000000002, 7001.0706] \n", - "2016-01-01 00:28:10 [23450.916100000002, 2900.0733, 7554.7711] \n", - "2016-01-01 00:28:20 [23465.5579, 2872.2875000000004, 8110.08480000... \n", - "2016-01-01 00:28:30 [23476.096100000002, 2844.6195000000002, 8666.... \n", - "2016-01-01 00:28:40 [23482.2611, 2817.4788000000003, 9225.42960000... \n", + "2016-01-01 00:28:00 [23432.3208, 2928.6274000000003, 7000.6948] \n", + "2016-01-01 00:28:10 [23451.1801, 2899.534, 7554.4035] \n", + "2016-01-01 00:28:20 [23465.812100000003, 2871.8025000000002, 8109.... \n", + "2016-01-01 00:28:30 [23476.3596, 2844.1066, 8666.6296] \n", + "2016-01-01 00:28:40 [23482.5073, 2817.0387, 9225.1501] \n", "\n", " B_NEC_MCO_SHA_2C \\\n", "Timestamp \n", - "2016-01-01 00:28:00 [23605.90473830581, 2931.6376573093767, 7003.2... \n", - "2016-01-01 00:28:10 [23624.47275153368, 2902.82041629198, 7556.563... \n", - "2016-01-01 00:28:20 [23638.81245985172, 2874.59744914569, 8111.724... \n", - "2016-01-01 00:28:30 [23648.85230986529, 2846.960888233475, 8668.59... \n", - "2016-01-01 00:28:40 [23654.524148541142, 2819.9042242487117, 9227.... \n", + "2016-01-01 00:28:00 [23604.765693069177, 2930.2688321265728, 7004.... \n", + "2016-01-01 00:28:10 [23623.327008268625, 2901.392633804825, 7557.3... \n", + "2016-01-01 00:28:20 [23637.6657342565, 2873.1207904780367, 8112.48... \n", + "2016-01-01 00:28:30 [23647.710917671076, 2845.447078036548, 8669.2... \n", + "2016-01-01 00:28:40 [23653.3949273992, 2818.3663458182045, 9227.67... \n", "\n", " B_NEC_MMA_SHA_2C-Primary \\\n", "Timestamp \n", - "2016-01-01 00:28:00 [-126.91532798907662, -5.6198964252082355, 17.... \n", - "2016-01-01 00:28:10 [-126.72131580143507, -5.5793404901198125, 19.... \n", - "2016-01-01 00:28:20 [-126.50108249657657, -5.538713843992417, 21.0... \n", - "2016-01-01 00:28:30 [-126.2546612277731, -5.4980200665725025, 22.7... \n", - "2016-01-01 00:28:40 [-125.98209353284206, -5.457262630766026, 24.4... \n", + "2016-01-01 00:28:00 [-125.78890509740265, -5.302191425378982, 17.6... \n", + "2016-01-01 00:28:10 [-125.60166979171441, -5.252627485291866, 19.3... \n", + "2016-01-01 00:28:20 [-125.38835978948227, -5.203014637815361, 21.1... \n", + "2016-01-01 00:28:30 [-125.14900320901313, -5.153357531132542, 22.8... \n", + "2016-01-01 00:28:40 [-124.88363654458195, -5.103660714780139, 24.5... \n", "\n", " B_NEC_MMA_SHA_2C-Secondary \\\n", "Timestamp \n", - "2016-01-01 00:28:00 [-33.09016927969169, -1.7093996086641592, -6.8... \n", - "2016-01-01 00:28:10 [-33.089430547649556, -1.6811466971580775, -7.... \n", - "2016-01-01 00:28:20 [-33.08040682359359, -1.653116949429096, -8.60... \n", - "2016-01-01 00:28:30 [-33.06303949938177, -1.6253091491888805, -9.4... \n", - "2016-01-01 00:28:40 [-33.03727353768437, -1.5977218864183431, -10.... \n", - "\n", - " F F_MCO_SHA_2C F_MMA_SHA_2C-Primary \\\n", - "Timestamp \n", - "2016-01-01 00:28:00 24630.6298 24796.748750 128.250015 \n", - "2016-01-01 00:28:10 24808.1068 24972.859780 128.305090 \n", - "2016-01-01 00:28:20 24993.3690 25156.646166 128.357507 \n", - "2016-01-01 00:28:30 25186.2222 25347.936846 128.407260 \n", - "2016-01-01 00:28:40 25386.4754 25546.550658 128.454343 \n", - "\n", - " F_MMA_SHA_2C-Secondary Latitude Longitude QDLat \\\n", - "Timestamp \n", - "2016-01-01 00:28:00 33.843027 0.197610 -102.681841 8.425159 \n", - "2016-01-01 00:28:10 34.025930 0.838157 -102.693946 9.068963 \n", - "2016-01-01 00:28:20 34.221683 1.478724 -102.706041 9.713511 \n", - "2016-01-01 00:28:30 34.430033 2.119308 -102.718118 10.358768 \n", - "2016-01-01 00:28:40 34.650704 2.759911 -102.730169 11.004701 \n", + "2016-01-01 00:28:00 [-32.5031562033331, -1.386212193226803, -8.065... \n", + "2016-01-01 00:28:10 [-32.494418615582255, -1.3566476193699097, -8.... \n", + "2016-01-01 00:28:20 [-32.47777846302375, -1.3273711242288924, -9.7... \n", + "2016-01-01 00:28:30 [-32.45318157347463, -1.2983807863969918, -10.... \n", + "2016-01-01 00:28:40 [-32.42057710492788, -1.2696744882122784, -11.... \n", "\n", - " QDLon Radius Spacecraft \n", - "Timestamp \n", - "2016-01-01 00:28:00 -30.812750 6828390.61 A \n", - "2016-01-01 00:28:10 -30.902256 6828288.87 A \n", - "2016-01-01 00:28:20 -30.990784 6828186.32 A \n", - "2016-01-01 00:28:30 -31.078363 6828082.97 A \n", - "2016-01-01 00:28:40 -31.165012 6827978.83 A " + " QDLat QDLon \n", + "Timestamp \n", + "2016-01-01 00:28:00 8.425159 -30.812750 \n", + "2016-01-01 00:28:10 9.068963 -30.902256 \n", + "2016-01-01 00:28:20 9.713511 -30.990784 \n", + "2016-01-01 00:28:30 10.358768 -31.078363 \n", + "2016-01-01 00:28:40 11.004701 -31.165012 " ] }, "execution_count": 4, @@ -339,12 +331,12 @@ { "data": { "text/plain": [ - "(array([23432.0698, 23450.9161, 23465.5579, ..., 19454.1593, 19270.8571,\n", - " 19083.8857]),\n", - " array([2929.1195, 2900.0733, 2872.2875, ..., 825.3428, 809.5323,\n", - " 793.5203]),\n", - " array([ 7001.0706, 7554.7711, 8110.0848, ..., 32625.3874, 33094.8776,\n", - " 33559.6071]))" + "(array([23432.3208, 23451.1801, 23465.8121, ..., 19454.4269, 19271.3803,\n", + " 19084.199 ]),\n", + " array([2928.6274, 2899.534 , 2871.8025, ..., 824.8021, 808.9134,\n", + " 792.9848]),\n", + " array([ 7000.6948, 7554.4035, 8109.7535, ..., 32624.9088, 33094.2485,\n", + " 33559.0953]))" ] }, "execution_count": 5, @@ -387,23 +379,23 @@ "\n", "Dimensions: (Timestamp: 4256, dim: 3)\n", "Coordinates:\n", - " * Timestamp (Timestamp) datetime64[ns] 2016-01-01T00:28:00 ...\n", + " * Timestamp (Timestamp) datetime64[ns] 2016-01-01T00:28:00 ... 2016-01-01T23:59:50\n", "Dimensions without coordinates: dim\n", "Data variables:\n", - " Spacecraft (Timestamp) \n", - "array([[-13.829441, 4.811139, -12.867523],\n", - " [-13.745905, 4.513371, -13.351697],\n", - " [-13.673071, 4.881882, -14.067758],\n", + "array([[-14.152832, 5.046971, -13.00904 ],\n", + " [-14.05082 , 4.750641, -13.44751 ],\n", + " [-13.987496, 5.212095, -14.088606],\n", " ...,\n", - " [ 0.58677 , -1.772758, -4.235812],\n", - " [ 1.096503, -2.327229, -4.433677],\n", - " [ 2.135158, -3.167922, -4.39485 ]])\n", + " [ -0.065038, -2.56282 , -3.695835],\n", + " [ 0.583051, -3.247 , -4.246418],\n", + " [ 1.325767, -4.054801, -4.315355]])\n", "Coordinates:\n", - " * Timestamp (Timestamp) datetime64[ns] 2016-01-01T00:28:00 ...\n", + " * Timestamp (Timestamp) datetime64[ns] 2016-01-01T00:28:00 ... 2016-01-01T23:59:50\n", "Dimensions without coordinates: dim" ] }, @@ -527,7 +519,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.5" + "version": "3.7.3" } }, "nbformat": 4, diff --git a/docs/readme.rst b/docs/readme.rst index 5bbc517..66611c1 100644 --- a/docs/readme.rst +++ b/docs/readme.rst @@ -1,10 +1,23 @@ Introduction ============ -This is the documentation for the ``viresclient`` Python package. This is a tool which connects to a VirES_ server through the WPS_ interface and handles product requests and downloads. This enables easy access to ESA's Swarm mission data and models. If you would like access (a user account is required) or need help, please email info@vires.services +This is the documentation for the ``viresclient`` Python package. This is a tool which connects to a VirES_ server through the WPS_ interface and handles product requests and downloads. This enables easy access to ESA's Swarm mission data and models. For enquiries or help, please email info@vires.services or `raise an issue on GitHub`_ .. _VirES: https://vires.services .. _WPS: http://www.opengeospatial.org/standards/wps +.. _`raise an issue on GitHub`: https://github.com/ESA-VirES/VirES-Python-Client/issues + +Some links where you can read more about VirES: + + - `VirES web service`_ + - `Swarm DQW slides about viresclient`_ + - `EOX blog posts`_ + - `Swarm mission`_ + + .. _`VirES web service`: https://vires.services/ + .. _`Swarm DQW slides about viresclient`: https://github.com/smithara/viresclient_examples/blob/master/viresclient_SwarmDQW8.pdf + .. _`EOX blog posts`: https://eox.at/category/vires/ + .. _`Swarm mission`: https://earth.esa.int/web/guest/missions/esa-operational-eo-missions/swarm Data can be accessed from the server as CSV or CDF files and saved to disk, or loaded directly into Python objects pandas.DataFrame_, or xarray.Dataset_. @@ -20,96 +33,9 @@ The project is on GitHub at https://github.com/ESA-VirES/VirES-Python-Client - p A repository of example notebooks can be found at https://github.com/smithara/viresclient_examples. We welcome contribution of notebooks to this repository that show some short analyses or generating useful figures. -Installation ------------- - -Linux/Unix and Python ≥ 3.5 is required for full support (since cdflib requires ≥ 3.5). - -Python 3.4 can also be used, but conversion from CDF to pandas/xarray is not supported - you can still download and save CDF files - :meth:`viresclient.ReturnedData.to_file`, or download as CSV files and convert to pandas - :meth:`viresclient.ReturnedData.as_dataframe`. - -It can currently be installed with:: - - pip install viresclient - -Dependencies:: - - Jinja2 ≥ 2.10.0 - pandas ≥ 0.18.0 - cdflib = 0.3.9 - tables ≥ 3.4.4 - tqdm ≥ 4.23.0 - xarray ≥ 0.10.0 - -There is an unresolved bug with Windows support - see here_. - -.. _here: https://github.com/ESA-VirES/VirES-Python-Client/issues/1 - -Example usage -------------- - -Import the package and set up the connection to the server: - -.. code-block:: python - - from viresclient import SwarmRequest - import datetime as dt - - request = SwarmRequest(url="https://staging.viresdisc.vires.services/openows", - username="your username", - password="your password") - -Choose which collection to access: - -.. code-block:: python - - request.set_collection("SW_OPER_MAGA_LR_1B") - -Choose a combination of variables to retrieve. ``measurements`` are measured by the satellite and members of the specified ``collection``; ``models`` are evaluated on the server at the positions of the satellite; ``auxiliaries`` are additional parameters not unique to the ``collection``. If ``residuals`` is set to ``True`` then only data-model residuals are returned. Optionally specify a resampling of the original time series with ``sampling_step`` (an `ISO-8601 duration `_). - -.. code-block:: python - - request.set_products(measurements=["F","B_NEC"], - models=["MCO_SHA_2C", "MMA_SHA_2C-Primary", "MMA_SHA_2C-Secondary"], - auxiliaries=["QDLat", "QDLon", "MLT", "OrbitNumber", "SunZenithAngle"], - residuals=False, - sampling_step="PT10S") - -Set a parameter range filter to apply. You can add multiple filters in sequence - -.. code-block:: python - - request.set_range_filter(parameter="Latitude", - minimum=0, - maximum=90) - - request.set_range_filter("Longitude", 0, 90) - -Specify the time range from which to retrieve data, make the request to the server (specifying the output file format, currently either csv or cdf): - -.. code-block:: python - - data = request.get_between(start_time=dt.datetime(2016,1,1), - end_time=dt.datetime(2016,1,2), - filetype="cdf", - asynchronous=True) - -Transfer your data to a pandas.DataFrame_, or a xarray.Dataset_, or just save it as is: - -.. _pandas.DataFrame: https://pandas.pydata.org/pandas-docs/stable/dsintro.html#dataframe - -.. _xarray.Dataset: http://xarray.pydata.org/en/stable/data-structures.html#dataset - -.. code-block:: python +How to acknowledge VirES +------------------------ - df = data.as_dataframe() - ds = data.as_xarray() - data.to_file('outfile.cdf', overwrite=False) +You can reference ``viresclient`` directly using the DOI of our zenodo_ record. VirES uses data from a number of different sources so please also acknowledge these appropriately. -The returned data has columns for: - - ``Spacecraft, Timestamp, Latitude, Longitude, Radius`` - - those specified by ``measurements`` and ``auxiliaries`` -... and model values and residuals, named as: - - ``F_`` -- scalar field - - ``B_NEC_`` -- vector field - - ``F_res_`` -- scalar field residual (``F - F_``) - - ``B_NEC_res_`` -- vector field residual (``B_NEC - B_NEC_``) + .. _zenodo: https://doi.org/10.5281/zenodo.2554163 diff --git a/docs/release_notes.rst b/docs/release_notes.rst index 153ad99..2b42a21 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -1,6 +1,12 @@ Release notes ============= +Changes from v0.2.6 to 0.3.0 +---------------------------- + +- Service officially open to public through self-registration on https://vires.services +- Token-based authentication added + Changes from v0.2.5 to 0.2.6 ----------------------------