23 __all__ = [
"TractInfo"]
 
   31 from .patchInfo 
import PatchInfo, makeSkyPolygonFromBBox
 
   35     """Information about a tract in a SkyMap sky pixelization 
   41     patchInnerDimensions : `tuple` of `int` 
   42         Dimensions of inner region of patches (x,y pixels). 
   44         Overlap between adjacent patches (in pixels) 
   45     ctrCoord : `lsst.geom.SpherePoint` 
   46         ICRS sky coordinate of center of inner region of tract; also used as 
   47         the CRVAL for the WCS. 
   48     vertexCoordList : `list` of `lsst.geom.SpherePoint` 
   49         Vertices that define the boundaries of the inner region. 
   50     tractOverlap : `lsst.geom.Angle` 
   51         Minimum overlap between adjacent sky tracts; this defines the minimum 
   52         distance the tract extends beyond the inner region in all directions. 
   53     wcs : `lsst.afw.image.SkyWcs` 
   54         WCS for tract. The reference pixel will be shifted as required so that 
   55         the lower left-hand pixel (index 0,0) has pixel position 0.0, 0.0. 
   59     The tract is subdivided into rectangular patches. Each patch has the 
   62     - An inner region defined by an inner bounding box. The inner regions of 
   63       the patches exactly tile the tract, and all inner regions have the same 
   64       dimensions. The tract is made larger as required to make this work. 
   66     - An outer region defined by an outer bounding box. The outer region 
   67       extends beyond the inner region by patchBorder pixels in all directions, 
   68       except there is no border at the edges of the tract. 
   69       Thus patches overlap each other but never extend off the tract. 
   70       If you do not want any overlap between adjacent patches then set 
   73     - An index that consists of a pair of integers: 
   75       * 0 <= x index < numPatches[0] 
   77       * 0 <= y index < numPatches[1] 
   79       Patch 0,0 is at the minimum corner of the tract bounding box. 
   81     - It is not enforced that ctrCoord is the center of vertexCoordList, but 
   85     def __init__(self, id, patchInnerDimensions, patchBorder, ctrCoord, vertexCoordList, tractOverlap, wcs):
 
   88             assert len(patchInnerDimensions) == 2
 
   91             raise TypeError(
"patchInnerDimensions=%s; must be two ints" % (patchInnerDimensions,))
 
  101     def _minimumBoundingBox(self, wcs):
 
  102         """Calculate the minimum bounding box for the tract, given the WCS. 
  104         The bounding box is created in the frame of the supplied WCS, 
  105         so that it's OK if the coordinates are negative. 
  107         We compute the bounding box that holds all the vertices and the 
  114                 minBBoxD.include(wcs.skyToPixel(vertexCoord))
 
  117                 angleIncr = 
geom.Angle(360.0, geom.degrees) / float(numAngles)
 
  118                 for i 
in range(numAngles):
 
  119                     offAngle = angleIncr * i
 
  120                     offCoord = vertexCoord.offset(offAngle, halfOverlap)
 
  121                     pixPos = wcs.skyToPixel(offCoord)
 
  122                     minBBoxD.include(pixPos)
 
  125     def _setupPatches(self, minBBox, wcs):
 
  126         """Setup for patches of a particular size. 
  128         We grow the bounding box to hold an exact multiple of 
  129         the desired size (patchInnerDimensions), while keeping 
  130         the center roughly the same.  We return the final 
  131         bounding box, and the number of patches in each dimension 
  136         minBBox : `lsst.geom.Box2I` 
  137             Minimum bounding box for tract 
  138         wcs : `lsst.afw.geom.SkyWcs` 
  143         bbox : `lsst.geom.Box2I 
  144             final bounding box, number of patches 
  148         bboxMin = bbox.getMin()
 
  149         bboxDim = bbox.getDimensions()
 
  152             num = (bboxDim[i] + innerDim - 1) // innerDim  
 
  153             deltaDim = (innerDim * num) - bboxDim[i]
 
  155                 bboxDim[i] = innerDim * num
 
  156                 bboxMin[i] -= deltaDim // 2
 
  159         return bbox, numPatches
 
  161     def _finalOrientation(self, bbox, wcs):
 
  162         """Determine the final orientation 
  164         We offset everything so the lower-left corner is at 0,0 
  165         and compute the final Wcs. 
  169         bbox : `lsst.geom.Box2I` 
  170             Current bounding box. 
  171         wcs : `lsst.afw.geom.SkyWcs 
  176         finalBBox : `lsst.geom.Box2I` 
  177             Revised bounding box. 
  178         wcs : `lsst.afw.geom.SkyWcs` 
  184         pixPosOffset = 
geom.Extent2D(finalBBox.getMinX() - bbox.getMinX(),
 
  185                                      finalBBox.getMinY() - bbox.getMinY())
 
  186         wcs = wcs.copyAtShiftedPixelOrigin(pixPosOffset)
 
  187         return finalBBox, wcs
 
  190         """Return a single integer that uniquely identifies the given patch 
  193         x, y = patchInfo.getIndex()
 
  199         x = sequentialIndex % nx
 
  200         y = (sequentialIndex - x) / nx
 
  204         """Find the patch containing the specified coord. 
  208         coord : `lsst.geom.SpherePoint` 
  209             ICRS sky coordinate to search for. 
  213         result : `lsst.skymap.PatchInfo` 
  214             PatchInfo of patch whose inner bbox contains the specified coord 
  219             If coord is not in tract or we cannot determine the 
  220             pixel coordinate (which likely means the coord is off the tract). 
  223             pixel = self.
getWcs().skyToPixel(coord)
 
  226             raise LookupError(
"Unable to determine pixel position for coordinate %s" % (coord,))
 
  229             raise LookupError(
"coord %s is not in tract %s" % (coord, self.
getId()))
 
  234         """Find patches containing the specified list of coords. 
  238         coordList : `list` of `lsst.geom.SpherePoint` 
  239             ICRS sky coordinates to search for. 
  243         result : `list` of `lsst.skymap.PatchInfo` 
  244             List of PatchInfo for patches that contain, or may contain, the 
  245             specified region. The list will be empty if there is no overlap. 
  251         - This may give incorrect answers on regions that are larger than a 
  254         - This uses a naive algorithm that may find some patches that do not 
  255           overlap the region (especially if the region is not a rectangle 
  256           aligned along patch x,y). 
  259         for coord 
in coordList:
 
  261                 pixelPos = self.
getWcs().skyToPixel(coord)
 
  265             box2D.include(pixelPos)
 
  275                      for xInd 
in range(llPatchInd[0], urPatchInd[0]+1)
 
  276                      for yInd 
in range(llPatchInd[1], urPatchInd[1]+1))
 
  279         """Get bounding box of tract (as an geom.Box2I) 
  284         """Get ICRS sky coordinate of center of tract 
  285         (as an lsst.geom.SpherePoint) 
  295         """Get the number of patches in x, y. 
  299         result : `tuple` of `int` 
  300             The number of patches in x, y 
  308         """Return information for the specified patch. 
  312         index : `tuple` of `int` 
  313             Index of patch, as a pair of ints; 
  314             or a sequential index as returned by getSequentialPatchIndex; 
  315             negative values are not supported. 
  319         result : `lsst.skymap.PatchInfo` 
  320             The patch info for that index. 
  325             If index is out of range. 
  327         if isinstance(index, numbers.Number):
 
  331             raise IndexError(
"Patch index %s is not in range [0-%d, 0-%d]" %
 
  335         if not self._bbox.
contains(innerBBox):
 
  337                 "Bug: patch index %s valid but inner bbox=%s not contained in tract bbox=%s" %
 
  338                 (index, innerBBox, self._bbox))
 
  341         outerBBox.clip(self._bbox)
 
  349         """Get dimensions of inner region of the patches (all are the same) 
  354         """Get minimum overlap of adjacent sky tracts. 
  359         """Get list of ICRS sky coordinates of vertices that define the 
  360         boundary of the inner region. 
  364         **warning:** this is not a deep copy. 
  369         """Get inner on-sky region as a sphgeom.ConvexPolygon. 
  371         skyUnitVectors = [sp.getVector() 
for sp 
in self.
getVertexList()]
 
  372         return ConvexPolygon.convexHull(skyUnitVectors)
 
  375         """Get outer on-sky region as a sphgeom.ConvexPolygon 
  384         wcs : `lsst.afw.geom.SkyWcs` 
  385             The WCS of this tract 
  390         return "TractInfo(id=%s)" % (self.
_id,)
 
  393         return "TractInfo(id=%s, ctrCoord=%s)" % (self.
_id, self.
_ctrCoord.getVector())
 
  397         for y 
in range(yNum):
 
  398             for x 
in range(xNum):
 
  409         """Does this tract contain the coordinate?""" 
  411             pixels = self.
getWcs().skyToPixel(coord)
 
  419     """Information for a tract specified explicitly. 
  421     A tract is placed at the explicitly defined coordinates, with the nominated 
  422     radius.  The tracts are square (i.e., the radius is really a half-size). 
  425     def __init__(self, ident, patchInnerDimensions, patchBorder, ctrCoord, radius, tractOverlap, wcs):
 
  429         super(ExplicitTractInfo, self).
__init__(ident, patchInnerDimensions, patchBorder, ctrCoord,
 
  430                                                 vertexList, tractOverlap, wcs)
 
  437     def _minimumBoundingBox(self, wcs):
 
  438         """Calculate the minimum bounding box for the tract, given the WCS, and 
  439         the nominated radius. 
  444             pixPos = wcs.skyToPixel(cornerCoord)