23__all__ = [
"RingsSkyMapConfig",
"RingsSkyMap"]
31from .cachingSkyMap
import CachingSkyMap
32from .tractInfo
import ExplicitTractInfo
36 """Configuration for the RingsSkyMap"""
37 numRings =
Field(dtype=int, doc=
"Number of rings", check=
lambda x: x > 0)
38 raStart =
Field(dtype=float, default=0.0, doc=
"Starting center RA for each ring (degrees)",
39 check=
lambda x: x >= 0.0
and x < 360.0)
43 """Rings sky map pixelization.
45 We divide the sphere into N rings of Declination, plus the two polar
46 caps, which sets the size of the individual tracts. The rings are
47 divided in RA into an integral number of tracts of this size; this
48 division
is made at the Declination closest to zero so
as to ensure
51 Rings are numbered
in the rings
from south to north. The south pole cap
is
52 ``tract=0``, then the tract at ``raStart``
in the southernmost ring
is
53 ``tract=1``. Numbering continues (
in the positive RA direction) around that
54 ring
and then continues
in the same fashion
with the next ring north,
and
55 so on until all reaching the north pole cap, which
is
56 ``tract=len(skymap) - 1``.
58 However, ``version=0`` had a bug
in the numbering of the tracts: the first
59 and last tracts
in the first (southernmost) ring were identical,
and the
60 first tract
in the last (northernmost) ring was missing. When using
61 ``version=0``, these tracts remain missing
in order to preserve the
66 config : `lsst.skymap.RingsSkyMapConfig`
67 The configuration
for this SkyMap.
68 version : `int`, optional
69 Software version of this
class, to retain compatibility
with old
70 verisons. ``version=0`` covers the period
from first implementation
71 until DM-14809, at which point bugs were identified
in the numbering
72 of tracts (affecting only tracts at RA=0). ``version=1`` uses the
73 post-DM-14809 tract numbering.
75 ConfigClass = RingsSkyMapConfig
79 assert version
in (0, 1),
"Unrecognised version: %s" % (version,)
82 self.
_ringSize_ringSize = math.pi / (config.numRings + 1)
84 for i
in range(config.numRings):
85 startDec = self.
_ringSize_ringSize*(i + 0.5) - 0.5*math.pi
86 stopDec = startDec + self.
_ringSize_ringSize
87 dec =
min(math.fabs(startDec), math.fabs(stopDec))
89 numTracts = sum(self.
_ringNums_ringNums) + 2
90 super(RingsSkyMap, self).
__init__(numTracts, config, version)
94 """Calculate ring indices given a numerical index of a tract.
96 The ring indices are the ring number and the tract number within
99 The ring number
is -1
for the south polar cap
and increases to the
100 north. The north polar cap has ring number = numRings. The tract
101 number
is zero
for either of the polar caps.
106 return self.
configconfig.numRings, 0
107 if index < 0
or index >= self.
_numTracts_numTracts:
108 raise IndexError(
"Tract index %d is out of range [0, %d]" % (index, len(self) - 1))
115 while ring < self.
configconfig.numRings
and tractNum > self.
_ringNums_ringNums[ring]:
116 tractNum -= self.
_ringNums_ringNums[ring]
119 while ring < self.
configconfig.numRings
and tractNum >= self.
_ringNums_ringNums[ring]:
120 tractNum -= self.
_ringNums_ringNums[ring]
122 return ring, tractNum
125 """Generate TractInfo for the specified tract index."""
128 ra, dec = 0, -0.5*math.pi
129 elif ringNum == self.
configconfig.numRings:
130 ra, dec = 0, 0.5*math.pi
132 dec = self.
_ringSize_ringSize*(ringNum + 1) - 0.5*math.pi
133 ra = ((2*math.pi*tractNum/self.
_ringNums_ringNums[ringNum])*geom.radians
139 0.5*self.
_ringSize_ringSize*geom.radians, self.
configconfig.tractOverlap*geom.degrees,
142 def _decToRingNum(self, dec):
143 """Calculate ring number from Declination.
153 Ring number: -1 for the south polar cap,
and increasing to the
154 north, ending
with ``numRings``
for the north polar cap.
156 firstRingStart = self._ringSize_ringSize*0.5 - 0.5*math.pi
157 if dec < firstRingStart:
160 elif dec > firstRingStart*-1:
162 return self.
configconfig.numRings
163 return int((dec.asRadians() - firstRingStart)/self.
_ringSize_ringSize)
165 def _raToTractNum(self, ra, ringNum):
166 """Calculate tract number from the Right Ascension.
173 Ring number (from ``_decToRingNum``).
178 Tract number within the ring (starts at 0
for the tract at raStart).
180 if ringNum
in (-1, self.
configconfig.numRings):
182 assert ringNum
in range(self.
configconfig.numRings)
184 / (2*math.pi/self.
_ringNums_ringNums[ringNum]) + 0.5)
185 return 0
if tractNum == self.
_ringNums_ringNums[ringNum]
else tractNum
188 ringNum = self.
_decToRingNum_decToRingNum(coord.getLatitude())
192 if ringNum == self.
configconfig.numRings:
195 tractNum = self.
_raToTractNum_raToTractNum(coord.getLongitude(), ringNum)
202 index = sum(self.
_ringNums_ringNums[:ringNum], tractNum + 1)
208 _dec = np.deg2rad(dec)
210 _ra = np.atleast_1d(ra)
211 _dec = np.atleast_1d(dec)
214 indexes = np.full(_ra.size, -1, dtype=np.int32)
217 firstRingStart = self.
_ringSize_ringSize*0.5 - 0.5*np.pi
218 ringNums = np.zeros(len(_dec), dtype=np.int32)
220 ringNums[_dec < firstRingStart] = -1
221 ringNums[_dec > -1*firstRingStart] = self.
configconfig.numRings
223 mid = (_dec >= firstRingStart) & (_dec <= -1*firstRingStart)
224 ringNums[mid] = ((_dec[mid] - firstRingStart)/self.
_ringSize_ringSize).astype(np.int32)
226 indexes[ringNums == -1] = 0
227 indexes[ringNums == self.
configconfig.numRings] = self.
_numTracts_numTracts - 1
230 inRange, = np.where(indexes < 0)
233 _ringNumArray = np.array(self.
_ringNums_ringNums)
234 _ringCumulative = np.cumsum(np.insert(_ringNumArray, 0, 0))
236 deltaWrap = (_ra[inRange] - self.
_raStart_raStart.asRadians()) % (2.*np.pi)
237 tractNum = (deltaWrap/(2.*np.pi/_ringNumArray[ringNums[inRange]]) + 0.5).astype(np.int32)
239 tractNum[tractNum == _ringNumArray[ringNums[inRange]]] = 0
244 offByOne, = np.where((tractNum == 0)
245 & (ringNums[inRange] != 0))
246 ringNums[inRange[offByOne]] += 1
248 indexes[inRange] = _ringCumulative[ringNums[inRange]] + tractNum + 1
253 """Find all tracts which include the specified coord.
258 ICRS sky coordinate to search for.
262 tractList : `list` of `TractInfo`
263 The tracts which include the specified coord.
265 ringNum = self._decToRingNum_decToRingNum(coord.getLatitude())
270 for r
in [ringNum - 1, ringNum, ringNum + 1]:
271 if r < 0
or r >= self.
configconfig.numRings:
274 tractNum = self.
_raToTractNum_raToTractNum(coord.getLongitude(), r)
276 for t
in [tractNum - 1, tractNum, tractNum + 1]:
289 index = sum(self.
_ringNums_ringNums[:r + extra], t + 1)
291 if tract.contains(coord):
292 tractList.append(tract)
296 for entry
in [0, len(self)-1]:
298 if tract.contains(coord):
299 tractList.append(tract)
305 for coord
in coordList:
307 patchList = tractInfo.findPatchList(coordList)
308 if patchList
and not (tractInfo, patchList)
in retList:
309 retList.append((tractInfo, patchList))
313 """Add subclass-specific state or configuration options to the SHA1."""
314 sha1.update(struct.pack(
"<id", self.
configconfig.numRings, self.
configconfig.raStart))
A class representing an angle.
Point in an unspecified spherical coordinate system.
def getRingIndices(self, index)
def _raToTractNum(self, ra, ringNum)
def _decToRingNum(self, dec)
def __init__(self, config, version=1)
def findTractIdArray(self, ra, dec, degrees=False)
def updateSha1(self, sha1)
def findTract(self, coord)
def findTractPatchList(self, coordList)
def generateTract(self, index)
def findAllTracts(self, coord)
daf::base::PropertyList * list
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
std::shared_ptr< afw::geom::SkyWcs > makeWcs(SipForwardTransform const &sipForward, SipReverseTransform const &sipReverse, geom::SpherePoint const &skyOrigin)
Create a new TAN SIP Wcs from a pair of SIP transforms and the sky origin.