LSSTApplications  18.0.0+106,18.0.0+50,19.0.0,19.0.0+1,19.0.0+10,19.0.0+11,19.0.0+13,19.0.0+17,19.0.0+2,19.0.0-1-g20d9b18+6,19.0.0-1-g425ff20,19.0.0-1-g5549ca4,19.0.0-1-g580fafe+6,19.0.0-1-g6fe20d0+1,19.0.0-1-g7011481+9,19.0.0-1-g8c57eb9+6,19.0.0-1-gb5175dc+11,19.0.0-1-gdc0e4a7+9,19.0.0-1-ge272bc4+6,19.0.0-1-ge3aa853,19.0.0-10-g448f008b,19.0.0-12-g6990b2c,19.0.0-2-g0d9f9cd+11,19.0.0-2-g3d9e4fb2+11,19.0.0-2-g5037de4,19.0.0-2-gb96a1c4+3,19.0.0-2-gd955cfd+15,19.0.0-3-g2d13df8,19.0.0-3-g6f3c7dc,19.0.0-4-g725f80e+11,19.0.0-4-ga671dab3b+1,19.0.0-4-gad373c5+3,19.0.0-5-ga2acb9c+2,19.0.0-5-gfe96e6c+2,w.2020.01
LSSTDataManagementBasePackage
ringsSkyMap.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008, 2009, 2010, 2012 LSST Corporation.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 
23 __all__ = ["RingsSkyMapConfig", "RingsSkyMap"]
24 
25 import struct
26 import math
27 
28 from lsst.pex.config import Field
29 import lsst.geom as geom
30 from .cachingSkyMap import CachingSkyMap
31 from .tractInfo import ExplicitTractInfo
32 
33 
34 class RingsSkyMapConfig(CachingSkyMap.ConfigClass):
35  """Configuration for the RingsSkyMap"""
36  numRings = Field(dtype=int, doc="Number of rings", check=lambda x: x > 0)
37  raStart = Field(dtype=float, default=0.0, doc="Starting center RA for each ring (degrees)",
38  check=lambda x: x >= 0.0 and x < 360.0)
39 
40 
42  """Rings sky map pixelization.
43 
44  We divide the sphere into N rings of Declination, plus the two polar
45  caps, which sets the size of the individual tracts. The rings are
46  divided in RA into an integral number of tracts of this size; this
47  division is made at the Declination closest to zero so as to ensure
48  full overlap.
49 
50  Rings are numbered in the rings from south to north. The south pole cap is
51  ``tract=0``, then the tract at ``raStart`` in the southernmost ring is
52  ``tract=1``. Numbering continues (in the positive RA direction) around that
53  ring and then continues in the same fashion with the next ring north, and
54  so on until all reaching the north pole cap, which is
55  ``tract=len(skymap) - 1``.
56 
57  However, ``version=0`` had a bug in the numbering of the tracts: the first
58  and last tracts in the first (southernmost) ring were identical, and the
59  first tract in the last (northernmost) ring was missing. When using
60  ``version=0``, these tracts remain missing in order to preserve the
61  numbering scheme.
62 
63  Parameters
64  ----------
65  config : `lsst.skymap.RingsSkyMapConfig`
66  The configuration for this SkyMap.
67  version : `int`, optional
68  Software version of this class, to retain compatibility with old
69  verisons. ``version=0`` covers the period from first implementation
70  until DM-14809, at which point bugs were identified in the numbering
71  of tracts (affecting only tracts at RA=0). ``version=1`` uses the
72  post-DM-14809 tract numbering.
73  """
74  ConfigClass = RingsSkyMapConfig
75  _version = (1, 0) # for pickle
76 
77  def __init__(self, config, version=1):
78  assert version in (0, 1), "Unrecognised version: %s" % (version,)
79  # We count rings from south to north
80  # Note: pole caps together count for one additional ring when calculating the ring size
81  self._ringSize = math.pi / (config.numRings + 1) # Size of a ring in Declination (radians)
82  self._ringNums = [] # Number of tracts for each ring
83  for i in range(config.numRings):
84  startDec = self._ringSize*(i + 0.5) - 0.5*math.pi
85  stopDec = startDec + self._ringSize
86  dec = min(math.fabs(startDec), math.fabs(stopDec)) # Declination for determining division in RA
87  self._ringNums.append(int(2*math.pi*math.cos(dec)/self._ringSize) + 1)
88  numTracts = sum(self._ringNums) + 2
89  super(RingsSkyMap, self).__init__(numTracts, config, version)
90  self._raStart = self.config.raStart*geom.degrees
91 
92  def getRingIndices(self, index):
93  """Calculate ring indices given a numerical index of a tract.
94 
95  The ring indices are the ring number and the tract number within
96  the ring.
97 
98  The ring number is -1 for the south polar cap and increases to the
99  north. The north polar cap has ring number = numRings. The tract
100  number is zero for either of the polar caps.
101  """
102  if index == 0: # South polar cap
103  return -1, 0
104  if index == self._numTracts - 1: # North polar cap
105  return self.config.numRings, 0
106  if index < 0 or index >= self._numTracts:
107  raise IndexError("Tract index %d is out of range [0, %d]" % (index, len(self) - 1))
108  ring = 0 # Ring number
109  tractNum = index - 1 # Tract number within ring
110  if self._version == 0:
111  # Maintain the off-by-one bug in version=0 (DM-14809).
112  # This means that the first tract in the first ring is duplicated
113  # and the first tract in the last ring is missing.
114  while ring < self.config.numRings and tractNum > self._ringNums[ring]:
115  tractNum -= self._ringNums[ring]
116  ring += 1
117  else:
118  while ring < self.config.numRings and tractNum >= self._ringNums[ring]:
119  tractNum -= self._ringNums[ring]
120  ring += 1
121  return ring, tractNum
122 
123  def generateTract(self, index):
124  """Generate TractInfo for the specified tract index."""
125  ringNum, tractNum = self.getRingIndices(index)
126  if ringNum == -1: # South polar cap
127  ra, dec = 0, -0.5*math.pi
128  elif ringNum == self.config.numRings: # North polar cap
129  ra, dec = 0, 0.5*math.pi
130  else:
131  dec = self._ringSize*(ringNum + 1) - 0.5*math.pi
132  ra = ((2*math.pi*tractNum/self._ringNums[ringNum])*geom.radians +
133  self._raStart).wrap().asRadians()
134 
135  center = geom.SpherePoint(ra, dec, geom.radians)
136  wcs = self._wcsFactory.makeWcs(crPixPos=geom.Point2D(0, 0), crValCoord=center)
137  return ExplicitTractInfo(index, self.config.patchInnerDimensions, self.config.patchBorder, center,
138  0.5*self._ringSize*geom.radians, self.config.tractOverlap*geom.degrees,
139  wcs)
140 
141  def _decToRingNum(self, dec):
142  """Calculate ring number from Declination.
143 
144  Parameters
145  ----------
146  dec : `lsst.geom.Angle`
147  Declination.
148 
149  Returns
150  -------
151  ringNum : `int`
152  Ring number: -1 for the south polar cap, and increasing to the
153  north, ending with ``numRings`` for the north polar cap.
154  """
155  firstRingStart = self._ringSize*0.5 - 0.5*math.pi
156  if dec < firstRingStart:
157  # Southern cap
158  return -1
159  elif dec > firstRingStart*-1:
160  # Northern cap
161  return self.config.numRings
162  return int((dec.asRadians() - firstRingStart)/self._ringSize)
163 
164  def _raToTractNum(self, ra, ringNum):
165  """Calculate tract number from the Right Ascension.
166 
167  Parameters
168  ----------
169  ra : `lsst.geom.Angle`
170  Right Ascension.
171  ringNum : `int`
172  Ring number (from ``_decToRingNum``).
173 
174  Returns
175  -------
176  tractNum : `int`
177  Tract number within the ring (starts at 0 for the tract at raStart).
178  """
179  if ringNum in (-1, self.config.numRings):
180  return 0
181  assert ringNum in range(self.config.numRings)
182  tractNum = int((ra - self._raStart).wrap().asRadians() /
183  (2*math.pi/self._ringNums[ringNum]) + 0.5)
184  return 0 if tractNum == self._ringNums[ringNum] else tractNum # Allow wraparound
185 
186  def findTract(self, coord):
187  ringNum = self._decToRingNum(coord.getLatitude())
188  if ringNum == -1:
189  # Southern cap
190  return self[0]
191  if ringNum == self.config.numRings:
192  # Northern cap
193  return self[self._numTracts - 1]
194  tractNum = self._raToTractNum(coord.getLongitude(), ringNum)
195 
196  if self._version == 0 and tractNum == 0 and ringNum != 0:
197  # Account for off-by-one error in getRingIndices
198  # Note that this means that tract 1 gets duplicated.
199  ringNum += 1
200 
201  index = sum(self._ringNums[:ringNum], tractNum + 1) # Allow 1 for south pole
202  return self[index]
203 
204  def findAllTracts(self, coord):
205  """Find all tracts which include the specified coord.
206 
207  Parameters
208  ----------
209  coord : `lsst.geom.SpherePoint`
210  ICRS sky coordinate to search for.
211 
212  Returns
213  -------
214  tractList : `list` of `TractInfo`
215  The tracts which include the specified coord.
216  """
217  ringNum = self._decToRingNum(coord.getLatitude())
218 
219  tractList = list()
220  # ringNum denotes the closest ring to the specified coord
221  # I will check adjacent rings which may include the specified coord
222  for r in [ringNum - 1, ringNum, ringNum + 1]:
223  if r < 0 or r >= self.config.numRings:
224  # Poles will be checked explicitly outside this loop
225  continue
226  tractNum = self._raToTractNum(coord.getLongitude(), r)
227  # Adjacent tracts will also be checked.
228  for t in [tractNum - 1, tractNum, tractNum + 1]:
229  # Wrap over raStart
230  if t < 0:
231  t = t + self._ringNums[r]
232  elif t > self._ringNums[r] - 1:
233  t = t - self._ringNums[r]
234 
235  extra = 0
236  if self._version == 0 and t == 0 and r != 0:
237  # Account for off-by-one error in getRingIndices
238  # Note that this means that tract 1 gets duplicated.
239  extra = 1
240 
241  index = sum(self._ringNums[:r + extra], t + 1) # Allow 1 for south pole
242  tract = self[index]
243  if tract.contains(coord):
244  tractList.append(tract)
245 
246  # Always check tracts at poles
247  # Southern cap is 0, Northern cap is the last entry in self
248  for entry in [0, len(self)-1]:
249  tract = self[entry]
250  if tract.contains(coord):
251  tractList.append(tract)
252 
253  return tractList
254 
255  def findTractPatchList(self, coordList):
256  retList = []
257  for coord in coordList:
258  for tractInfo in self.findAllTracts(coord):
259  patchList = tractInfo.findPatchList(coordList)
260  if patchList and not (tractInfo, patchList) in retList:
261  retList.append((tractInfo, patchList))
262  return retList
263 
264  def updateSha1(self, sha1):
265  """Add subclass-specific state or configuration options to the SHA1."""
266  sha1.update(struct.pack("<id", self.config.numRings, self.config.raStart))
def _raToTractNum(self, ra, ringNum)
Definition: ringsSkyMap.py:164
def findTractPatchList(self, coordList)
Definition: ringsSkyMap.py:255
def __init__(self, config, version=1)
Definition: ringsSkyMap.py:77
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
Definition: functional.cc:33
int min
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.
Point in an unspecified spherical coordinate system.
Definition: SpherePoint.h:57
def wrap(ctrl)
Definition: wrap.py:302
daf::base::PropertyList * list
Definition: fits.cc:903