Coverage for neddy/conesearch.py: 77%
118 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-09-20 10:57 +0000
« 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*
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'
19class conesearch(_basesearch):
20 """
21 *The NED conesearch object*
23 **Key Arguments**
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
37 **Usage**
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 """
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
83 # VARIABLE DATA ATRRIBUTES
84 from past.utils import old_div
85 self.arcmin = old_div(float(self.arcsec), 60.)
86 self.resultSpacing = 30
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()
93 return None
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
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 )
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()
119 self.log.debug('completed the ``get`` method')
120 return results
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*
130 **Key Arguments**
132 - ``raDeg`` -- conesearch centre RA
133 - ``decDeg`` -- conesearch centre DEC
134 - ``arcsec`` -- conesearch radius in arcsec
136 **Return**
138 - ``url`` -- the conesearch URL
139 """
140 self.log.debug('starting the ``_get_ned_query_url`` method')
142 import urllib.parse
143 from past.utils import old_div
145 radArcMin = old_div(float(arcsec), (60.))
147 if self.redshift == True:
148 z_constraint = "Available"
149 else:
150 z_constraint = "Unconstrained"
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",
178 }
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})
192 self.log.debug('completed the ``_get_ned_query_url`` method')
193 return url
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*
202 **Key Arguments**
204 - ``listOfCoordinates`` -- list of the coordinates to conesearch
205 - ``radiusArcsec`` -- the search radius
207 **Return**
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')
214 from fundamentals.download import multiobject_download
216 if listOfCoordinates == False:
217 listOfCoordinates = self.listOfCoordinates
218 if radiusArcsec == False:
219 radiusArcsec = self.arcsec
221 names = []
222 searchParams = []
223 nedUrls = []
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)
234 count = len(nedUrls)
235 if count:
236 print(f"{count} NED conesearch URLs have been built. Requesting from NED ...")
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 )
252 count = len(localUrls)
253 if count:
254 print(f"{count} conesearch results downloaded from NED")
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])
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!")
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)
288 self.log.debug('completed the ``get_crossmatch_names`` method')
289 return names, searchParams
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*
298 **Key Arguments**
300 - ``coordinate`` -- the crowd-field RA and DEC.
301 - ``radiusArcsec`` -- the original search radius
303 **Return**
305 - ``names`` -- the matched names
306 - ``searchParams`` -- the new search parameters
307 """
308 self.log.debug('starting the ``_oversized_subqueries`` method')
310 import math
311 from past.utils import old_div
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())
316 ra = coordinate[0]
317 dec = coordinate[1]
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 ]
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))
336 names, searchParams = self.get_crossmatch_names(
337 listOfCoordinates=subDiskCoordinates,
338 radiusArcsec=smallerRadiusArcsec
339 )
341 self.log.debug('completed the ``_oversized_subqueries`` method')
342 return names, searchParams
345if __name__ == '__main__':
346 main()