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