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.