Coverage for /home/casatest/venv/lib/python3.12/site-packages/casatasks/private/task_getantposalma.py: 13%

95 statements  

« prev     ^ index     » next       coverage.py v7.10.4, created at 2025-08-21 07:43 +0000

1from casatasks import casalog 

2from casatools import quanta 

3import certifi 

4from datetime import datetime 

5import json, os, shutil 

6import ssl 

7from urllib import request 

8from urllib.error import HTTPError, URLError 

9from urllib.parse import urlencode, urlparse 

10 

11 

12def _is_valid_url_host(url): 

13 parsed = urlparse(url) 

14 return bool(parsed.netloc) 

15 

16 

17def _query(url): 

18 myjson = None 

19 response = None 

20 try: 

21 context = ssl.create_default_context(cafile=certifi.where()) 

22 with request.urlopen(url, context=context, timeout=400) as response: 

23 if response.status == 200: 

24 myjson = response.read().decode('utf-8') 

25 except HTTPError as e: 

26 casalog.post( 

27 f"Caught HTTPError: {e.code} {e.reason}: {e.read().decode('utf-8')}", 

28 "WARN" 

29 ) 

30 except URLError as e: 

31 casalog.post(f"Caught URLError: {str(e)}", "WARN") 

32 except Exception as e: 

33 casalog.post(f"Caught Exception when trying to connect: {str(e)}", "WARN") 

34 return myjson 

35 

36 

37def getantposalma( 

38 outfile='', overwrite=False, asdm='', tw='', snr="default", search='both_latest', 

39 hosts=['tbd1.alma.cl', 'tbd2.alma.cl'] 

40): 

41 r""" 

42Retrieve antenna positions by querying ALMA web service. 

43 

44[`Description`_] [`Examples`_] [`Development`_] [`Details`_] 

45 

46 

47Parameters 

48 - outfile_ (path='') - Name of output file to which to write retrieved antenna positions. 

49 - overwrite_ (bool=False) - Overwrite a file by the same name if it exists? 

50 - asdm_ (string='') - The associated ASDM name. Must be specified 

51 - tw_ (string='') - Optional time window in which to consider baseline measurements in the database, when calculating the antenna positions. 

52 - snr_ (float=0) - Optional signal-to-noise. 

53 - search_ (string='both_latest') - Search algorithm to use. 

54 - hosts_ (stringVec=['https://asa.alma.cl/uncertainties-service/uncertainties/versions/last/measurements/casa/']) - Priority-ranked list of hosts to query. 

55 

56 

57 

58 

59.. _Description: 

60 

61Description 

62 

63.. warning:: **WARNING**: This task should be considered experimental 

64 since the values returned by the JAO service are in the process of 

65 being validated. 

66 

67This task retrieves ALMA antenna positions via a web service which runs 

68on an ALMA-hosted server. The antenna positions are with respect to ITRF. 

69The user must specify the value of the outfile parameter. This parameter 

70is the name of the file to which the antenna positions will be written. 

71This file can then be read by gencal so that it can use the most up to 

72date antenna positions for the observation. 

73 

74The web service is described by the server development team and can be 

75found `at this location <https://asw.alma.cl/groups/ASW/-/packages/843>`__.  

76 

77The input parameters are discussed in detail below. 

78 

79outfile is required to be specified. It is the name of the file to which to 

80write antenna positions. 

81 

82overwrite If False and a file with the same name exists, and exception 

83will be thrown. If true, an existing file with the same name will be 

84overwriten. 

85 

86asdm is required to be specified. It is the associated ASDM UID in the 

87form uid://A002/Xc02418/X29c8.  

88 

89tw is an optional parameter. It is time window in which the antenna positions 

90are required, specified as a comma separated pair. Times are UTC and are 

91expressed in YY-MM-DDThh:mm:ss.sss format. The end time must be later than 

92the begin time. 

93 

94snr is an optional parameter. If changed from the default value "default",  

95it must be a nonnegative number representing the signal-to-noise ratio. Antenna 

96positions which have corrections less than this value will not be written. 

97If not specified, the default snr as defined by the web service will be used. 

98The server side default value may change over time as determined by the server 

99side (non-CASA) team. As of this writing (March 2025), the web service team has 

100not provided publicly facing documentation on the details of how the default 

101value is chosen. The most recent information they have provided to us is that 

102the default value is 5.0. 

103 

104tw and search are optional parameters and are coupled as follows. search 

105indicates the search algorithm to use to find the desired antenna positions. 

106Supported values of this parameter at the time of writing are 'both_latest' 

107and 'both_closest'. The task passes the value of the search parameter verbatim to 

108the web service, meaning that users can take advantage of new search algorithms 

109as the web service team brings them online. The default algorithm used is 

110'both_latest'. In general, the search is limited in time to the specified 

111value of tw (time window). However, in the case that tw is not specified, the 

112following rules apply. For 'both_latest', the last updated position for each 

113antenna within the specified time window, or, if tw is not specified, within 

11430 days after the observation will be returned, taking into account snr if 

115specified, if provided. 

116 

117For 'both_closest', if tw is not specified, the position 

118of each antenna closest in time to the observation, within 30 days (before 

119or after the observation) will be returned, subject to the value of snr if it 

120is specified.  

121 

122hosts is a required parameter. It is a list of hosts to query, in order of 

123priority, to obtain positions. The first server to respond with a valid result is 

124the only one that is used. That response will be written and no additional 

125hosts will be queried. 

126 

127The format of the returned file is a two element list encoded in json. The first 

128element is a stringfied dictionary that contains antenna names as keys, with each 

129value being a three element list of x, y, and z coordinates in ITRF. The second 

130element is a dictionary containing various (possibly helpful) metadata that were 

131used when the task was run. The following code may be used to load these data 

132structures into python variables. 

133  

134 :: 

135  

136 import ast, json 

137 ... 

138 with open("outfile.json", "r") as f: 

139 antpos_str, md_dict = json.load(f) 

140 antpos_dict = ast.literal_eval(antpos_str) 

141 

142 

143.. _Examples: 

144 

145Examples 

146 Get antenna positions which have positions with a signal-to-noise ratio 

147 greater than 5. 

148  

149 :: 

150  

151 getantposalma( 

152 outfile='my_ant_pos.json', asdm='valid ASDM name here', snr=5, 

153 hosts=['tbd1.alma.cl', 'tbd2.alma.cl'] 

154 ) 

155  

156 

157.. _Development: 

158 

159Development 

160 No additional development details 

161 

162 

163 

164 

165.. _Details: 

166 

167 

168Parameter Details 

169 Detailed descriptions of each function parameter 

170 

171.. _outfile: 

172 

173| ``outfile (path='')`` - Name of output file to which to write antenna positions. If a file by this name already exists, it will be silently overwritten. The written file will be in JSON format. 

174| default: none 

175| Example: outfile='my_alma_antenna_positions.json' 

176 

177.. _overwrite: 

178 

179| ``overwrite (bool=False)`` - Overwrite a file by the same name if it exists? If False and a file 

180| with the same name exists, and exception will be thrown. 

181 

182.. _asdm: 

183 

184| ``asdm (string='')`` - The associated ASDM name. Must be specified. The ASDM is not required to be on the file system; its value is simply passed to the web service. 

185| default: '' 

186| Example:asdm='uid://A002/X10ac6bc/X896d' 

187 

188.. _tw: 

189 

190| ``tw (string='')`` - Optional time window in which to consider baseline measurements in the database, when calculating the antenna positions. Format is of the form begin_time,end_time, where times must be specified in YYYY-MM-DDThh:mm:ss.sss format and end_time must be later than begin time. Times should be UTC. 

191| Example: tw='2023-03-14T00:40:20,2023-03-20T17:58:20' 

192 

193.. _snr: 

194 

195| ``snr (float=0)`` - Optional signal-to-noise. Antenna positions which have corrections with S/N less than this value will not be retrieved nor written. If not specified, positions of all antennas will be written. 

196| default: 0 (no snr constraint will be used)  

197| Example: snr=5.0 

198 

199.. _search: 

200 

201| ``search (string='both_latest')`` - Search algorithm to use. Supported values are "both_latest" and "both_closest". For "both_latest", the last updated position for each antenna within 30 days after the observation will be returned, taking into account snr if specified. If provided, tw will override the 30 day default value. For "both_closest", the position of each antenna closest in time to the observation, within 30 days (before or after the observation) will be returned, subject to the value of snr if it is specified. If specified, the value of tw will override the default 30 days. The default algorithm to use will be "both_latest". 

202| Example: search="both_closest" 

203 

204.. _hosts: 

205 

206| ``hosts (stringVec=['https://asa.alma.cl/uncertainties-service/uncertainties/versions/last/measurements/casa/'])`` - Priority-ranked list of hosts to query to obtain positions. Only one server that returns a list of antenna positions is required. That response will be written and no additional hosts will be queried. 

207| Example: hosts=["server1.alma.cl", "server2.alma.cl"] 

208 

209 

210 """ 

211 if not outfile: 

212 raise ValueError("Parameter outfile must be specified") 

213 md = { 

214 "caltype": "ALMA antenna positions", 

215 "description": "ALMA ITRF antenna positions in meters", 

216 "product_code": "antposalma", 

217 "outfile": outfile 

218 } 

219 if not overwrite and os.path.exists(outfile): 

220 raise RuntimeError( 

221 f"A file or directory named {outfile} already exists and overwrite " 

222 "is False, so exiting. Either rename the existing file or directory, " 

223 "change the value of overwrite to True, or both." 

224 ) 

225 if not hosts: 

226 raise ValueError("Parameter hosts must be specified") 

227 if isinstance(hosts, list) and not hosts[0]: 

228 raise ValueError("The first element of the hosts list must be specified") 

229 md["hosts"] = hosts 

230 _qa = quanta() 

231 parms = {} 

232 if asdm: 

233 parms['asdm'] = asdm 

234 else: 

235 raise ValueError("parameter asdm must be specified") 

236 if tw: 

237 z = tw.split(",") 

238 if len(z) != 2: 

239 raise ValueError( 

240 "Parameter tw should contain exactly one comma that separates two times" 

241 ) 

242 s0, s1 = z 

243 msg = "The correct format is of the form YYYY-MM-DDThh:mm:ss." 

244 try: 

245 t_start = _qa.quantity(_qa.time(s0, form="fits")[0]) 

246 except Exception as e: 

247 raise ValueError(f"Begin time {s0} does not appear to have a valid format. {msg}") 

248 try: 

249 t_end = _qa.quantity(_qa.time(s1, form="fits")[0]) 

250 except Exception as e: 

251 raise ValueError(f"End time {s1} does not appear to have a valid format. {msg}") 

252 if _qa.ge(t_start, t_end): 

253 raise ValueError( 

254 f"Parameter tw, start time ({z[0]}) must be less than end time ({z[1]})." 

255 ) 

256 parms["tw"] = tw 

257 if isinstance(snr, str): 

258 if snr != "default": 

259 raise ValueError("If snr is a string, it's only permissible value is 'default'") 

260 elif snr < 0.0: 

261 raise ValueError(f"If a number, parameter snr ({snr}) must be non-negative.") 

262 elif snr >= 0.0: 

263 parms["snr"] = snr 

264 if search: 

265 parms['search'] = search 

266 qs = f"?{urlencode(parms)}" 

267 md.update(parms) 

268 antpos = None 

269 for h in hosts: 

270 if not _is_valid_url_host(h): 

271 raise ValueError( 

272 f'Parameter hosts: {h} is not a valid host expressed as a URL.' 

273 ) 

274 url = f"{h}/{qs}" 

275 casalog.post(f"Trying {url} ...", "NORMAL") 

276 antpos = _query(url) 

277 if antpos: 

278 md["successful_url"] = url 

279 antpos = json.loads(antpos) 

280 break 

281 if not antpos: 

282 raise RuntimeError("All URLs failed to return an antenna position list.") 

283 if os.path.exists(outfile): 

284 if overwrite: 

285 if os.path.isdir(outfile): 

286 casalog.post( 

287 f"Removing existing directory {outfile} before writing new " 

288 "file of same name", 

289 "WARN" 

290 ) 

291 shutil.rmtree(outfile) 

292 else: 

293 casalog.post( 

294 f"Removing existing file {outfile} before writing now file of " 

295 "same name", 

296 "WARN" 

297 ) 

298 os.remove(outfile) 

299 else: 

300 raise RuntimeError( 

301 "Logic Error: shouldn't have gotten to this point with overwrite=False" 

302 ) 

303 md["timestamp"] = str(datetime.now()) 

304 with open(outfile, "w") as f: 

305 json.dump({"data": antpos, "metadata": md}, f)