xopr.ops_api

This module provides functions to interact with part of the Open Polar Server (OPS) API that do not require authentication. It includes functions to retrieve segment IDs, layer points, and segment metadata.

  1"""
  2This module provides functions to interact with part of the Open Polar Server (OPS) API
  3that do not require authentication.
  4It includes functions to retrieve segment IDs, layer points, and segment metadata.
  5"""
  6
  7import base64
  8import json
  9import time
 10import urllib.parse
 11
 12import requests
 13
 14ops_base_url = "https://ops.cresis.ku.edu/ops"
 15
 16
 17def get_layer_points(segment_name : str, season_name : str, location=None, layer_names=None, include_geometry=True, raise_errors=True):
 18    """
 19    Get layer points for a segment from the OPS API.
 20
 21    Parameters
 22    ----------
 23    segment_name : str
 24        The segment name
 25    season_name : str
 26        The season name
 27    location : str, optional
 28        The location, either 'arctic' or 'antarctic'. If None, inferred from season_name.
 29    layer_names : list of str, optional
 30        List of layer names to retrieve. If None, retrieves all layers.
 31    include_geometry : bool, optional
 32        Whether to include geometry information in the response. Default is True.
 33
 34    Returns
 35    -------
 36    dict or None
 37        API response as JSON containing layer points data, or None if request fails.
 38
 39    Raises
 40    ------
 41    ValueError
 42        If neither segment_id nor both segment_name and season_name are provided.
 43    """
 44
 45    if location is None:
 46        if "antarctica" in season_name.lower():
 47            location = "antarctic"
 48        elif "greenland" in season_name.lower():
 49            location = "arctic"
 50        else:
 51            raise ValueError("Location could not be inferred from the season name. Please specify 'arctic' or 'antarctic' explicitly.")
 52
 53    data_payload = {
 54        "properties": {
 55            "location": location,
 56            "season": season_name,
 57            "segment": segment_name
 58        }
 59    }
 60
 61    if include_geometry:
 62        data_payload["properties"]["return_geom"] = 'geog'
 63
 64    # Add layer names if specified
 65    if layer_names:
 66        data_payload["properties"]["lyr_name"] = layer_names
 67
 68    return _ops_api_request(f"/get/layer/points", data_payload, request_type='POST')
 69
 70def get_layer_metadata():
 71    """
 72    Get the mapping of layer IDs to groups and names from the OPS API.
 73
 74    Returns
 75    -------
 76    dict or None
 77        API response as JSON containing layer metadata, or None if request fails.
 78    """
 79
 80    return _ops_api_request(f"/get/layers", {}, request_type='POST')
 81
 82def get_segment_metadata(segment_name : str, season_name : str):
 83    """
 84    Get segment metadata from the OPS API.
 85
 86    Parameters
 87    ----------
 88    segment_name : str, optional
 89        The segment name (alternative to segment_id).
 90    season_name : str, optional
 91        The season name (required if using segment_name).
 92
 93    Returns
 94    -------
 95    dict or None
 96        API response as JSON containing segment metadata, or None if request fails.
 97
 98    Raises
 99    ------
100    ValueError
101        If neither segment_id nor both segment_name and season_name are provided.
102    """
103
104    data_payload = {
105        "properties": {
106            "segment": segment_name,
107            "season": season_name
108        }
109    }
110
111    return _ops_api_request(f"/get/segment/metadata", data_payload)
112
113
114def _ops_api_request(path, data, request_type='POST', headers=None, base_url=ops_base_url, retries=3, job_timeout=200, debug=False, initial_retry_time=1):
115    """
116    Helper function to make a POST request to the OPS API.
117
118    Parameters
119    ----------
120    path : str
121        The API endpoint path.
122    data : dict
123        The data payload to send in the request.
124    headers : dict, optional
125        Additional headers to include in the request.
126    base_url : str, optional
127        The base URL for the OPS API. Default is ops_base_url.
128    retries : int, optional
129        Number of retry attempts for failed requests. Default is 3.
130
131    Returns
132    -------
133    dict or None
134        API response as JSON, or None if request fails after retries.
135    """
136
137    url = f"{base_url}/{path.lstrip('/')}"
138
139
140    # Encode the JSON data
141    encoded_data = urllib.parse.quote(json.dumps(data))
142    form_data = f"app=rds&data={encoded_data}"
143
144    # Create basic auth header for anonymous user
145    if headers is None:
146        credentials = base64.b64encode(b"anonymous:anonymous").decode("ascii")
147        headers = {
148            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
149            'Authorization': f'Basic {credentials}',
150            'Cookie': 'userName=anonymous; isAuthenticated=0'
151        }
152
153    try:
154        # Make the request
155        if request_type == 'POST':
156            if debug:
157                print(f"Making POST request to {url} with data: {form_data}")
158            response = requests.post(url, data=form_data, headers=headers)
159        elif request_type == 'GET':
160            if debug:
161                print(f"Making GET request to {url}")
162            response = requests.get(url, headers=headers)
163
164        # Check if request was successful
165        response.raise_for_status()
166
167        response_json = response.json()
168
169        if debug:
170            print(f"Received response with status: {response_json['status']}")
171
172        if response_json['status'] == 303: # Indicates that the request was processed as a background task
173            task_id = response_json['data']['task_id']
174            task_start_time = time.time()
175            if debug:
176                print(f"Task {task_id} started.")
177            while time.time() - task_start_time < job_timeout:
178                # Check the status of the background task
179                status_response = _ops_api_request(f"/get/status/{urllib.parse.quote(task_id)}", {}, request_type='GET', headers=headers, base_url=base_url, retries=retries)
180
181                if debug:
182                    print(f"Checking status for task {task_id}: {status_response}")
183
184                if status_response and status_response['status'] != 503:
185                    return status_response
186
187                time.sleep(initial_retry_time)  # Wait before retrying
188                initial_retry_time *= 2  # Exponential backoff
189
190            raise TimeoutError(f"Task {task_id} timed out after {job_timeout} seconds.")
191
192        return response_json
193
194    except requests.exceptions.HTTPError as e:
195        if debug:
196            print(f"HTTP error occurred: {response.status_code}")
197        if response.status_code == 504:
198            print(f"Gateway timeout occurred. Retrying in {initial_retry_time} seconds...")
199            time.sleep(initial_retry_time)
200            return _ops_api_request(path, data, request_type=request_type, headers=headers, base_url=base_url, retries=retries-1, initial_retry_time=initial_retry_time*2)
201        raise e
ops_base_url = 'https://ops.cresis.ku.edu/ops'
def get_layer_points( segment_name: str, season_name: str, location=None, layer_names=None, include_geometry=True, raise_errors=True):
18def get_layer_points(segment_name : str, season_name : str, location=None, layer_names=None, include_geometry=True, raise_errors=True):
19    """
20    Get layer points for a segment from the OPS API.
21
22    Parameters
23    ----------
24    segment_name : str
25        The segment name
26    season_name : str
27        The season name
28    location : str, optional
29        The location, either 'arctic' or 'antarctic'. If None, inferred from season_name.
30    layer_names : list of str, optional
31        List of layer names to retrieve. If None, retrieves all layers.
32    include_geometry : bool, optional
33        Whether to include geometry information in the response. Default is True.
34
35    Returns
36    -------
37    dict or None
38        API response as JSON containing layer points data, or None if request fails.
39
40    Raises
41    ------
42    ValueError
43        If neither segment_id nor both segment_name and season_name are provided.
44    """
45
46    if location is None:
47        if "antarctica" in season_name.lower():
48            location = "antarctic"
49        elif "greenland" in season_name.lower():
50            location = "arctic"
51        else:
52            raise ValueError("Location could not be inferred from the season name. Please specify 'arctic' or 'antarctic' explicitly.")
53
54    data_payload = {
55        "properties": {
56            "location": location,
57            "season": season_name,
58            "segment": segment_name
59        }
60    }
61
62    if include_geometry:
63        data_payload["properties"]["return_geom"] = 'geog'
64
65    # Add layer names if specified
66    if layer_names:
67        data_payload["properties"]["lyr_name"] = layer_names
68
69    return _ops_api_request(f"/get/layer/points", data_payload, request_type='POST')

Get layer points for a segment from the OPS API.

Parameters
  • segment_name (str): The segment name
  • season_name (str): The season name
  • location (str, optional): The location, either 'arctic' or 'antarctic'. If None, inferred from season_name.
  • layer_names (list of str, optional): List of layer names to retrieve. If None, retrieves all layers.
  • include_geometry (bool, optional): Whether to include geometry information in the response. Default is True.
Returns
  • dict or None: API response as JSON containing layer points data, or None if request fails.
Raises
  • ValueError: If neither segment_id nor both segment_name and season_name are provided.
def get_layer_metadata():
71def get_layer_metadata():
72    """
73    Get the mapping of layer IDs to groups and names from the OPS API.
74
75    Returns
76    -------
77    dict or None
78        API response as JSON containing layer metadata, or None if request fails.
79    """
80
81    return _ops_api_request(f"/get/layers", {}, request_type='POST')

Get the mapping of layer IDs to groups and names from the OPS API.

Returns
  • dict or None: API response as JSON containing layer metadata, or None if request fails.
def get_segment_metadata(segment_name: str, season_name: str):
 83def get_segment_metadata(segment_name : str, season_name : str):
 84    """
 85    Get segment metadata from the OPS API.
 86
 87    Parameters
 88    ----------
 89    segment_name : str, optional
 90        The segment name (alternative to segment_id).
 91    season_name : str, optional
 92        The season name (required if using segment_name).
 93
 94    Returns
 95    -------
 96    dict or None
 97        API response as JSON containing segment metadata, or None if request fails.
 98
 99    Raises
100    ------
101    ValueError
102        If neither segment_id nor both segment_name and season_name are provided.
103    """
104
105    data_payload = {
106        "properties": {
107            "segment": segment_name,
108            "season": season_name
109        }
110    }
111
112    return _ops_api_request(f"/get/segment/metadata", data_payload)

Get segment metadata from the OPS API.

Parameters
  • segment_name (str, optional): The segment name (alternative to segment_id).
  • season_name (str, optional): The season name (required if using segment_name).
Returns
  • dict or None: API response as JSON containing segment metadata, or None if request fails.
Raises
  • ValueError: If neither segment_id nor both segment_name and season_name are provided.