lsst.skymap  15.0-8-g13fca11+11
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 import struct
24 import math
25 
26 from lsst.pex.config import Field
27 import lsst.afw.geom as afwGeom
28 from .cachingSkyMap import CachingSkyMap
29 from .tractInfo import ExplicitTractInfo
30 
31 __all__ = ["RingsSkyMapConfig", "RingsSkyMap"]
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  ConfigClass = RingsSkyMapConfig
51  _version = (1, 0) # for pickle
52 
53  def __init__(self, config, version=0):
54  """Constructor
55 
56  @param[in] config: an instance of self.ConfigClass; if None the default config is used
57  @param[in] version: software version of this class, to retain compatibility with old instances
58  """
59  # We count rings from south to north
60  # Note: pole caps together count for one additional ring
61  self._ringSize = math.pi / (config.numRings + 1) # Size of a ring in Declination (radians)
62  self._ringNums = [] # Number of tracts for each ring
63  for i in range(config.numRings):
64  startDec = self._ringSize*(i + 0.5) - 0.5*math.pi
65  stopDec = startDec + self._ringSize
66  dec = min(math.fabs(startDec), math.fabs(stopDec)) # Declination for determining division in RA
67  self._ringNums.append(int(2*math.pi*math.cos(dec)/self._ringSize) + 1)
68  numTracts = sum(self._ringNums) + 2
69  super(RingsSkyMap, self).__init__(numTracts, config, version)
70 
71  def getRingIndices(self, index):
72  """Calculate ring indices given a numerical index of a tract
73 
74  The ring indices are the ring number and the tract number within
75  the ring.
76 
77  The ring number is -1 for the south polar cap and increases to the
78  north. The north polar cap has ring number = numRings. The tract
79  number is zero for either of the polar caps.
80  """
81  if index == 0: # South polar cap
82  return -1, 0
83  if index == self._numTracts - 1: # North polar cap
84  return self.config.numRings, 0
85  index -= 1
86  ring = 0
87  while ring < self.config.numRings and index > self._ringNums[ring]:
88  index -= self._ringNums[ring]
89  ring += 1
90  return ring, index
91 
92  def generateTract(self, index):
93  """Generate the TractInfo for this index"""
94  ringNum, tractNum = self.getRingIndices(index)
95  if ringNum == -1: # South polar cap
96  ra, dec = 0, -0.5*math.pi
97  elif ringNum == self.config.numRings: # North polar cap
98  ra, dec = 0, 0.5*math.pi
99  else:
100  dec = self._ringSize*(ringNum + 1) - 0.5*math.pi
101  ra = math.fmod(self.config.raStart + 2*math.pi*tractNum/self._ringNums[ringNum], 2*math.pi)
102 
103  center = afwGeom.SpherePoint(ra, dec, afwGeom.radians)
104  wcs = self._wcsFactory.makeWcs(crPixPos=afwGeom.Point2D(0, 0), crValCoord=center)
105  return ExplicitTractInfo(index, self.config.patchInnerDimensions, self.config.patchBorder, center,
106  0.5*self._ringSize*afwGeom.radians, self.config.tractOverlap*afwGeom.degrees,
107  wcs)
108 
109  def findTract(self, coord):
110  """Find the tract whose center is nearest the specified coord.
111 
112  @param[in] coord: sky coordinate (afwCoord.Coord)
113  @return TractInfo of tract whose center is nearest the specified coord
114 
115  @warning:
116  - if tracts do not cover the whole sky then the returned tract may not include the coord
117 
118  @note
119  - This routine will be more efficient if coord is ICRS.
120  - If coord is equidistant between multiple sky tract centers then one is arbitrarily chosen.
121  - The default implementation is not very efficient; subclasses may wish to override.
122  """
123  ra = coord.getLongitude().asRadians()
124  dec = coord.getLatitude().asRadians()
125 
126  firstRingStart = self._ringSize*0.5 - 0.5*math.pi
127  if dec < firstRingStart:
128  # Southern cap
129  return self[0]
130  elif dec > firstRingStart*-1:
131  # Northern cap
132  return self[-1]
133 
134  ringNum = int((dec - firstRingStart)/self._ringSize)
135  tractNum = int(math.fmod(ra - self.config.raStart, 2*math.pi) /
136  (2*math.pi/self._ringNums[ringNum]) + 0.5)
137 
138  index = tractNum + 1 # Allow 1 for south pole
139  for i in range(ringNum):
140  index += self._ringNums[i]
141 
142  return self[index]
143 
144  def findAllTracts(self, coord):
145  """Find all tracts which include the specified coord.
146 
147  @param[in] coord: sky coordinate (afwCoord.Coord)
148  @return List of TractInfo of tracts which include the specified coord
149 
150  @note
151  - This routine will be more efficient if coord is ICRS.
152  """
153  ra = coord.getLongitude().asRadians()
154  dec = coord.getLatitude().asRadians()
155 
156  firstRingStart = self._ringSize*0.5 - 0.5*math.pi
157 
158  ringNum = int((dec - firstRingStart)/self._ringSize)
159 
160  tractList = list()
161  # ringNum denotes the closest ring to the specified coord
162  # I will check adjacent rings which may include the specified coord
163  for r in [ringNum - 1, ringNum, ringNum + 1]:
164  if r < 0 or r > self.config.numRings - 1:
165  continue
166  tractNum = int(math.fmod(ra - self.config.raStart, 2*math.pi) /
167  (2*math.pi/self._ringNums[r]) + 0.5)
168  # Adjacent tracts will also be checked.
169  for t in [tractNum - 1, tractNum, tractNum + 1]:
170  # Wrap over raStart
171  if t < 0:
172  t = t + self._ringNums[r]
173  elif t > self._ringNums[r] - 1:
174  t = t - self._ringNums[r]
175 
176  index = t + 1 # Allow 1 for south pole
177  for i in range(r):
178  index += self._ringNums[i]
179 
180  tract = self[index]
181  if tract.contains(coord):
182  tractList.append(tract)
183 
184  # Always check tracts at poles
185  # Southern cap is 0, Northern cap is the last entry in self
186  for entry in [0, len(self)-1]:
187  tract = self[entry]
188  if tract.contains(coord):
189  tractList.append(tract)
190 
191  return tractList
192 
193  def findTractPatchList(self, coordList):
194  """Find tracts and patches that overlap a region
195 
196  @param[in] coordList: list of sky coordinates (afwCoord.Coord)
197  @return list of (TractInfo, list of PatchInfo) for tracts and patches that contain,
198  or may contain, the specified region. The list will be empty if there is no overlap.
199 
200  @warning this uses a naive algorithm that may find some tracts and patches that do not overlap
201  the region (especially if the region is not a rectangle aligned along patch x,y).
202  """
203  retList = []
204  for coord in coordList:
205  for tractInfo in self.findAllTracts(coord):
206  patchList = tractInfo.findPatchList(coordList)
207  if patchList and not (tractInfo, patchList) in retList:
208  retList.append((tractInfo, patchList))
209  return retList
210 
211  def updateSha1(self, sha1):
212  """Add subclass-specific state or configuration options to the SHA1."""
213  sha1.update(struct.pack("<id", self.config.numRings, self.config.raStart))
def findTractPatchList(self, coordList)
Definition: ringsSkyMap.py:193
def __init__(self, config, version=0)
Definition: ringsSkyMap.py:53