Coverage for neddy/conesearch.py: 77%

118 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-09-20 10:57 +0000

1#!/usr/local/bin/python 

2# encoding: utf-8 

3""" 

4*Perform a conesearch on NED* 

5 

6:Author: 

7 David Young 

8""" 

9from __future__ import print_function 

10from __future__ import division 

11from neddy import _basesearch 

12import os 

13import sys 

14from future import standard_library 

15standard_library.install_aliases() 

16os.environ['TERM'] = 'vt100' 

17 

18 

19class conesearch(_basesearch): 

20 """ 

21 *The NED conesearch object* 

22 

23 **Key Arguments** 

24 

25 - ``log`` -- logger 

26 - ``ra`` -- ra 

27 - ``dec`` -- dec 

28 - ``radiusArcsec`` -- the search radius in arcsecs 

29 - ``nearestOnly`` -- return only the nearest object from NED 

30 - ``unclassified`` -- include the unclassified sources in the search results 

31 - ``quiet`` -- don't print to stdout 

32 - ``listOfCoordinates`` -- a list of `ra`, `dec`, and `radiusArcsec` (multiple sources) 

33 - ``outputFilePath`` -- path of file to print results to. Default *False* (don't print to file) 

34 - ``verbose`` -- return more metadata for matches 

35 - ``redshift`` -- require a redshift for a source to appear in search results 

36 

37 **Usage** 

38 

39 ```python 

40 from neddy import conesearch 

41 search = conesearch( 

42 log=log, 

43 ra=0.000, 

44 dec=0.000, 

45 radiusArcsec=5.0, 

46 nearestOnly=False, 

47 unclassified=True, 

48 outputFilePath=False, 

49 verbose=True, 

50 redshift=False 

51 ) 

52 results = search.get() 

53 ``` 

54 """ 

55 

56 def __init__( 

57 self, 

58 log, 

59 ra=False, 

60 dec=False, 

61 radiusArcsec=False, 

62 nearestOnly=False, 

63 unclassified=False, 

64 quiet=False, 

65 listOfCoordinates=False, 

66 outputFilePath=False, 

67 verbose=False, 

68 redshift=False 

69 ): 

70 self.log = log 

71 self.log.debug("instansiating a new 'conesearch' object") 

72 self.ra = ra 

73 self.dec = dec 

74 self.arcsec = radiusArcsec 

75 self.nearestOnly = nearestOnly 

76 self.unclassified = unclassified 

77 self.quiet = quiet 

78 self.listOfCoordinates = listOfCoordinates 

79 self.outputFilePath = outputFilePath 

80 self.verbose = verbose 

81 self.redshift = redshift 

82 

83 # VARIABLE DATA ATRRIBUTES 

84 from past.utils import old_div 

85 self.arcmin = old_div(float(self.arcsec), 60.) 

86 self.resultSpacing = 30 

87 

88 # CREATE A LIST IF SINGLE COORDINATES GIVEN 

89 if self.listOfCoordinates == False: 

90 self.listOfCoordinates = ["%(ra)s %(dec)s" % locals()] 

91 self._convert_coordinates_to_decimal_degrees() 

92 

93 return None 

94 

95 def get(self): 

96 """ 

97 *return results of a NED conesearch* 

98 """ 

99 self.log.debug('starting the ``get`` method') 

100 from neddy import namesearch 

101 

102 # SEARCH NED WITH SINGLE CONESEARCHES TO RETURN LIST OF MATCHED NAMES 

103 names, searchParams = self.get_crossmatch_names( 

104 listOfCoordinates=self.listOfCoordinates, 

105 radiusArcsec=self.arcsec 

106 ) 

107 

108 # NOW PERFORM A NAME SEARCH AGAINST THE MATCHED NAMES 

109 search = namesearch( 

110 log=self.log, 

111 names=names, 

112 quiet=False, 

113 searchParams=searchParams, 

114 verbose=self.verbose, 

115 outputFilePath=self.outputFilePath 

116 ) 

117 results = search.get() 

118 

119 self.log.debug('completed the ``get`` method') 

120 return results 

121 

122 def _get_ned_query_url( 

123 self, 

124 raDeg, 

125 decDeg, 

126 arcsec): 

127 """ 

128 *build and return the NED conesearch URL for a single coordinate* 

129 

130 **Key Arguments** 

131 

132 - ``raDeg`` -- conesearch centre RA 

133 - ``decDeg`` -- conesearch centre DEC 

134 - ``arcsec`` -- conesearch radius in arcsec 

135 

136 **Return** 

137 

138 - ``url`` -- the conesearch URL 

139 """ 

140 self.log.debug('starting the ``_get_ned_query_url`` method') 

141 

142 import urllib.parse 

143 from past.utils import old_div 

144 

145 radArcMin = old_div(float(arcsec), (60.)) 

146 

147 if self.redshift == True: 

148 z_constraint = "Available" 

149 else: 

150 z_constraint = "Unconstrained" 

151 

152 url = "http://ned.ipac.caltech.edu/cgi-bin/objsearch" 

153 params = { 

154 "in_csys": "Equatorial", 

155 "in_equinox": "J2000.0", 

156 "lon": "%(raDeg)0.6fd" % locals(), 

157 "lat": "%(decDeg)0.6fd" % locals(), 

158 "radius": "%(radArcMin)0.6s" % locals(), 

159 "hconst": "73", 

160 "omegam": "0.27", 

161 "omegav": "0.73", 

162 "corr_z": "1", 

163 "z_constraint": z_constraint, 

164 "z_value1": "", 

165 "z_value2": "", 

166 "z_unit": "z", 

167 "ot_include": "ANY", 

168 "nmp_op": "ANY", 

169 "out_csys": "Equatorial", 

170 "out_equinox": "J2000.0", 

171 "obj_sort": "Distance to search center", 

172 "of": "ascii_bar", 

173 "zv_breaker": "30000.0", 

174 "list_limit": "500", 

175 "img_stamp": "NO", 

176 "search_type": "Near Position Search", 

177 

178 } 

179 

180 url = url + "?" + urllib.parse.urlencode(params) 

181 if not self.unclassified: 

182 url = url + "&" + urllib.parse.urlencode({"ot_include": "ANY"}) 

183 in_objtypes1 = ["Galaxies", "GPairs", "GTriples", "GGroups", 

184 "GClusters", "QSO", "QSOGroups", "GravLens", "AbsLineSys", "EmissnLine"] 

185 for o in in_objtypes1: 

186 url = url + "&" + urllib.parse.urlencode({"in_objtypes1": o}) 

187 in_objtypes3 = ["Supernovae", "HIIregion", "PN", "SNR", "StarAssoc", "StarClust", "MolCloud", "Nova", "VarStar", "WolfRayet", 

188 "CarbonStar", "PofG", "Other", "Star", "BlueStar", "RedStar", "Pulsar", "ReflNeb", "DblStar", "EmissnObj", "EmissnNeb", "WhiteDwarf"] 

189 for o in in_objtypes3: 

190 url = url + "&" + urllib.parse.urlencode({"in_objtypes3": o}) 

191 

192 self.log.debug('completed the ``_get_ned_query_url`` method') 

193 return url 

194 

195 def get_crossmatch_names( 

196 self, 

197 listOfCoordinates=False, 

198 radiusArcsec=False): 

199 """ 

200 *return a list of NED sources found within the conesearch radius* 

201 

202 **Key Arguments** 

203 

204 - ``listOfCoordinates`` -- list of the coordinates to conesearch 

205 - ``radiusArcsec`` -- the search radius 

206 

207 **Return** 

208 

209 - ``names`` -- the names of the sources matched within the search radius 

210 - ``searchParams`` -- the parameters of the search as read from the command-line/method call 

211 """ 

212 self.log.debug('starting the ``get_crossmatch_names`` method') 

213 

214 from fundamentals.download import multiobject_download 

215 

216 if listOfCoordinates == False: 

217 listOfCoordinates = self.listOfCoordinates 

218 if radiusArcsec == False: 

219 radiusArcsec = self.arcsec 

220 

221 names = [] 

222 searchParams = [] 

223 nedUrls = [] 

224 

225 # GENERATE NED CONESEARCH URLS 

226 for i, coord in enumerate(listOfCoordinates): 

227 url = self._get_ned_query_url( 

228 raDeg=coord[0], 

229 decDeg=coord[1], 

230 arcsec=radiusArcsec 

231 ) 

232 nedUrls.append(url) 

233 

234 count = len(nedUrls) 

235 if count: 

236 print(f"{count} NED conesearch URLs have been built. Requesting from NED ...") 

237 

238 # DOWNLOAD THE RESULTS TO FILE 

239 localUrls = multiobject_download( 

240 urlList=nedUrls, 

241 downloadDirectory="/tmp", 

242 log=self.log, 

243 timeStamp=True, 

244 timeout=3600, 

245 concurrentDownloads=10, 

246 resetFilename=False, 

247 credentials=False, # { 'username' : "...", "password", "..." } 

248 longTime=True, 

249 indexFilenames=True 

250 ) 

251 

252 count = len(localUrls) 

253 if count: 

254 print(f"{count} conesearch results downloaded from NED") 

255 

256 for ii, nedResults in enumerate(localUrls): 

257 if nedResults == None: 

258 thisUrl = nedUrls[ii] 

259 self.log.error( 

260 f'cound not download results for NED URL: {thisUrl}') 

261 continue 

262 i = int(nedResults.split("/")[-1].split("_")[0]) 

263 

264 # PARSE CONESEARCH RESULTS INTO PYTHON DICTS 

265 results, resultLen = self._parse_the_ned_position_results( 

266 ra=listOfCoordinates[i][0], 

267 dec=listOfCoordinates[i][1], 

268 nedResults=nedResults 

269 ) 

270 print(f" {resultLen} returned from single NED conesearch") 

271 if resultLen > 45000: 

272 print(" To many results returned from single NED query ... aborting!") 

273 

274 # DO FINER SUB-SEARCHES TO COLLECT ALL NED MATCH RESULTS 

275 subnames, subsearchParams = self._oversized_subqueries( 

276 coordinate=listOfCoordinates[i], 

277 radiusArcsec=radiusArcsec 

278 ) 

279 names += subnames 

280 searchParams += subsearchParams 

281 else: 

282 for r in results: 

283 searchParams.append( 

284 {"searchIndex": i + 1, "searchRa": r["searchRa"], "searchDec": r["searchDec"]}) 

285 names.append(r["matchName"]) 

286 os.remove(nedResults) 

287 

288 self.log.debug('completed the ``get_crossmatch_names`` method') 

289 return names, searchParams 

290 

291 def _oversized_subqueries( 

292 self, 

293 coordinate, 

294 radiusArcsec): 

295 """ 

296 *subdivide an oversized query (> 50,000 matches in NED get truncated to 50000) into finer search areas* 

297 

298 **Key Arguments** 

299 

300 - ``coordinate`` -- the crowd-field RA and DEC.  

301 - ``radiusArcsec`` -- the original search radius 

302 

303 **Return** 

304 

305 - ``names`` -- the matched names 

306 - ``searchParams`` -- the new search parameters 

307 """ 

308 self.log.debug('starting the ``_oversized_subqueries`` method') 

309 

310 import math 

311 from past.utils import old_div 

312 

313 smallerRadiusArcsec = old_div(radiusArcsec, 2.) 

314 print("Calculating 7 sub-disks for coordinates %(coordinate)s, with smaller search radius of %(smallerRadiusArcsec)s arcsec" % locals()) 

315 

316 ra = coordinate[0] 

317 dec = coordinate[1] 

318 

319 shifts = [ 

320 (0, 0), 

321 (0, old_div(math.sqrt(3.), 2.)), 

322 (old_div(3., 4.), old_div(math.sqrt(3.), 4.)), 

323 (old_div(3., 4.), old_div(-math.sqrt(3.), 4.)), 

324 (0, old_div(-math.sqrt(3.), 2.)), 

325 (old_div(-3., 4.), old_div(-math.sqrt(3.), 4.)), 

326 (old_div(-3., 4.), old_div(math.sqrt(3.), 4.)) 

327 ] 

328 

329 subDiskCoordinates = [] 

330 count = 0 

331 for s in shifts: 

332 x1 = ra + s[0] * radiusArcsec / (60 * 60) 

333 y1 = dec + s[1] * radiusArcsec / (60 * 60) 

334 subDiskCoordinates.append((x1, y1)) 

335 

336 names, searchParams = self.get_crossmatch_names( 

337 listOfCoordinates=subDiskCoordinates, 

338 radiusArcsec=smallerRadiusArcsec 

339 ) 

340 

341 self.log.debug('completed the ``_oversized_subqueries`` method') 

342 return names, searchParams 

343 

344 

345if __name__ == '__main__': 

346 main()