lsst.skymap  14.0-2-g8373656+8
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 math
25 
26 from lsst.pex.config import Field
27 from lsst.afw.coord import IcrsCoord
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 = IcrsCoord(ra*afwGeom.radians, 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  icrsCoord = coord.toIcrs()
125  ra = icrsCoord.getLongitude().asRadians()
126  dec = icrsCoord.getLatitude().asRadians()
127 
128  firstRingStart = self._ringSize*0.5 - 0.5*math.pi
129  if dec < firstRingStart:
130  # Southern cap
131  return self[0]
132  elif dec > firstRingStart*-1:
133  # Northern cap
134  return self[-1]
135 
136  ringNum = int((dec - firstRingStart)/self._ringSize)
137  tractNum = int(math.fmod(ra - self.config.raStart, 2*math.pi) /
138  (2*math.pi/self._ringNums[ringNum]) + 0.5)
139 
140  index = tractNum + 1 # Allow 1 for south pole
141  for i in range(ringNum):
142  index += self._ringNums[i]
143 
144  return self[index]
145 
146  def findAllTracts(self, coord):
147  """Find all tracts which include the specified coord.
148 
149  @param[in] coord: sky coordinate (afwCoord.Coord)
150  @return List of TractInfo of tracts which include the specified coord
151 
152  @note
153  - This routine will be more efficient if coord is ICRS.
154  """
155  icrsCoord = coord.toIcrs()
156  ra = icrsCoord.getLongitude().asRadians()
157  dec = icrsCoord.getLatitude().asRadians()
158 
159  firstRingStart = self._ringSize*0.5 - 0.5*math.pi
160 
161  ringNum = int((dec - firstRingStart)/self._ringSize)
162 
163  tractList = list()
164  # ringNum denotes the closest ring to the specified coord
165  # I will check adjacent rings which may include the specified coord
166  for r in [ringNum - 1, ringNum, ringNum + 1]:
167  if r < 0 or r > self.config.numRings - 1:
168  continue
169  tractNum = int(math.fmod(ra - self.config.raStart, 2*math.pi) /
170  (2*math.pi/self._ringNums[r]) + 0.5)
171  # Adjacent tracts will also be checked.
172  for t in [tractNum - 1, tractNum, tractNum + 1]:
173  # Wrap over raStart
174  if t < 0:
175  t = t + self._ringNums[r]
176  elif t > self._ringNums[r] - 1:
177  t = t - self._ringNums[r]
178 
179  index = t + 1 # Allow 1 for south pole
180  for i in range(r):
181  index += self._ringNums[i]
182 
183  tract = self[index]
184  if tract.contains(icrsCoord):
185  tractList.append(tract)
186 
187  # Always check tracts at poles
188  # Southern cap is 0, Northern cap is the last entry in self
189  for entry in [0, len(self)-1]:
190  tract = self[entry]
191  if tract.contains(icrsCoord):
192  tractList.append(tract)
193 
194  return tractList
195 
196  def findTractPatchList(self, coordList):
197  """Find tracts and patches that overlap a region
198 
199  @param[in] coordList: list of sky coordinates (afwCoord.Coord)
200  @return list of (TractInfo, list of PatchInfo) for tracts and patches that contain,
201  or may contain, the specified region. The list will be empty if there is no overlap.
202 
203  @warning this uses a naive algorithm that may find some tracts and patches that do not overlap
204  the region (especially if the region is not a rectangle aligned along patch x,y).
205  """
206  retList = []
207  for coord in coordList:
208  for tractInfo in self.findAllTracts(coord):
209  patchList = tractInfo.findPatchList(coordList)
210  if patchList and not (tractInfo, patchList) in retList:
211  retList.append((tractInfo, patchList))
212  return retList
def findTractPatchList(self, coordList)
Definition: ringsSkyMap.py:196
def __init__(self, config, version=0)
Definition: ringsSkyMap.py:54