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