diff --git a/argopy/options.py b/argopy/options.py index 78fe7c1db..9c6b6035c 100644 --- a/argopy/options.py +++ b/argopy/options.py @@ -50,6 +50,7 @@ PARALLEL = "parallel" PARALLEL_DEFAULT_METHOD = "parallel_default_method" LON = "longitude_convention" +NVS = "nvs" # Define the list of available options and default values: OPTIONS = { @@ -69,6 +70,7 @@ PARALLEL: False, PARALLEL_DEFAULT_METHOD: "thread", LON: "180", + NVS: "https://vocab.nerc.ac.uk/collection", } DEFAULT = OPTIONS.copy() @@ -134,6 +136,7 @@ def validate_parallel_method(method): PARALLEL: validate_parallel, PARALLEL_DEFAULT_METHOD: validate_parallel_method, LON: lambda x: x in ['180', '360'], + NVS: lambda x: isinstance(x, str) or x is None, } @@ -229,6 +232,9 @@ class set_options: - '180': longitude goes from -180 to 180 - '360': longitude goes from 0 to 360 + nvs: str, default: 'https://vocab.nerc.ac.uk/collection' + Argo NVS server + Other Parameters ---------------- server: : str, default: None diff --git a/argopy/related/reference_tables.py b/argopy/related/reference_tables.py index 892329a59..5c77dcc38 100644 --- a/argopy/related/reference_tables.py +++ b/argopy/related/reference_tables.py @@ -1,100 +1,39 @@ import pandas as pd from functools import lru_cache import collections -from pathlib import Path -from ..stores import httpstore, filestore -from ..options import OPTIONS -from ..utils import path2assets +from argopy.stores import httpstore +from argopy.options import OPTIONS +from argopy.utils.format import urnparser +from argopy.utils.locals import Asset +VALID_REF = Asset().load("nvs_reference_tables")['data']['valid_ref'] -VALID_REF = filestore(cache=True).open_json(Path(path2assets).joinpath("nvs_reference_tables.json"))['data']['valid_ref'] - -class ArgoNVSReferenceTables: - """Argo Reference Tables - - Utility function to retrieve Argo Reference Tables from a NVS server. - - By default, this relies on: https://vocab.nerc.ac.uk/collection - - Examples - -------- - Methods: - - >>> R = ArgoNVSReferenceTables() - >>> R.search('sensor') - >>> R.tbl(3) - >>> R.tbl('R09') - - Properties: - - >>> R.all_tbl_name - >>> R.all_tbl - >>> R.valid_ref - - Notes - ----- - This class relies on a list of valid reference table ids that is updated on every argopy release. - - """ - valid_ref = VALID_REF.copy() - - """List of all available Reference Tables""" +class NVScollection: + """ A class to handle any NVS collection table """ def __init__( self, - nvs="https://vocab.nerc.ac.uk/collection", - cache: bool = True, - cachedir: str = "", + **kwargs, ): - """Argo Reference Tables from NVS""" + """Reference Tables from NVS collection""" + self.nvs = kwargs.get("nvs", OPTIONS["nvs"]) - cachedir = OPTIONS["cachedir"] if cachedir == "" else cachedir - self.fs = httpstore(cache=cache, cachedir=cachedir) - self.nvs = nvs + self.fs = kwargs.get("fs", None) + if self.fs is None: + self._cache = kwargs.get("cache", True) + self._cachedir = kwargs.get("cachedir", OPTIONS["cachedir"]) + self._timeout = kwargs.get("timeout", OPTIONS["api_timeout"]) + self.fs = httpstore(cache=self._cache, cachedir=self._cachedir, timeout=self._timeout) - def _valid_ref(self, rtid): - """ - Validate any rtid argument and return the corresponding valid ID from the list. - - Parameters - ---------- - rtid: Input reference ID. Can be a string (e.g., "R12", "12", "r12") or a number (e.g., 12). - - Returns: - str: Valid reference ID from the list, or None if not found. - """ - # Convert rtid to a string and standardize its format - if isinstance(rtid, (int, float)): - # If rtid is a number, format it as "RXX" - rtid_str = f"R{int(rtid):02d}" - else: - # If rtid is a string, convert to uppercase and standardize - rtid_str = str(rtid).strip().upper() - if rtid_str.startswith('R') and len(rtid_str) > 1: - # If it starts with 'R', ensure the numeric part is two digits - prefix = rtid_str[0] - suffix = rtid_str[1:] - try: - num = int(suffix) - rtid_str = f"{prefix}{num:02d}" - except ValueError: - pass # Keep the original string if conversion fails - elif ~rtid_str.startswith('R'): - try: - num = int(rtid_str) - rtid_str = f"R{num}" - except ValueError: - pass # Keep the original string if conversion fails + @property + def valid_ref(self): + df = self._FullCollection() + return df['ID'].to_list() - # Check if the standardized rtid_str is in the valid_refs list - if rtid_str in self.valid_ref: - return rtid_str - else: - raise ValueError( - f"Invalid Argo Reference Table '{rtid}', must be one in: {', '.join(self.valid_ref)}" - ) + def _valid_ref(self, rtid): + """No validation""" return rtid def _jsConcept2df(self, data): @@ -104,18 +43,21 @@ def _jsConcept2df(self, data): "prefLabel": [], "definition": [], "deprecated": [], + "urn": [], "id": [], } for k in data["@graph"]: if k["@type"] == "skos:Collection": Collection_name = k["dc:alternative"] elif k["@type"] == "skos:Concept": - content["altLabel"].append(k["skos:altLabel"]) + content["altLabel"].append(urnparser(k['skos:notation'])['termid']) content["prefLabel"].append(k["skos:prefLabel"]["@value"]) - content["definition"].append(k["skos:definition"]["@value"] if k["skos:definition"] != '' else None) + content["definition"].append(k["skos:definition"]["@value"]) content["deprecated"].append(k["owl:deprecated"]) + content["urn"].append(k['skos:notation']) content["id"].append(k["@id"]) df = pd.DataFrame.from_dict(content) + df['deprecated'] = df.apply(lambda x: True if x['deprecated']=='true' else False, axis=1) df.name = Collection_name return df @@ -128,6 +70,25 @@ def _jsCollection(self, data): rtid = k["@id"] return (name, desc, rtid) + def _jsFullCollection(self, data): + """Return all skos:Collection information as data""" + result = [] + for k in data["@graph"]: + if k["@type"] == "skos:Collection": + title = k["dc:title"] + name = k["dc:alternative"] + desc = k["dc:description"] + url = k["@id"] + tid = k['@id'].split('/')[-3] + result.append((tid, title, name, desc, url)) + return result + + @lru_cache + def _FullCollection(self): + url = f"{self.nvs}/?_profile=nvs&_mediatype=application/ld+json" + js = self.fs.open_json(url) + return pd.DataFrame(self._jsFullCollection(js), columns=['ID', 'title', 'name', 'description', 'url']) + def get_url(self, rtid, fmt="ld+json"): """Return URL toward a given reference table for a given format @@ -158,7 +119,7 @@ def get_url(self, rtid, fmt="ld+json"): @lru_cache def tbl(self, rtid): - """Return an Argo Reference table + """Return a Reference table Parameters ---------- @@ -174,8 +135,9 @@ def tbl(self, rtid): df = self._jsConcept2df(js) return df + @lru_cache def tbl_name(self, rtid): - """Return name of an Argo Reference table + """Return name of a Reference table Parameters ---------- @@ -190,6 +152,41 @@ def tbl_name(self, rtid): js = self.fs.open_json(self.get_url(rtid)) return self._jsCollection(js) + @property + def all_tbl(self): + """Return all Reference tables + + Returns + ------- + OrderedDict + Dictionary with all table short names as key and table content as class:`pandas.DataFrame` + """ + URLs = [self.get_url(rtid) for rtid in self.valid_ref] + df_list = self.fs.open_mfjson(URLs, preprocess=self._jsConcept2df) + all_tables = {} + [all_tables.update({t.name: t}) for t in df_list] + all_tables = collections.OrderedDict(sorted(all_tables.items())) + return all_tables + + @property + def all_tbl_name(self): + """Return names of all Reference tables + + Returns + ------- + OrderedDict + Dictionary with all table short names as key and table names as tuple('short name', 'description', 'NVS id link') + """ + URLs = [self.get_url(rtid) for rtid in self.valid_ref] + name_list = self.fs.open_mfjson(URLs, preprocess=self._jsCollection) + all_tables = {} + [ + all_tables.update({rtid.split("/")[-3]: (name, desc, rtid)}) + for name, desc, rtid in name_list + ] + all_tables = collections.OrderedDict(sorted(all_tables.items())) + return all_tables + def search(self, txt, where="all"): """Search for string in tables title and/or description @@ -218,37 +215,76 @@ def search(self, txt, where="all"): results.append(tbl_id) return results - @property - def all_tbl(self): - """Return all Argo Reference tables - Returns - ------- - OrderedDict - Dictionary with all table short names as key and table content as class:`pandas.DataFrame` +class ArgoNVSReferenceTables(NVScollection): + """Argo Reference Tables + + Utility function to retrieve Argo Reference Tables from a NVS server. + + By default, this relies on: https://vocab.nerc.ac.uk/collection + + Examples + -------- + Methods: + + >>> R = ArgoNVSReferenceTables() + >>> R.search('sensor') + >>> R.tbl(3) + >>> R.tbl('R09') + + Properties: + + >>> R.all_tbl_name + >>> R.all_tbl + >>> R.valid_ref + + Notes + ----- + This class relies on a list of valid reference table ids that is updated on every argopy release. + + """ + valid_ref = VALID_REF.copy() + + """List of all available Reference Tables""" + + def _valid_ref(self, rtid): """ - URLs = [self.get_url(rtid) for rtid in self.valid_ref] - df_list = self.fs.open_mfjson(URLs, preprocess=self._jsConcept2df) - all_tables = {} - [all_tables.update({t.name: t}) for t in df_list] - all_tables = collections.OrderedDict(sorted(all_tables.items())) - return all_tables + Validate any rtid argument and return the corresponding valid ID from the list. - @property - def all_tbl_name(self): - """Return names of all Argo Reference tables + Parameters + ---------- + rtid: Input reference ID. Can be a string (e.g., "R12", "12", "r12") or a number (e.g., 12). - Returns - ------- - OrderedDict - Dictionary with all table short names as key and table names as tuple('short name', 'description', 'NVS id link') + Returns: + str: Valid reference ID from the list, or None if not found. """ - URLs = [self.get_url(rtid) for rtid in self.valid_ref] - name_list = self.fs.open_mfjson(URLs, preprocess=self._jsCollection) - all_tables = {} - [ - all_tables.update({rtid.split("/")[-3]: (name, desc, rtid)}) - for name, desc, rtid in name_list - ] - all_tables = collections.OrderedDict(sorted(all_tables.items())) - return all_tables + # Convert rtid to a string and standardize its format + if isinstance(rtid, (int, float)): + # If rtid is a number, format it as "RXX" + rtid_str = f"R{int(rtid):02d}" + else: + # If rtid is a string, convert to uppercase and standardize + rtid_str = str(rtid).strip().upper() + if rtid_str.startswith('R') and len(rtid_str) > 1: + # If it starts with 'R', ensure the numeric part is two digits + prefix = rtid_str[0] + suffix = rtid_str[1:] + try: + num = int(suffix) + rtid_str = f"{prefix}{num:02d}" + except ValueError: + pass # Keep the original string if conversion fails + elif ~rtid_str.startswith('R'): + try: + num = int(rtid_str) + rtid_str = f"R{num}" + except ValueError: + pass # Keep the original string if conversion fails + + # Check if the standardized rtid_str is in the valid_refs list + if rtid_str in self.valid_ref: + return rtid_str + else: + raise ValueError( + f"Invalid Argo Reference Table '{rtid}', must be one in: {', '.join(self.valid_ref)}" + ) \ No newline at end of file diff --git a/argopy/sensors/__init__.py b/argopy/sensors/__init__.py new file mode 100644 index 000000000..08fae8df8 --- /dev/null +++ b/argopy/sensors/__init__.py @@ -0,0 +1,3 @@ +from argopy.sensors.references import ArgoSensorReferences + +__all__ = ('ArgoSensorReferences') \ No newline at end of file diff --git a/argopy/sensors/accessories.py b/argopy/sensors/accessories.py new file mode 100644 index 000000000..cfc69ce46 --- /dev/null +++ b/argopy/sensors/accessories.py @@ -0,0 +1,112 @@ +from typing import Literal, Any +import pandas as pd +import xarray as xr +import numpy as np +from dataclasses import dataclass +from html import escape + +from argopy.options import OPTIONS +from argopy.utils.format import ppliststr +from argopy.utils.checkers import to_list +from argopy.utils.accessories import NVSrow + + +# Define some options expected values as tuples +# (for argument validation) +SearchOutput = ("wmo", "sn", "wmo_sn", "df") +Error = ("raise", "ignore", "silent") +Ds = ("core", "deep", "bgc") + +# Define Literal types using tuples +# (for typing) +SearchOutputOptions = Literal[*SearchOutput] +ErrorOptions = Literal[*Error] +DsOptions = Literal[*Ds] + + +class SensorType(NVSrow): + """One single sensor type data from a R25-"Argo sensor types" row + + .. warning:: + This class is experimental and may change in a future release. + + Examples + -------- + .. code-block:: python + + from argopy import ArgoNVSReferenceTables + + sensor_type = 'CTD' + + df = ArgoNVSReferenceTables().tbl(25) + df_match = df[df["altLabel"].apply(lambda x: x == sensor_type)].iloc[0] + + st = SensorType.from_series(df_match) + + st.name + st.long_name + st.definition + st.deprecated + st.uri + + """ + reftable = "R25" + + @staticmethod + def from_series(obj: pd.Series) -> "SensorType": + """Create a :class:`SensorType` from a R25-"Argo sensor models" row""" + return SensorType(obj) + + +class SensorModel(NVSrow): + """One single sensor model data from a R27-"Argo sensor models" row + + .. warning:: + This class is experimental and may change in a future release. + + Examples + -------- + .. code-block:: python + + from argopy import ArgoSensor + + sm = ArgoSensor('AANDERAA_OPTODE_4330F').vocabulary + + sm.name + sm.long_name + sm.definition + sm.deprecated + sm.urn + sm.uri + + .. code-block:: python + + from argopy import ArgoNVSReferenceTables + + sensor_model = 'AANDERAA_OPTODE_4330F' + + df = ArgoNVSReferenceTables().tbl(27) + df_match = df[df["altLabel"].apply(lambda x: x == sensor_model)].iloc[0] + + sm = SensorModel.from_series(df_match) + + sm.name + sm.long_name + sm.definition + sm.deprecated + sm.urn + sm.uri + """ + + reftable = "R27" + + @staticmethod + def from_series(obj: pd.Series) -> "SensorModel": + """Create a :class:`SensorModel` from a R27-"Argo sensor models" row""" + return SensorModel(obj) + + def __contains__(self, string) -> bool: + return ( + string.lower() in self.name.lower() + or string.lower() in self.long_name.lower() + ) diff --git a/argopy/sensors/references.py b/argopy/sensors/references.py new file mode 100644 index 000000000..e0d0d661c --- /dev/null +++ b/argopy/sensors/references.py @@ -0,0 +1,373 @@ +import pandas as pd +from pathlib import Path +from typing import Literal, NoReturn +from abc import ABC, abstractmethod +import logging +import fnmatch + +from argopy.options import OPTIONS +from argopy.stores import httpstore +from argopy.related.reference_tables import ArgoNVSReferenceTables +from argopy.errors import DataNotFound, OptionValueError + +from argopy.utils.locals import Asset +from argopy.utils.decorators import register_accessor +from argopy.utils.format import ppliststr +from argopy.utils.checkers import to_list + +from argopy.sensors.accessories import SensorType, SensorModel, Error, ErrorOptions + + +log = logging.getLogger("argopy.sensors.ref") + + +class SensorReferenceHolder(ABC): + """Parent class to hold R25, R26, R27 and R27_to_R25 mapping data""" + + _r25: pd.DataFrame | None = None + """NVS Reference table for Argo sensor types (R25)""" + + _r26: pd.DataFrame | None = None + """NVS Reference table for Argo sensor maker (R26)""" + + _r27: pd.DataFrame | None = None + """NVS Reference table for Argo sensor models (R27)""" + + _r27_to_r25: dict[str, str] | None = None + """Dictionary mapping of R27 to R25""" + + def __call__(self, *args, **kwargs) -> NoReturn: + raise ValueError("A SensorReference instance cannot be called directly.") + + def __init__(self, obj): + self._obj = obj # An instance of SensorReferences, possibly with a filesystem + if getattr(obj, "_fs", None) is None: + self._fs = httpstore( + cache=True, + cachedir=OPTIONS["cachedir"], + timeout=OPTIONS["api_timeout"], + ) + else: + self._fs = obj._fs + + @property + def r25(self): + """NVS Reference table for Argo sensor types (R25)""" + if self._r25 is None: + self._r25 = ArgoNVSReferenceTables(fs=self._fs).tbl("R25") + return self._r25 + + @property + def r26(self): + """NVS Reference table for Argo sensor maker (R26)""" + if self._r26 is None: + self._r26 = ArgoNVSReferenceTables(fs=self._fs).tbl("R26") + return self._r26 + + @property + def r27(self): + """NVS Reference table for Argo sensor models (R27)""" + if self._r27 is None: + self._r27 = ArgoNVSReferenceTables(fs=self._fs).tbl("R27") + return self._r27 + + def _load_mappers(self): + """Load from static assets file the NVS R25 to R27 key mappings + + These mapping files were download from https://github.com/OneArgo/ArgoVocabs/issues/156. + """ + df = [] + for uri in ['1', '2', '2b', '3', '3b', '4_cndc', '4_ido_doxy', '4_pres', '4_temp']: + df.append(Asset().load(f"nvs_R25_R27:NVS_R25_R27_mappings_{uri}.txt", + header=None, + names=["origin", "model", "?", "destination", "type", "??"])) + df = pd.concat(df) + for col in ['origin', 'destination', 'type', 'model', '?', '??']: + df[col] = df[col].apply(lambda x: x.strip()) + df = df.reset_index(drop=True) + self._r27_to_r25 : pd.DataFrame | None = df + + @property + def r27_to_r25(self) -> pd.DataFrame: + """Dictionary mapping of R27 to R25""" + if self._r27_to_r25 is None: + self._load_mappers() + return self._r27_to_r25 + + @abstractmethod + def to_dataframe(self): + raise NotImplementedError + + @abstractmethod + def hint(self): + raise NotImplementedError + + +class SensorReferenceR27(SensorReferenceHolder): + """Argo sensor models""" + + def to_dataframe(self) -> pd.DataFrame: + """Reference Table **Sensor Models (R27)** as a :class:`pandas.DataFrame` + + Returns + ------- + :class:`pandas.DataFrame` + + See Also + -------- + :class:`ArgoNVSReferenceTables` + """ + return self.r27 + + def hint(self) -> list[str]: + """List of Argo sensor models + + Return a sorted list of strings with altLabel from Argo Reference table R27 on 'SENSOR_MODEL'. + + Returns + ------- + list[str] + + Notes + ----- + Argo netCDF variable ``SENSOR_MODEL`` is populated with values from this list. + """ + return sorted(to_list(self.r27["altLabel"].values)) + + def to_type( + self, + model: str | SensorModel, + errors: ErrorOptions = "raise", + obj: bool = False, + ) -> list[str] | list[SensorType] | None: + """Get all sensor types of a given sensor model + + All valid sensor model names can be obtained with :meth:`ArgoSensor.ref.model.hint`. + + Mapping between sensor model name (R27) and sensor type (R25) are from AVTT work at https://github.com/OneArgo/ArgoVocabs/issues/156. + + Parameters + ---------- + model : str | :class:`argopy.related.SensorModel` + The sensor model to read the sensor type for. + errors : Literal["raise", "ignore", "silent"], optional, default: "raise" + How to handle possible errors. If set to "ignore", the method may return None. + obj: bool, optional, default: False + Return a list of strings (False) or a list of :class:`argopy.related.SensorType` + + Returns + ------- + list[str] | list[:class:`argopy.related.SensorType`] | None + + Raises + ------ + :class:`DataNotFound` + """ + if errors not in Error: + raise OptionValueError( + f"Invalid 'errors' option value '{errors}', must be in: {ppliststr(Error, last='or')}" + ) + model_name: str = model.name if isinstance(model, SensorModel) else model + + match = fnmatch.filter(self.r27_to_r25["model"], model_name.upper()) + types = self.r27_to_r25[self.r27_to_r25["model"].apply(lambda x: x in match)]['type'].tolist() + + if len(types) > 0: + # Since 'types' comes from the mapping, we double-check values against the R25 entries: + types = [self.r25[self.r25["altLabel"].apply(lambda x: x == this_type)]['altLabel'].item() for this_type in types] + if not obj: + return types + else: + rows = [self.r25[self.r25["altLabel"].apply(lambda x: x == this_type)].iloc[0] for this_type in types] + return [SensorType.from_series(row) for row in rows] + + elif errors == "raise": + raise DataNotFound( + f"Can't determine the type of sensor model '{model_name}' (no matching key in r27_to_r25 mapper)" + ) + elif errors == "silent": + log.error( + f"Can't determine the type of sensor model '{model_name}' (no matching key in r27_to_r25 mapper)" + ) + return None + + def search( + self, + model: str, + output: Literal["df", "name"] = "df", + ) -> pd.DataFrame | list[str]: + """Return Argo sensor model references matching a string + + Look for occurrences in Argo Reference table R27 `altLabel` and return a subset of the :class:`pandas.DataFrame` with matching row(s). + + Parameters + ---------- + model : str + The model to search for. You can use wildcards: "SBE41CP*" "*DEEP*", "RBR*", or an exact name like "RBR_ARGO3_DEEP6". + output : str, Literal["df", "name"], default "df" + Is the output a :class:`pandas.DataFrame` with matching rows from R27, or a list of string. + + Returns + ------- + :class:`pandas.DataFrame`, list[str] + + Raises + ------ + :class:`DataNotFound` + """ + match = fnmatch.filter(self.r27["altLabel"], model.upper()) + data = self.r27[self.r27["altLabel"].apply(lambda x: x in match)] + + if data.shape[0] == 0: + raise DataNotFound( + f"'{model}' is not a valid sensor model name. You can use wildcard for search, e.g. 'SBE61*'." + ) + else: + if output == "name": + return sorted(to_list(data["altLabel"].values)) + else: + return data.reset_index(drop=True) + + +class SensorReferenceR25(SensorReferenceHolder): + """Argo sensor types""" + + def to_dataframe(self) -> pd.DataFrame: + """Reference Table **Sensor Types (R25)** as a :class:`pandas.DataFrame` + + Returns + ------- + :class:`pandas.DataFrame` + """ + return self.r25 + + def hint(self) -> list[str]: + """List of Argo sensor types + + Return a sorted list of strings with altLabel from Argo Reference table R25 on 'SENSOR'. + + Returns + ------- + list[str] + + Notes + ----- + Argo netCDF variable ``SENSOR`` is populated with values from this list. + """ + return sorted(to_list(self.r25["altLabel"].values)) + + def to_model( + self, + type: str | SensorType, + errors: Literal["raise", "ignore"] = "raise", + obj: bool = False, + ) -> list[str] | list[SensorModel] | None: + """Get all sensor model names of a given sensor type + + All valid sensor types can be obtained with :meth:`ArgoSensor.ref.sensor.hint` + + Mapping between sensor model name (R27) and sensor type (R25) are from AVTT work at https://github.com/OneArgo/ArgoVocabs/issues/156. + + Parameters + ---------- + type : str, :class:`argopy.related.SensorType` + The sensor type to read the sensor model name for. + errors : Literal["raise", "ignore"] = "raise" + How to handle possible errors. If set to "ignore", the method will return None. + obj: bool, optional, default: False + Return a list of strings (False) or a list of :class:`argopy.related.SensorModel` + + Returns + ------- + list[str] | list[:class:`argopy.related.SensorModel`] | None + + Raises + ------ + :class:`DataNotFound` + """ + sensor_type = type.name if isinstance(type, SensorType) else type + result = [] + + match = fnmatch.filter(self.r27_to_r25["type"], sensor_type.upper()) + models = self.r27_to_r25[self.r27_to_r25["type"].apply(lambda x: x in match)]['model'].tolist() + + if len(models) > 0: + # Since 'models' comes from the mapping, we double-check values against the R27 entries: + models = [self.r27[self.r27["altLabel"].apply(lambda x: x == model)]['altLabel'].item() for + model in models] + if not obj: + return models + else: + rows = [self.r27[self.r27["altLabel"].apply(lambda x: x == model)].iloc[0] for + model in models] + return [SensorModel.from_series(row) for row in rows] + + if len(result) == 0: + if errors == "raise": + raise DataNotFound( + f"Can't find any sensor model for this type '{sensor_type}' (no matching key in r27_to_r25 mapper)" + ) + else: + return None + else: + return result + + +class SensorReferenceR26(SensorReferenceHolder): + """Argo sensor maker""" + + def to_dataframe(self) -> pd.DataFrame: + """Reference Table **Sensor Makers (R26)** as a :class:`pandas.DataFrame` + + Returns + ------- + :class:`pandas.DataFrame` + """ + return self.r26 + + def hint(self) -> list[str]: + """List of Argo sensor makers + + Return a sorted list of strings with altLabel from Argo Reference table R26 on 'SENSOR_MAKER'. + + Returns + ------- + list[str] + + Notes + ----- + Argo netCDF variable ``SENSOR_MAKER`` is populated with values from this list. + """ + return sorted(to_list(self.r26["altLabel"].values)) + + +class ArgoSensorReferences: + + __slots__ = ["_fs", "_cache"] + + def __init__(self, **kwargs): + if 'fs' not in kwargs: + self._fs = httpstore( + cache=True, + cachedir=OPTIONS["cachedir"], + timeout=OPTIONS["api_timeout"], + ) + else: + self._fs = kwargs['fs'] + + def __call__(self, *args, **kwargs) -> NoReturn: + raise ValueError("ArgoSensor.ref cannot be called directly.") + +@register_accessor("type", ArgoSensorReferences) +class SensorExtension(SensorReferenceR25): + _name = "ref.type" + + +@register_accessor("maker", ArgoSensorReferences) +class MakerExtension(SensorReferenceR26): + _name = "ref.maker" + + +@register_accessor("model", ArgoSensorReferences) +class ModelExtension(SensorReferenceR27): + _name = "ref.model" \ No newline at end of file diff --git a/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_1.txt b/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_1.txt new file mode 100644 index 000000000..d671729c8 --- /dev/null +++ b/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_1.txt @@ -0,0 +1,125 @@ +R27, AANDERAA_OPTODE , MIN, R25, OPTODE_DOXY , I +R27, AANDERAA_OPTODE_3830 , MIN, R25, OPTODE_DOXY , I +R27, AANDERAA_OPTODE_3835 , MIN, R25, OPTODE_DOXY , I +R27, AANDERAA_OPTODE_3930 , MIN, R25, OPTODE_DOXY , I +R27, AANDERAA_OPTODE_4330 , MIN, R25, OPTODE_DOXY , I +R27, AANDERAA_OPTODE_4330F , MIN, R25, OPTODE_DOXY , I +R27, AANDERAA_OPTODE_4831 , MIN, R25, OPTODE_DOXY , I +R27, AANDERAA_OPTODE_4831F , MIN, R25, OPTODE_DOXY , I +R27, AMETEK , MIN, R25, CTD_PRES , I +R27, AMETEK_3000PSIA , MIN, R25, CTD_PRES , I +R27, ARO_FT , MIN, R25, OPTODE_DOXY , I +R27, AROD_FT , MIN, R25, OPTODE_DOXY , I +R27, C_ROVER , MIN, R25, TRANSMISSOMETER_CP660 , I +R27, CTD_F01 , MIN, R25, CTD_PRES , I +R27, CTD_F01 , MIN, R25, CTD_TEMP , I +R27, CTD_F01 , MIN, R25, CTD_CNDC , I +R27, CYCLOPS_7_FLUOROMETER , MIN, R25, FLUOROMETER_CHLA , I +R27, CYCLOPS_7_FLUOROMETER , MIN, R25, BACKSCATTERINGMETER_TURBIDITY , I +R27, DRUCK , MIN, R25, CTD_PRES , I +R27, DRUCK_2900PSIA , MIN, R25, CTD_PRES , I +R27, DURA , MIN, R25, TRANSISTOR_PH , I +R27, ECO_FL , MIN, R25, FLUOROMETER_CHLA , I +R27, ECO_FLBB , MIN, R25, FLUOROMETER_CHLA , I +R27, ECO_FLBB_2K , MIN, R25, FLUOROMETER_CHLA , I +R27, ECO_FLBB_AP2 , MIN, R25, FLUOROMETER_CHLA , I +R27, ECO_FLBB2 , MIN, R25, FLUOROMETER_CHLA , I +R27, ECO_FLBBCD , MIN, R25, FLUOROMETER_CHLA , I +R27, ECO_FLBBCD , MIN, R25, FLUOROMETER_CDOM , I +R27, ECO_FLBBCD_AP2 , MIN, R25, FLUOROMETER_CHLA , I +R27, ECO_FLBBCD_AP2 , MIN, R25, FLUOROMETER_CDOM , I +R27, ECO_FLBBFL , MIN, R25, FLUOROMETER_CHLA , I +R27, ECO_FLBBFL_AP2 , MIN, R25, FLUOROMETER_CHLA , I +R27, ECO_FLNTU , MIN, R25, FLUOROMETER_CHLA , I +R27, ECO_FLNTU , MIN, R25, BACKSCATTERINGMETER_TURBIDITY , I +R27, ECO_NTU , MIN, R25, BACKSCATTERINGMETER_TURBIDITY , I +R27, EM , MIN, R25, EM , I +R27, FLOATCLOCK , MIN, R25, FLOATCLOCK_MTIME , I +R27, FSI , MIN, R25, CTD_PRES , I +R27, FSI , MIN, R25, CTD_TEMP , I +R27, FSI , MIN, R25, CTD_CNDC , I +R27, GDF , MIN, R25, TRANSISTOR_PH , I +R27, ISUS , MIN, R25, SPECTROPHOTOMETER_NITRATE , I +R27, ISUS_V3 , MIN, R25, SPECTROPHOTOMETER_NITRATE , I +R27, KELLER_PA8 , MIN, R25, CTD_PRES , I +R27, KISTLER , MIN, R25, CTD_PRES , I +R27, KISTLER_10153PSIA , MIN, R25, CTD_PRES , I +R27, KISTLER_2900PSIA , MIN, R25, CTD_PRES , I +R27, MCOMS_FLBB2 , MIN, R25, FLUOROMETER_CHLA , I +R27, MCOMS_FLBBCD , MIN, R25, FLUOROMETER_CHLA , I +R27, MCOMS_FLBBCD , MIN, R25, FLUOROMETER_CDOM , I +R27, MENSOR , MIN, R25, CTD_PRES , I +R27, MP40_C_2000_G , MIN, R25, CTD_PRES , I +R27, OPUS_DS , MIN, R25, SPECTROPHOTOMETER_NITRATE , I +R27, OPUS_DS , MIN, R25, SPECTROPHOTOMETER_BISULFIDE , I +R27, PAINE , MIN, R25, CTD_PRES , I +R27, PAINE_1500PSIA , MIN, R25, CTD_PRES , I +R27, PAINE_1600PSIA , MIN, R25, CTD_PRES , I +R27, PAINE_2000PSIA , MIN, R25, CTD_PRES , I +R27, PAINE_2900PSIA , MIN, R25, CTD_PRES , I +R27, PAINE_3000PSIA , MIN, R25, CTD_PRES , I +R27, PAL_UW , MIN, R25, ACOUSTIC , I +R27, RAFOS , MIN, R25, ACOUSTIC_GEOLOCATION , I +R27, RAMSES_ACC , MIN, R25, RADIOMETER_PAR , I +R27, RBR , MIN, R25, CTD_PRES , I +R27, RBR , MIN, R25, CTD_TEMP , I +R27, RBR , MIN, R25, CTD_CNDC , I +R27, RBR_ARGO , MIN, R25, CTD_PRES , I +R27, RBR_ARGO , MIN, R25, CTD_TEMP , I +R27, RBR_ARGO , MIN, R25, CTD_CNDC , I +R27, RBR_ARGO3 , MIN, R25, CTD_PRES , I +R27, RBR_ARGO3 , MIN, R25, CTD_TEMP , I +R27, RBR_ARGO3 , MIN, R25, CTD_CNDC , I +R27, RBR_ARGO3_DEEP4K , MIN, R25, CTD_PRES , I +R27, RBR_ARGO3_DEEP4K , MIN, R25, CTD_TEMP , I +R27, RBR_ARGO3_DEEP4K , MIN, R25, CTD_CNDC , I +R27, RBR_ARGO3_DEEP6K , MIN, R25, CTD_PRES , I +R27, RBR_ARGO3_DEEP6K , MIN, R25, CTD_TEMP , I +R27, RBR_ARGO3_DEEP6K , MIN, R25, CTD_CNDC , I +R27, RBR_CODA_T_ODO , MIN, R25, OPTODE_DOXY , I +R27, RBR_PRES , MIN, R25, CTD_PRES , I +R27, RBR_PRES_A , MIN, R25, CTD_PRES , I +R27, SATLANTIC_OCR504_ICSW , MIN, R25, RADIOMETER_PAR , I +R27, SATLANTIC_OCR507_ICSW , MIN, R25, RADIOMETER_PAR , I +R27, SATLANTIC_OCR507_ICSWR10W , MIN, R25, RADIOMETER_PAR , I +R27, SATLANTIC_PAR , MIN, R25, RADIOMETER_PAR , I +R27, SBE , MIN, R25, CTD_PRES , I +R27, SBE , MIN, R25, CTD_TEMP , I +R27, SBE , MIN, R25, CTD_CNDC , I +R27, SBE_STS , MIN, R25, STS_CNDC , I +R27, SBE_STS , MIN, R25, STS_TEMP , I +R27, SBE37 , MIN, R25, CTD_PRES , I +R27, SBE37 , MIN, R25, CTD_TEMP , I +R27, SBE37 , MIN, R25, CTD_CNDC , I +R27, SBE41 , MIN, R25, CTD_PRES , I +R27, SBE41 , MIN, R25, CTD_TEMP , I +R27, SBE41 , MIN, R25, CTD_CNDC , I +R27, SBE41_IDO , MIN, R25, CTD_PRES , I +R27, SBE41_IDO , MIN, R25, CTD_TEMP , I +R27, SBE41_IDO , MIN, R25, CTD_CNDC , I +R27, SBE41_IDO , MIN, R25, IDO_DOXY , I +R27, SBE41CP , MIN, R25, CTD_PRES , I +R27, SBE41CP , MIN, R25, CTD_TEMP , I +R27, SBE41CP , MIN, R25, CTD_CNDC , I +R27, SBE41CP_IDO , MIN, R25, CTD_PRES , I +R27, SBE41CP_IDO , MIN, R25, CTD_TEMP , I +R27, SBE41CP_IDO , MIN, R25, CTD_CNDC , I +R27, SBE41CP_IDO , MIN, R25, IDO_DOXY , I +R27, SBE41N , MIN, R25, CTD_PRES , I +R27, SBE41N , MIN, R25, CTD_TEMP , I +R27, SBE41N , MIN, R25, CTD_CNDC , I +R27, SBE43_IDO , MIN, R25, IDO_DOXY , I +R27, SBE43F_IDO , MIN, R25, IDO_DOXY , I +R27, SBE43I , MIN, R25, IDO_DOXY , I +R27, SBE61 , MIN, R25, CTD_PRES , I +R27, SBE61 , MIN, R25, CTD_TEMP , I +R27, SBE61 , MIN, R25, CTD_CNDC , I +R27, SBE63_OPTODE , MIN, R25, OPTODE_DOXY , I +R27, SBE83_OPTODE , MIN, R25, OPTODE_DOXY , I +R27, SEAFET , MIN, R25, TRANSISTOR_PH , I +R27, SEAPOINT_TURBIDITY_METER , MIN, R25, BACKSCATTERINGMETER_TURBIDITY , I +R27, SEASCAN_SSTD , MIN, R25, CTD_PRES , I +R27, SUNA , MIN, R25, SPECTROPHOTOMETER_NITRATE , I +R27, SUNA , MIN, R25, SPECTROPHOTOMETER_BISULFIDE , I +R27, SUNA_V2 , MIN, R25, SPECTROPHOTOMETER_NITRATE , I +R27, SUNA_V2 , MIN, R25, SPECTROPHOTOMETER_BISULFIDE , I \ No newline at end of file diff --git a/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_2.txt b/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_2.txt new file mode 100644 index 000000000..dd79316c2 --- /dev/null +++ b/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_2.txt @@ -0,0 +1,25 @@ +R27, ECO_BB3 , MIN, R25, BACKSCATTERINGMETER_BBP470 , I +R27, ECO_FLBB2 , MIN, R25, BACKSCATTERINGMETER_BBP470 , I +R27, ECO_BB3 , MIN, R25, BACKSCATTERINGMETER_BBP532 , I +R27, ECO_FLBB2 , MIN, R25, BACKSCATTERINGMETER_BBP532 , I +R27, MCOMS_FLBB2 , MIN, R25, BACKSCATTERINGMETER_BBP532 , I +R27, ECO_BB3 , MIN, R25, BACKSCATTERINGMETER_BBP700 , I +R27, ECO_FLBB , MIN, R25, BACKSCATTERINGMETER_BBP700 , I +R27, ECO_FLBB2 , MIN, R25, BACKSCATTERINGMETER_BBP700 , I +R27, ECO_FLBB_2K , MIN, R25, BACKSCATTERINGMETER_BBP700 , I +R27, ECO_FLBB_AP2 , MIN, R25, BACKSCATTERINGMETER_BBP700 , I +R27, ECO_FLBBCD , MIN, R25, BACKSCATTERINGMETER_BBP700 , I +R27, ECO_FLBBCD_AP2 , MIN, R25, BACKSCATTERINGMETER_BBP700 , I +R27, ECO_FLBBFL , MIN, R25, BACKSCATTERINGMETER_BBP700 , I +R27, ECO_FLBBFL_AP2 , MIN, R25, BACKSCATTERINGMETER_BBP700 , I +R27, MCOMS_FLBB2 , MIN, R25, BACKSCATTERINGMETER_BBP700 , I +R27, MCOMS_FLBBCD , MIN, R25, BACKSCATTERINGMETER_BBP700 , I +R27, SATLANTIC_OCR504_ICSW , MIN, R25, RADIOMETER_DOWN_IRR380 , I +R27, SATLANTIC_OCR504_ICSW , MIN, R25, RADIOMETER_DOWN_IRR412 , I +R27, SATLANTIC_OCR504_ICSW , MIN, R25, RADIOMETER_DOWN_IRR443 , I +R27, SATLANTIC_OCR504_ICSW , MIN, R25, RADIOMETER_DOWN_IRR490 , I +R27, SATLANTIC_OCR504_ICSW , MIN, R25, RADIOMETER_DOWN_IRR555 , I +R27, SATLANTIC_OCR504_R10W , MIN, R25, RADIOMETER_UP_RAD412 , I +R27, SATLANTIC_OCR504_R10W , MIN, R25, RADIOMETER_UP_RAD443 , I +R27, SATLANTIC_OCR504_R10W , MIN, R25, RADIOMETER_UP_RAD490 , I +R27, SATLANTIC_OCR504_R10W , MIN, R25, RADIOMETER_UP_RAD555 , I diff --git a/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_2b.txt b/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_2b.txt new file mode 100644 index 000000000..3b7098ff8 --- /dev/null +++ b/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_2b.txt @@ -0,0 +1 @@ +R27, RAMSES_ACC, MIN, R25,RADIOMETER_DOWN_IRR , I \ No newline at end of file diff --git a/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_3.txt b/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_3.txt new file mode 100644 index 000000000..8358155ff --- /dev/null +++ b/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_3.txt @@ -0,0 +1 @@ +R27, DRUCK_10153PSIA , MIN, R25, CTD_PRES , I \ No newline at end of file diff --git a/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_3b.txt b/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_3b.txt new file mode 100644 index 000000000..2ae333bb5 --- /dev/null +++ b/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_3b.txt @@ -0,0 +1,10 @@ +R27, DSB301-10-C85 , MIN, R25, CTD_PRES , I +R27, OCR504_ICSW , MIN, R25, RADIOMETER_DOWN_IRR380 , I +R27, OCR504_ICSW , MIN, R25, RADIOMETER_DOWN_IRR412 , I +R27, OCR504_ICSW , MIN, R25, RADIOMETER_DOWN_IRR443 , I +R27, OCR504_ICSW , MIN, R25, RADIOMETER_DOWN_IRR490 , I +R27, OCR504_ICSW , MIN, R25, RADIOMETER_DOWN_IRR555 , I +R27, OCR504_R10W , MIN, R25, RADIOMETER_UP_RAD412 , I +R27, OCR504_R10W , MIN, R25, RADIOMETER_UP_RAD443 , I +R27, OCR504_R10W , MIN, R25, RADIOMETER_UP_RAD490 , I +R27, OCR504_R10W , MIN, R25, RADIOMETER_UP_RAD555 , I \ No newline at end of file diff --git a/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_4_cndc.txt b/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_4_cndc.txt new file mode 100644 index 000000000..a5012c2dc --- /dev/null +++ b/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_4_cndc.txt @@ -0,0 +1,42 @@ +R27, SBE41_V2.5, MIN, R25,CTD_CNDC, I +R27, SBE41_V2.6, MIN, R25,CTD_CNDC, I +R27, SBE41_V3, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V1, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V1.1, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V1.2, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V1.2a, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V1.3, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V1.3b, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V1.4, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V1.5, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V1.7, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V1.8, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V1.9, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V1.9a, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V2, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V3, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V3.0a, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V3.0c, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V4.4.0, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V5.0.1, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V5.3.0, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V5.3.1, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V5.3.2, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V7.2.3, MIN, R25,CTD_CNDC, I +R27, SBE41CP_V7.2.5, MIN, R25,CTD_CNDC, I +R27, SBE41N_V5.3.0, MIN, R25,CTD_CNDC, I +R27, SBE41N_V5.3.4, MIN, R25,CTD_CNDC, I +R27, SBE41N_V5.4.0, MIN, R25,CTD_CNDC, I +R27, SBE61_V4.5.2, MIN, R25,CTD_CNDC, I +R27, SBE61_V4.5.3, MIN, R25,CTD_CNDC, I +R27, SBE61_V5.0.0, MIN, R25,CTD_CNDC, I +R27, SBE61_V5.0.1, MIN, R25,CTD_CNDC, I +R27, SBE61_V5.0.10, MIN, R25,CTD_CNDC, I +R27, SBE61_V5.0.12, MIN, R25,CTD_CNDC, I +R27, SBE61_V5.0.2, MIN, R25,CTD_CNDC, I +R27, SBE61_V5.0.3, MIN, R25,CTD_CNDC, I +R27, SBE61_V5.0.9, MIN, R25,CTD_CNDC, I +R27, SBE41_IDO_V1.0c, MIN, R25,CTD_CNDC, I +R27, SBE41_IDO_V2.0, MIN, R25,CTD_CNDC, I +R27, SBE41_IDO_V3.0, MIN, R25,CTD_CNDC, I +R27, SBE41CP_IDO_V2.0b, MIN, R25,CTD_CNDC, I \ No newline at end of file diff --git a/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_4_ido_doxy.txt b/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_4_ido_doxy.txt new file mode 100644 index 000000000..9f41276bc --- /dev/null +++ b/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_4_ido_doxy.txt @@ -0,0 +1,4 @@ +R27, SBE41_IDO_V1.0c, MIN, R25,IDO_DOXY, I +R27, SBE41_IDO_V2.0, MIN, R25,IDO_DOXY, I +R27, SBE41_IDO_V3.0, MIN, R25,IDO_DOXY, I +R27, SBE41CP_IDO_V2.0b, MIN, R25,IDO_DOXY, I \ No newline at end of file diff --git a/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_4_pres.txt b/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_4_pres.txt new file mode 100644 index 000000000..765950382 --- /dev/null +++ b/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_4_pres.txt @@ -0,0 +1,42 @@ +R27, SBE41_V2.5, MIN, R25,CTD_PRES, I +R27, SBE41_V2.6, MIN, R25,CTD_PRES, I +R27, SBE41_V3, MIN, R25,CTD_PRES, I +R27, SBE41CP_V1, MIN, R25,CTD_PRES, I +R27, SBE41CP_V1.1, MIN, R25,CTD_PRES, I +R27, SBE41CP_V1.2, MIN, R25,CTD_PRES, I +R27, SBE41CP_V1.2a, MIN, R25,CTD_PRES, I +R27, SBE41CP_V1.3, MIN, R25,CTD_PRES, I +R27, SBE41CP_V1.3b, MIN, R25,CTD_PRES, I +R27, SBE41CP_V1.4, MIN, R25,CTD_PRES, I +R27, SBE41CP_V1.5, MIN, R25,CTD_PRES, I +R27, SBE41CP_V1.7, MIN, R25,CTD_PRES, I +R27, SBE41CP_V1.8, MIN, R25,CTD_PRES, I +R27, SBE41CP_V1.9, MIN, R25,CTD_PRES, I +R27, SBE41CP_V1.9a, MIN, R25,CTD_PRES, I +R27, SBE41CP_V2, MIN, R25,CTD_PRES, I +R27, SBE41CP_V3, MIN, R25,CTD_PRES, I +R27, SBE41CP_V3.0a, MIN, R25,CTD_PRES, I +R27, SBE41CP_V3.0c, MIN, R25,CTD_PRES, I +R27, SBE41CP_V4.4.0, MIN, R25,CTD_PRES, I +R27, SBE41CP_V5.0.1, MIN, R25,CTD_PRES, I +R27, SBE41CP_V5.3.0, MIN, R25,CTD_PRES, I +R27, SBE41CP_V5.3.1, MIN, R25,CTD_PRES, I +R27, SBE41CP_V5.3.2, MIN, R25,CTD_PRES, I +R27, SBE41CP_V7.2.3, MIN, R25,CTD_PRES, I +R27, SBE41CP_V7.2.5, MIN, R25,CTD_PRES, I +R27, SBE41N_V5.3.0, MIN, R25,CTD_PRES, I +R27, SBE41N_V5.3.4, MIN, R25,CTD_PRES, I +R27, SBE41N_V5.4.0, MIN, R25,CTD_PRES, I +R27, SBE61_V4.5.2, MIN, R25,CTD_PRES, I +R27, SBE61_V4.5.3, MIN, R25,CTD_PRES, I +R27, SBE61_V5.0.0, MIN, R25,CTD_PRES, I +R27, SBE61_V5.0.1, MIN, R25,CTD_PRES, I +R27, SBE61_V5.0.10, MIN, R25,CTD_PRES, I +R27, SBE61_V5.0.12, MIN, R25,CTD_PRES, I +R27, SBE61_V5.0.2, MIN, R25,CTD_PRES, I +R27, SBE61_V5.0.3, MIN, R25,CTD_PRES, I +R27, SBE61_V5.0.9, MIN, R25,CTD_PRES, I +R27, SBE41_IDO_V1.0c, MIN, R25,CTD_PRES, I +R27, SBE41_IDO_V2.0, MIN, R25,CTD_PRES, I +R27, SBE41_IDO_V3.0, MIN, R25,CTD_PRES, I +R27, SBE41CP_IDO_V2.0b, MIN, R25,CTD_PRES, I \ No newline at end of file diff --git a/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_4_temp.txt b/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_4_temp.txt new file mode 100644 index 000000000..05430e673 --- /dev/null +++ b/argopy/static/assets/nvs_R25_R27/NVS_R25_R27_mappings_4_temp.txt @@ -0,0 +1,42 @@ +R27, SBE41_V2.5, MIN, R25,CTD_TEMP, I +R27, SBE41_V2.6, MIN, R25,CTD_TEMP, I +R27, SBE41_V3, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V1, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V1.1, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V1.2, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V1.2a, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V1.3, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V1.3b, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V1.4, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V1.5, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V1.7, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V1.8, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V1.9, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V1.9a, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V2, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V3, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V3.0a, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V3.0c, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V4.4.0, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V5.0.1, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V5.3.0, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V5.3.1, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V5.3.2, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V7.2.3, MIN, R25,CTD_TEMP, I +R27, SBE41CP_V7.2.5, MIN, R25,CTD_TEMP, I +R27, SBE41N_V5.3.0, MIN, R25,CTD_TEMP, I +R27, SBE41N_V5.3.4, MIN, R25,CTD_TEMP, I +R27, SBE41N_V5.4.0, MIN, R25,CTD_TEMP, I +R27, SBE61_V4.5.2, MIN, R25,CTD_TEMP, I +R27, SBE61_V4.5.3, MIN, R25,CTD_TEMP, I +R27, SBE61_V5.0.0, MIN, R25,CTD_TEMP, I +R27, SBE61_V5.0.1, MIN, R25,CTD_TEMP, I +R27, SBE61_V5.0.10, MIN, R25,CTD_TEMP, I +R27, SBE61_V5.0.12, MIN, R25,CTD_TEMP, I +R27, SBE61_V5.0.2, MIN, R25,CTD_TEMP, I +R27, SBE61_V5.0.3, MIN, R25,CTD_TEMP, I +R27, SBE61_V5.0.9, MIN, R25,CTD_TEMP, I +R27, SBE41_IDO_V1.0c, MIN, R25,CTD_TEMP, I +R27, SBE41_IDO_V2.0, MIN, R25,CTD_TEMP, I +R27, SBE41_IDO_V3.0, MIN, R25,CTD_TEMP, I +R27, SBE41CP_IDO_V2.0b, MIN, R25,CTD_TEMP, I \ No newline at end of file diff --git a/argopy/static/assets/nvs_R25_R27/README.md b/argopy/static/assets/nvs_R25_R27/README.md new file mode 100644 index 000000000..55c3a1888 --- /dev/null +++ b/argopy/static/assets/nvs_R25_R27/README.md @@ -0,0 +1,6 @@ +# Sensor models and types + +This folder hold mapping data to get R25 sensor types from R27 sensor models. + +Relevant issues from ADMT/AVTT: +- https://github.com/OneArgo/ArgoVocabs/issues/156 diff --git a/argopy/utils/accessories.py b/argopy/utils/accessories.py index 3a7624839..88321c0ab 100644 --- a/argopy/utils/accessories.py +++ b/argopy/utils/accessories.py @@ -3,8 +3,11 @@ import warnings import logging import copy +import pandas as pd +from typing import ClassVar +from dataclasses import dataclass -from .checkers import check_wmo, is_wmo +from argopy.utils.checkers import check_wmo, is_wmo log = logging.getLogger("argopy.utils.accessories") @@ -299,3 +302,92 @@ def _is_valid(self, value: str) -> bool: def __repr__(self): return ", ".join(self.possible_values) + + +@dataclass +class NVSrow: + """This proto makes it easier to work with a single NVS table row from a :class:`pd.DataFrame` + + It will turn :class:`pd.DataFrame` columns into class attributes + + Examples + -------- + .. code-block:: python + :caption: Use this prototype to create a NVS table row class + + class SensorType(NVSrow): + reftable = "R25" + @staticmethod + def from_series(obj: pd.Series) -> "SensorType": + return SensorType(obj) + + .. code-block:: python + :caption: Then use the row class + + from argopy import ArgoNVSReferenceTables + + df = ArgoNVSReferenceTables().tbl(25) + row = df[df["altLabel"].apply(lambda x: x == 'CTD')].iloc[0] + + st = SensorType.from_series(row) + + st.name + st.long_name + st.definition + st.deprecated + st.uri + + """ + name: str = "" + """From 'altLabel' column""" + + long_name: str = "" + """From 'prefLabel' column""" + + definition: str = "" + """From 'definition' column""" + + uri: str = "" + """From 'ID' column, typically a link toward NVS specific row entry""" + + urn: str = "" + """From 'urn' column""" + + deprecated: bool = None + """From 'deprecated' column""" + + reftable: ClassVar[str] + """Reference table this row is based on""" + + def __init__(self, row: pd.Series | pd.DataFrame): + if not isinstance(row, pd.Series) and isinstance(row, pd.DataFrame): + row = row.iloc[0] + row = row.to_dict() + self.name = row["altLabel"] + self.long_name = row["prefLabel"] + self.definition = row["definition"] + self.deprecated = row["deprecated"] + self.uri = row["id"] + self.urn = row["urn"] + + @staticmethod + def from_series(obj: pd.Series) -> "NVSrow": + return NVSrow(obj) + + @staticmethod + def from_df(df: pd.DataFrame, txt: str, column: str = 'altLabel') -> "NVSrow": + row = df[df[column].apply(lambda x: str(x) == str(txt))].iloc[0] + return NVSrow(row) + + def __eq__(self, obj): + return self.name == obj + + def __repr__(self): + summary = [f"<{getattr(self, 'reftable', 'n/a')}.{self.urn}>"] + summary.append(f"%12s: {self.name}" % "name") + summary.append(f"%12s: {self.long_name}" % "long_name") + summary.append(f"%12s: {self.urn}" % "urn") + summary.append(f"%12s: {self.uri}" % "uri") + summary.append(f"%12s: {self.deprecated}" % "deprecated") + summary.append("%12s: %s" % ("definition", self.definition)) + return "\n".join(summary) diff --git a/argopy/utils/format.py b/argopy/utils/format.py index 7bea675e4..5c2b125b6 100644 --- a/argopy/utils/format.py +++ b/argopy/utils/format.py @@ -524,3 +524,47 @@ def _mono2multi(mono_path): new_uri = [_mono2multi(uri)[2:] for uri in flist] new_uri = list(set(new_uri)) return new_uri + + +def urnparser(urn): + """Parsing RFC 8141 compliant uniform resource names (URN) from NVS + SDN stands for SeaDataNet + """ + pp = urn.split(":") + if len(pp) == 4 and pp[0] == 'SDN': + return {'listid': pp[1], 'version': pp[2], 'termid': pp[3]} + else: + raise ValueError(f"This NVS URN '{urn}' does not follow the pattern: 'SDN:listid:version:termid' or 'SDN:listid::termid' for NVS2.0") + + +def ppliststr(l: list[str], last : str = 'and', n : int | None = None) -> str: + """Pretty print a list of strings + + Examples + -------- + .. code-block:: python + + ppliststr(['a', 'b', 'c', 'd']) -> "'a', 'b', 'c' and 'd'" + ppliststr(['a', 'b'], last='or') -> "'a' or 'b'" + ppliststr(['a', 'b', 'c', 'd'], n=3) -> "'a', 'b', 'c' and more ..." + + """ + n = n if n is not None else len(l) + if n == 0: + return "" + + s, ii, m = "", 0, len(l) + while ii < m: + item = l[ii] + if ii == n: + s += f" {last} more ..." + break + if ii == 0: + s += f"'{item}'" + elif ii == len(l) - 1: + s += f" {last} '{item}'" + else: + s += f", '{item}'" + ii += 1 + return s +