lsst.skymap  14.0-5-g3f35923+7
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: ICRS sky coordinate (lsst.afw.geom.SpherePoint)
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  distTractInfoList = []
125  for tractInfo in self:
126  angSep = coord.separation(tractInfo.getCtrCoord()).asDegrees()
127  distTractInfoList.append((angSep, tractInfo))
128  distTractInfoList.sort()
129  return distTractInfoList[0][1]
130 
131  def findTractPatchList(self, coordList):
132  """Find tracts and patches that overlap a region
133 
134  @param[in] coordList: list of ICRS sky coordinates (lsst.afw.geom.SpherePoint)
135  @return list of (TractInfo, list of PatchInfo) for tracts and patches that contain,
136  or may contain, the specified region. The list will be empty if there is no overlap.
137 
138  @warning this uses a naive algorithm that may find some tracts and patches that do not overlap
139  the region (especially if the region is not a rectangle aligned along patch x,y).
140  """
141  retList = []
142  for tractInfo in self:
143  patchList = tractInfo.findPatchList(coordList)
144  if patchList:
145  retList.append((tractInfo, patchList))
146  return retList
147 
148  def findClosestTractPatchList(self, coordList):
149  """Find closest tract and patches that overlap coordinates
150 
151  @param[in] coordList: list of ICRS sky coordinates (lsst.afw.geom.SpherePoint)
152  @return list of (TractInfo, list of PatchInfo) for tracts and patches that contain,
153  or may contain, the specified region. The list will be empty if there is no overlap.
154  """
155  retList = []
156  for coord in coordList:
157  tractInfo = self.findTract(coord)
158  patchList = tractInfo.findPatchList(coordList)
159  if patchList and not (tractInfo, patchList) in retList:
160  retList.append((tractInfo, patchList))
161  return retList
162 
163  def __getitem__(self, ind):
164  return self._tractInfoList[ind]
165 
166  def __iter__(self):
167  return iter(self._tractInfoList)
168 
169  def __len__(self):
170  return len(self._tractInfoList)
171 
172  def __hash__(self):
173  return hash(self.getSha1())
174 
175  def __eq__(self, other):
176  try:
177  return self.getSha1() == other.getSha1()
178  except AttributeError:
179  return NotImplemented
180 
181  def __ne__(self, other):
182  return not (self == other)
183 
184  def getSha1(self):
185  """Return a SHA1 hash that uniquely identifies this SkyMap instance.
186 
187  Returns
188  -------
189  sha1 : bytes
190  A 20-byte hash that uniquely identifies this SkyMap instance.
191 
192  Subclasses should almost always override `updateSha1()` instead of
193  this function to add subclass-specific state to the hash.
194  """
195  if self._sha1 is None:
196  sha1 = hashlib.sha1()
197  sha1.update(type(self).__name__.encode('utf-8'))
198  configPacked = struct.pack(
199  "<iiidd3sd",
200  self.config.patchInnerDimensions[0],
201  self.config.patchInnerDimensions[1],
202  self.config.patchBorder,
203  self.config.tractOverlap,
204  self.config.pixelScale,
205  self.config.projection.encode('ascii'),
206  self.config.rotation
207  )
208  sha1.update(configPacked)
209  self.updateSha1(sha1)
210  self._sha1 = sha1.digest()
211  return self._sha1
212 
213  def updateSha1(self, sha1):
214  """Add subclass-specific state or configuration options to the SHA1.
215 
216  Parameters
217  ----------
218  sha1 : hashlib.sha1
219  A hashlib object on which `update()` can be called to add
220  additional state to the hash.
221 
222  This method is conceptually "protected": it should be reimplemented by
223  all subclasses, but called only by the base class implementation of
224  `getSha1()`.
225  """
226  raise NotImplementedError()
def findTractPatchList(self, coordList)
Definition: baseSkyMap.py:131
def findClosestTractPatchList(self, coordList)
Definition: baseSkyMap.py:148
def __init__(self, config=None)
Definition: baseSkyMap.py:93