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