Coverage for HMpTy/htm/htm.py : 89%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/local/bin/python
2# encoding: utf-8
3"""
4*Tools for working with Hierarchical Triangular Meshes, including coordinate crossmatching*
6:Author:
7 David Young (originally forked from Erin Sheldon's esutil - esheldon)
8"""
9from __future__ import absolute_import
10from __future__ import division
11from past.utils import old_div
12import sys
13import os
14os.environ['TERM'] = 'vt100'
15from fundamentals import tools
16from astrocalc.coords import unit_conversion
17from . import _htmcCode
18import numpy
19from sys import stdout
21class HTM(_htmcCode.HTMC):
22 """
23 *A Hierarchical Triangular Mesh object*
25 **Key Arguments**
27 - ``depth`` -- the depth of the mesh you wish to create. Default *16*
30 **Usage**
32 To generate a mesh object:
34 ```python
35 from HMpTy import HTM
36 mesh16 = HTM(
37 depth=16
38 )
39 ```
41 """
43 @property
44 def depth(
45 self):
46 """*the depth of the HTM tree*
48 **Usage**
50 ```python
51 mesh.depth
52 ```
54 """
55 return super(HTM, self).depth()
57 @property
58 def area(
59 self):
60 """*The mean area of triangles in this mesh in units of square degrees.*
62 **Usage**
64 ```python
65 mesh.area
66 ```
68 """
69 pi = numpy.pi
70 area0 = 4.0 * pi / 8.0
71 areadiv = 4.0 ** self.depth
72 area = old_div(area0, areadiv) * (180.0 / pi) ** 2
73 return area
75 def lookup_id(
76 self,
77 ra,
78 dec):
79 """*Lookup the ID of HTM trixel that a coordinate or lists of coordinates lie on*
81 **Key Arguments**
83 - ``ra`` -- list, numpy array or single ra value (first coordinate set)
84 - ``dec`` -- list, numpy array or single dec value (first coordinate set - must match ra1 array length)
87 **Return**
89 - ``htmIds`` -- a list of HTM trixel ids the coordinates lie on
92 **Usage**
94 To find the trixel IDs that a set of coordinate lie on:
96 ```python
97 raList1 = ["13:20:00.00", 200.0, "13:20:00.00", 175.23, 21.36]
98 decList1 = ["+24:18:00.00", 24.3, "+24:18:00.00", -28.25, -15.32]
100 htmids = mesh.lookup_id(raList1, decList1)
101 for h, r, d in zip(htmids, raList1, decList1):
102 print(r, d, " --> ", h)
103 ```
105 """
106 self.log.debug('starting the ``lookup_id`` method')
108 from astrocalc.coords import coordinates_to_array
109 raArray, decArray = coordinates_to_array(
110 log=self.log,
111 ra=ra,
112 dec=dec
113 )
115 self.log.debug('completed the ``lookup_id`` method')
116 return super(HTM, self).lookup_id(raArray, decArray)
118 # use the tab-trigger below for new method
119 # xt-class-method
121 def intersect(self, ra, dec, radius, inclusive=True, convertCoordinates=True):
122 """*return IDs of all triangles contained within and/or intersecting a circle centered on a given ra and dec*
124 **Key Arguments**
126 - ``ra`` -- RA of central point in decimal degrees or sexagesimal
127 - ``dec`` -- DEC of central point in decimal degrees or sexagesimal
128 - ``radius`` -- radius of circle in degrees
129 - ``inclusive`` -- include IDs of triangles that intersect the circle as well as those completely inclosed by the circle. Default *True*
130 - ``convertCoordinates`` -- convert the corrdinates passed to intersect. Default *True*
131 -
134 **Return**
136 - ``trixelArray`` -- a numpy array of the match trixel IDs
139 **Usage**
141 To return the trixels overlapping a circle with a 10 arcsec radius centred at 23:25:53.56, +26:54:23.9
143 ```python
144 overlappingTrixels = mesh16.intersect(
145 ra="23:25:53.56",
146 dec="+26:54:23.9",
147 radius=10 / (60 * 60),
148 inclusive=True
149 )
150 ```
152 Or to return the trixels completing enclosed by a circle with a 1 degree radius centred at 23:25:53.56, +26:54:23.9
154 ```python
155 overlappingTrixels = mesh16.intersect(
156 ra="23:25:53.56",
157 dec="+26:54:23.9",
158 radius=1,
159 inclusive=False
160 )
161 ```
163 """
164 # CONVERT RA AND DEC DECIMAL DEGREES
166 if convertCoordinates == True:
167 converter = unit_conversion(
168 log=self.log
169 )
170 ra = converter.ra_sexegesimal_to_decimal(
171 ra=ra
172 )
173 dec = converter.dec_sexegesimal_to_decimal(
174 dec=dec
175 )
177 if inclusive:
178 inc = 1
179 else:
180 inc = 0
181 return super(HTM, self).intersect(ra, dec, radius, inc)
183 def match(self, ra1, dec1, ra2, dec2, radius, maxmatch=1, convertToArray=True):
184 """*Crossmatch two lists of ra/dec points*
186 This is very efficient for large search angles and large lists. Note, if you need to match against the same points many times, you should use a `Matcher` object
188 **Key Arguments**
190 - ``ra1`` -- list, numpy array or single ra value (first coordinate set)
191 - ``dec1`` -- list, numpy array or single dec value (first coordinate set - must match ra1 array length)
192 - ``ra2`` -- list, numpy array or single ra value (second coordinate set)
193 - ``dec2`` -- list, numpy array or single dec value (second coordinate set - must match ra2 array length)
194 - ``radius`` -- search radius in degrees. Can be list, numpy array or single value. If list or numpy array must be same length as ra1 array length)
195 - ``maxmatch`` -- maximum number of matches to return. Set to `0` to match all points. Default *1* (i.e. closest match)
196 - ``convertToArray`` -- convert the coordinates into an array. Default *True*. Can bypass the conversion check if you are sure coordinates in numpy array
199 **Return**
201 - ``matchIndices1`` -- match indices for list1 (ra1, dec1)
202 - ``matchIndices2`` -- match indices for list2 (ra2, dec2)
203 - ``sepDeg`` -- separations between matched corrdinates in degrees. All returned arrays are the same size
206 **Usage**
208 To match 2 lists of corrdinates try something like this:
210 ```python
211 twoArcsec = 2.0 / 3600.
212 raList1 = [200.0, 200.0, 200.0, 175.23, 21.36]
213 decList1 = [24.3, 24.3, 24.3, -28.25, -15.32]
214 raList2 = [200.0, 200.0, 200.0, 175.23, 55.25]
215 decList2 = [24.3 + 0.75 * twoArcsec, 24.3 + 0.25 * twoArcsec,
216 24.3 - 0.33 * twoArcsec, -28.25 + 0.58 * twoArcsec, 75.22]
217 matchIndices1, matchIndices2, seps = mesh.match(
218 ra1=raList1,
219 dec1=decList1,
220 ra2=raList2,
221 dec2=decList2,
222 radius=twoArcsec,
223 maxmatch=0
224 )
226 for m1, m2, s in zip(matchIndices1, matchIndices2, seps):
227 print(raList1[m1], decList1[m1], " -> ", s * 3600., " arcsec -> ", raList2[m2], decList2[m2])
228 ```
230 Note from the print statement, you can index the arrays ``raList1``, ``decList1`` with the ``matchIndices1`` array values and ``raList2``, ``decList2`` with the ``matchIndices2`` values.
232 """
234 # CONVERT LISTS AND SINGLE VALUES TO ARRAYS OF FLOATS
235 ra1 = numpy.array(ra1, dtype='f8', ndmin=1, copy=False)
236 dec1 = numpy.array(dec1, dtype='f8', ndmin=1, copy=False)
237 ra2 = numpy.array(ra2, dtype='f8', ndmin=1, copy=False)
238 dec2 = numpy.array(dec2, dtype='f8', ndmin=1, copy=False)
239 radius = numpy.array(radius, dtype='f8', ndmin=1, copy=False)
241 # CHECK ARRAY SIZES MATCH
242 if (ra1.size != dec1.size
243 or ra2.size != ra2.size):
244 stup = (ra1.size, dec1.size, ra2.size, dec2.size)
245 raise ValueError("ra1 must equal dec1 in size "
246 "and ra2 must equal dec2 in size, "
247 "got %d,%d and %d,%d" % stup)
249 if radius.size != 1 and radius.size != ra2.size:
250 raise ValueError("radius size (%d) != 1 and"
251 " != ra2,dec2 size (%d)" % (radius.size, ra2.size))
253 # QUICK TRIMMING IN DEC SPACE OF BOTH SETS OF ARRAYS
254 decMatchIndices2 = (numpy.abs(dec1[:, None] - dec2) < radius).any(0)
255 decMatchIndices2 = numpy.where(decMatchIndices2)[0]
256 ra2a = ra2[decMatchIndices2]
257 dec2a = dec2[decMatchIndices2]
259 decMatchIndices1 = (numpy.abs(dec2[:, None] - dec1) < radius).any(0)
260 decMatchIndices1 = numpy.where(decMatchIndices1)[0]
261 ra1a = ra1[decMatchIndices1]
262 dec1a = dec1[decMatchIndices1]
264 # new way using a Matcher
265 depth = self.depth
266 matcher = Matcher(
267 log=self.log,
268 depth=depth,
269 ra=ra2a,
270 dec=dec2a,
271 convertToArray=convertToArray)
272 matchIndices1, matchIndices2, seps = matcher.match(
273 ra=ra1a,
274 dec=dec1a,
275 radius=radius,
276 maxmatch=maxmatch)
278 matchIndices1 = decMatchIndices1[matchIndices1]
279 matchIndices2 = decMatchIndices2[matchIndices2]
281 return matchIndices1, matchIndices2, seps
283class Matcher(_htmcCode.Matcher):
284 """*A matcher-array object to match other arrays of ra,dec against*
286 The Matcher object is initialized with a set of ra,dec coordinates and can then be used and reused to match against other sets of coordinates
288 **Key Arguments**
290 - ``log`` -- logger
291 - ``depth`` -- the depth of the mesh generate the Matcher object at. Default *16*
292 - ``ra`` -- list, numpy array or single ra value
293 - ``dec`` -- --list, numpy array or single dec value (must match ra array length)
294 - ``convertToArray`` -- convert the coordinates into an array. Default *True*. Can bypass the conversion check if you are sure coordinates in numpy array
297 **Return**
299 - None
302 **Usage**
304 If we have a set of coordinates such that:
306 ```python
307 raList1 = [200.0, 200.0, 200.0, 175.23, 21.36]
308 decList1 = [24.3, 24.3, 24.3, -28.25, -15.32]
309 ```
311 We can initialise a matcher object like so:
313 ```python
314 from HMpTy import Matcher
315 coordinateSet = Matcher(
316 log=log,
317 ra=raList1,
318 dec=decList1,
319 depth=16
320 )
321 ```
323 """
325 def __init__(
326 self,
327 ra,
328 dec,
329 depth=16,
330 log=False,
331 convertToArray=True):
333 self.convertToArray = convertToArray
335 if log == False:
336 if log == False:
337 from fundamentals.logs import emptyLogger
338 self.log = emptyLogger()
339 else:
340 self.log = log
342 if convertToArray == True:
343 from astrocalc.coords import coordinates_to_array
344 ra, dec = coordinates_to_array(
345 log=log,
346 ra=ra,
347 dec=dec
348 )
350 if ra.size != dec.size:
351 raise ValueError("ra size (%d) != "
352 "dec size (%d)" % (ra.size, dec.size))
354 super(Matcher, self).__init__(depth, ra, dec)
356 @property
357 def depth(
358 self):
359 """*the depth of the Matcher object*
361 **Usage**
363 ```python
364 coordinateSet.depth
365 ```
367 """
368 return super(Matcher, self).depth
370 def match(self, ra, dec, radius, maxmatch=1):
371 """*match a corrdinate set against this Matcher object's coordinate set*
373 **Key Arguments**
375 - ``ra`` -- list, numpy array or single ra value
376 - ``dec`` -- --list, numpy array or single dec value (must match ra array length)
377 - ``radius`` -- radius of circle in degrees
378 - ``maxmatch`` -- maximum number of matches to return. Set to `0` to match all points. Default *1* (i.e. closest match)
381 **Return**
383 - None
386 **Usage**
388 Once we have initialised a Matcher coordinateSet object we can match other coordinate sets against it:
390 ```python
391 twoArcsec = 2.0 / 3600.
392 raList2 = [200.0, 200.0, 200.0, 175.23, 55.25]
393 decList2 = [24.3 + 0.75 * twoArcsec, 24.3 + 0.25 * twoArcsec,
394 24.3 - 0.33 * twoArcsec, -28.25 + 0.58 * twoArcsec, 75.22]
396 matchIndices1, matchIndices2, seps = coordinateSet.match(
397 ra=raList2,
398 dec=decList2,
399 radius=twoArcsec,
400 maxmatch=0
401 )
403 for m1, m2, s in zip(matchIndices1, matchIndices2, seps):
404 print(raList1[m1], decList1[m1], " -> ", s * 3600., " arcsec -> ", raList2[m2], decList2[m2])
405 ```
407 Or to return just the nearest matches:
409 ```python
410 matchIndices1, matchIndices2, seps = coordinateSet.match(
411 ra=raList2,
412 dec=decList2,
413 radius=twoArcsec,
414 maxmatch=1
415 )
416 ```
418 Note from the print statement, you can index the arrays ``raList1``, ``decList1`` with the ``matchIndices1`` array values and ``raList2``, ``decList2`` with the ``matchIndices2`` values.
420 """
422 if self.convertToArray == True:
423 from astrocalc.coords import coordinates_to_array
424 ra, dec = coordinates_to_array(
425 log=self.log,
426 ra=ra,
427 dec=dec
428 )
430 radius = numpy.array(radius, dtype='f8', ndmin=1, copy=False)
432 if ra.size != dec.size:
433 raise ValueError("ra size (%d) != "
434 "dec size (%d)" % (ra.size, dec.size))
436 if radius.size != 1 and radius.size != ra.size:
437 raise ValueError("radius size (%d) != 1 and"
438 " != ra,dec size (%d)" % (radius.size, ra.size))
440 return super(Matcher, self).match(ra, dec, radius, maxmatch, False)