lsst.skymap  14.0-4-g3609236+6
baseSkyMap.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008, 2009, 2010 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 @todo
24 - Consider tweaking pixel scale so the average scale is as specified, rather than the scale at the center
25 """
26 import hashlib
27 import struct
28 
29 from builtins import object
30 import lsst.pex.config as pexConfig
31 import lsst.afw.geom as afwGeom
32 from . import detail
33 
34 __all__ = ["BaseSkyMap"]
35 
36 
37 class BaseSkyMapConfig(pexConfig.Config):
38  patchInnerDimensions = pexConfig.ListField(
39  doc="dimensions of inner region of patches (x,y pixels)",
40  dtype=int,
41  length=2,
42  default=(4000, 4000),
43  )
44  patchBorder = pexConfig.Field(
45  doc="border between patch inner and outer bbox (pixels)",
46  dtype=int,
47  default=100,
48  )
49  tractOverlap = pexConfig.Field(
50  doc="minimum overlap between adjacent sky tracts, on the sky (deg)",
51  dtype=float,
52  default=1.0,
53  )
54  pixelScale = pexConfig.Field(
55  doc="nominal pixel scale (arcsec/pixel)",
56  dtype=float,
57  default=0.333
58  )
59  projection = pexConfig.Field(
60  doc="""one of the FITS WCS projection codes, such as:
61  - STG: stereographic projection
62  - MOL: Molleweide's projection
63  - TAN: tangent-plane projection
64  """,
65  dtype=str,
66  default="STG",
67  )
68  rotation = pexConfig.Field(
69  doc="Rotation for WCS (deg)",
70  dtype=float,
71  default=0,
72  )
73 
74 
75 class BaseSkyMap(object):
76  """A collection of overlapping Tracts that map part or all of the sky.
77 
78  See TractInfo for more information.
79 
80  BaseSkyMap is an abstract base class. Subclasses must do the following:
81  @li define __init__ and have it construct the TractInfo objects and put them in _tractInfoList
82  @li define __getstate__ and __setstate__ to allow pickling (the butler saves sky maps using pickle);
83  see DodecaSkyMap for an example of how to do this. (Most of that code could be moved
84  into this base class, but that would make it harder to handle older versions of pickle data.)
85  @li define updateSha1 to add any subclass-specific state to the hash.
86 
87  All SkyMap subclasses must be conceptually immutable; they must always
88  refer to the same set of mathematical tracts and patches even if the in-
89  memory representation of those objects changes.
90  """
91  ConfigClass = BaseSkyMapConfig
92 
93  def __init__(self, config=None):
94  """Construct a BaseSkyMap
95 
96  @param[in] config: an instance of self.ConfigClass; if None the default config is used
97  """
98  if config is None:
99  config = self.ConfigClass()
100  config.freeze() # just to be sure, e.g. for pickling
101  self.config = config
102  self._tractInfoList = []
104  pixelScale=afwGeom.Angle(self.config.pixelScale, afwGeom.arcseconds),
105  projection=self.config.projection,
106  rotation=afwGeom.Angle(self.config.rotation, afwGeom.degrees),
107  )
108  self._sha1 = None
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  distTractInfoList = []
126  for tractInfo in self:
127  angSep = icrsCoord.angularSeparation(tractInfo.getCtrCoord()).asDegrees()
128  distTractInfoList.append((angSep, tractInfo))
129  distTractInfoList.sort()
130  return distTractInfoList[0][1]
131 
132  def findTractPatchList(self, coordList):
133  """Find tracts and patches that overlap a region
134 
135  @param[in] coordList: list of sky coordinates (afwCoord.Coord)
136  @return list of (TractInfo, list of PatchInfo) for tracts and patches that contain,
137  or may contain, the specified region. The list will be empty if there is no overlap.
138 
139  @warning this uses a naive algorithm that may find some tracts and patches that do not overlap
140  the region (especially if the region is not a rectangle aligned along patch x,y).
141  """
142  retList = []
143  for tractInfo in self:
144  patchList = tractInfo.findPatchList(coordList)
145  if patchList:
146  retList.append((tractInfo, patchList))
147  return retList
148 
149  def findClosestTractPatchList(self, coordList):
150  """Find closest tract and patches that overlap coordinates
151 
152  @param[in] coordList: list of sky coordinates (afwCoord.Coord)
153  @return list of (TractInfo, list of PatchInfo) for tracts and patches that contain,
154  or may contain, the specified region. The list will be empty if there is no overlap.
155  """
156  retList = []
157  for coord in coordList:
158  tractInfo = self.findTract(coord)
159  patchList = tractInfo.findPatchList(coordList)
160  if patchList and not (tractInfo, patchList) in retList:
161  retList.append((tractInfo, patchList))
162  return retList
163 
164  def __getitem__(self, ind):
165  return self._tractInfoList[ind]
166 
167  def __iter__(self):
168  return iter(self._tractInfoList)
169 
170  def __len__(self):
171  return len(self._tractInfoList)
172 
173  def __hash__(self):
174  return hash(self.getSha1())
175 
176  def __eq__(self, other):
177  try:
178  return self.getSha1() == other.getSha1()
179  except AttributeError:
180  return NotImplemented
181 
182  def __ne__(self, other):
183  return not (self == other)
184 
185  def getSha1(self):
186  """Return a SHA1 hash that uniquely identifies this SkyMap instance.
187 
188  Returns
189  -------
190  sha1 : bytes
191  A 20-byte hash that uniquely identifies this SkyMap instance.
192 
193  Subclasses should almost always override `updateSha1()` instead of
194  this function to add subclass-specific state to the hash.
195  """
196  if self._sha1 is None:
197  sha1 = hashlib.sha1()
198  sha1.update(type(self).__name__.encode('utf-8'))
199  configPacked = struct.pack(
200  "<iiidd3sd",
201  self.config.patchInnerDimensions[0],
202  self.config.patchInnerDimensions[1],
203  self.config.patchBorder,
204  self.config.tractOverlap,
205  self.config.pixelScale,
206  self.config.projection.encode('ascii'),
207  self.config.rotation
208  )
209  sha1.update(configPacked)
210  self.updateSha1(sha1)
211  self._sha1 = sha1.digest()
212  return self._sha1
213 
214  def updateSha1(self, sha1):
215  """Add subclass-specific state or configuration options to the SHA1.
216 
217  Parameters
218  ----------
219  sha1 : hashlib.sha1
220  A hashlib object on which `update()` can be called to add
221  additional state to the hash.
222 
223  This method is conceptually "protected": it should be reimplemented by
224  all subclasses, but called only by the base class implementation of
225  `getSha1()`.
226  """
227  raise NotImplementedError()
def findTractPatchList(self, coordList)
Definition: baseSkyMap.py:132
def findClosestTractPatchList(self, coordList)
Definition: baseSkyMap.py:149
def __init__(self, config=None)
Definition: baseSkyMap.py:93