Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# 

2# LSST Data Management System 

3# Copyright 2008, 2009, 2010, 2012 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__all__ = ["RingsSkyMapConfig", "RingsSkyMap"] 

24 

25import struct 

26import math 

27 

28from lsst.pex.config import Field 

29import lsst.geom as geom 

30from .cachingSkyMap import CachingSkyMap 

31from .tractInfo import ExplicitTractInfo 

32 

33 

34class RingsSkyMapConfig(CachingSkyMap.ConfigClass): 

35 """Configuration for the RingsSkyMap""" 

36 numRings = Field(dtype=int, doc="Number of rings", check=lambda x: x > 0) 36 ↛ exitline 36 didn't run the lambda on line 36

37 raStart = Field(dtype=float, default=0.0, doc="Starting center RA for each ring (degrees)", 37 ↛ exitline 37 didn't jump to the function exit

38 check=lambda x: x >= 0.0 and x < 360.0) 

39 

40 

41class RingsSkyMap(CachingSkyMap): 

42 """Rings sky map pixelization. 

43 

44 We divide the sphere into N rings of Declination, plus the two polar 

45 caps, which sets the size of the individual tracts. The rings are 

46 divided in RA into an integral number of tracts of this size; this 

47 division is made at the Declination closest to zero so as to ensure 

48 full overlap. 

49 

50 Rings are numbered in the rings from south to north. The south pole cap is 

51 ``tract=0``, then the tract at ``raStart`` in the southernmost ring is 

52 ``tract=1``. Numbering continues (in the positive RA direction) around that 

53 ring and then continues in the same fashion with the next ring north, and 

54 so on until all reaching the north pole cap, which is 

55 ``tract=len(skymap) - 1``. 

56 

57 However, ``version=0`` had a bug in the numbering of the tracts: the first 

58 and last tracts in the first (southernmost) ring were identical, and the 

59 first tract in the last (northernmost) ring was missing. When using 

60 ``version=0``, these tracts remain missing in order to preserve the 

61 numbering scheme. 

62 

63 Parameters 

64 ---------- 

65 config : `lsst.skymap.RingsSkyMapConfig` 

66 The configuration for this SkyMap. 

67 version : `int`, optional 

68 Software version of this class, to retain compatibility with old 

69 verisons. ``version=0`` covers the period from first implementation 

70 until DM-14809, at which point bugs were identified in the numbering 

71 of tracts (affecting only tracts at RA=0). ``version=1`` uses the 

72 post-DM-14809 tract numbering. 

73 """ 

74 ConfigClass = RingsSkyMapConfig 

75 _version = (1, 0) # for pickle 

76 

77 def __init__(self, config, version=1): 

78 assert version in (0, 1), "Unrecognised version: %s" % (version,) 

79 # We count rings from south to north 

80 # Note: pole caps together count for one additional ring when calculating the ring size 

81 self._ringSize = math.pi / (config.numRings + 1) # Size of a ring in Declination (radians) 

82 self._ringNums = [] # Number of tracts for each ring 

83 for i in range(config.numRings): 

84 startDec = self._ringSize*(i + 0.5) - 0.5*math.pi 

85 stopDec = startDec + self._ringSize 

86 dec = min(math.fabs(startDec), math.fabs(stopDec)) # Declination for determining division in RA 

87 self._ringNums.append(int(2*math.pi*math.cos(dec)/self._ringSize) + 1) 

88 numTracts = sum(self._ringNums) + 2 

89 super(RingsSkyMap, self).__init__(numTracts, config, version) 

90 self._raStart = self.config.raStart*geom.degrees 

91 

92 def getRingIndices(self, index): 

93 """Calculate ring indices given a numerical index of a tract. 

94 

95 The ring indices are the ring number and the tract number within 

96 the ring. 

97 

98 The ring number is -1 for the south polar cap and increases to the 

99 north. The north polar cap has ring number = numRings. The tract 

100 number is zero for either of the polar caps. 

101 """ 

102 if index == 0: # South polar cap 

103 return -1, 0 

104 if index == self._numTracts - 1: # North polar cap 

105 return self.config.numRings, 0 

106 if index < 0 or index >= self._numTracts: 

107 raise IndexError("Tract index %d is out of range [0, %d]" % (index, len(self) - 1)) 

108 ring = 0 # Ring number 

109 tractNum = index - 1 # Tract number within ring 

110 if self._version == 0: 

111 # Maintain the off-by-one bug in version=0 (DM-14809). 

112 # This means that the first tract in the first ring is duplicated 

113 # and the first tract in the last ring is missing. 

114 while ring < self.config.numRings and tractNum > self._ringNums[ring]: 

115 tractNum -= self._ringNums[ring] 

116 ring += 1 

117 else: 

118 while ring < self.config.numRings and tractNum >= self._ringNums[ring]: 

119 tractNum -= self._ringNums[ring] 

120 ring += 1 

121 return ring, tractNum 

122 

123 def generateTract(self, index): 

124 """Generate TractInfo for the specified tract index.""" 

125 ringNum, tractNum = self.getRingIndices(index) 

126 if ringNum == -1: # South polar cap 

127 ra, dec = 0, -0.5*math.pi 

128 elif ringNum == self.config.numRings: # North polar cap 

129 ra, dec = 0, 0.5*math.pi 

130 else: 

131 dec = self._ringSize*(ringNum + 1) - 0.5*math.pi 

132 ra = ((2*math.pi*tractNum/self._ringNums[ringNum])*geom.radians 

133 + self._raStart).wrap().asRadians() 

134 

135 center = geom.SpherePoint(ra, dec, geom.radians) 

136 wcs = self._wcsFactory.makeWcs(crPixPos=geom.Point2D(0, 0), crValCoord=center) 

137 return ExplicitTractInfo(index, self.config.patchInnerDimensions, self.config.patchBorder, center, 

138 0.5*self._ringSize*geom.radians, self.config.tractOverlap*geom.degrees, 

139 wcs) 

140 

141 def _decToRingNum(self, dec): 

142 """Calculate ring number from Declination. 

143 

144 Parameters 

145 ---------- 

146 dec : `lsst.geom.Angle` 

147 Declination. 

148 

149 Returns 

150 ------- 

151 ringNum : `int` 

152 Ring number: -1 for the south polar cap, and increasing to the 

153 north, ending with ``numRings`` for the north polar cap. 

154 """ 

155 firstRingStart = self._ringSize*0.5 - 0.5*math.pi 

156 if dec < firstRingStart: 

157 # Southern cap 

158 return -1 

159 elif dec > firstRingStart*-1: 

160 # Northern cap 

161 return self.config.numRings 

162 return int((dec.asRadians() - firstRingStart)/self._ringSize) 

163 

164 def _raToTractNum(self, ra, ringNum): 

165 """Calculate tract number from the Right Ascension. 

166 

167 Parameters 

168 ---------- 

169 ra : `lsst.geom.Angle` 

170 Right Ascension. 

171 ringNum : `int` 

172 Ring number (from ``_decToRingNum``). 

173 

174 Returns 

175 ------- 

176 tractNum : `int` 

177 Tract number within the ring (starts at 0 for the tract at raStart). 

178 """ 

179 if ringNum in (-1, self.config.numRings): 

180 return 0 

181 assert ringNum in range(self.config.numRings) 

182 tractNum = int((ra - self._raStart).wrap().asRadians() 

183 / (2*math.pi/self._ringNums[ringNum]) + 0.5) 

184 return 0 if tractNum == self._ringNums[ringNum] else tractNum # Allow wraparound 

185 

186 def findTract(self, coord): 

187 ringNum = self._decToRingNum(coord.getLatitude()) 

188 if ringNum == -1: 

189 # Southern cap 

190 return self[0] 

191 if ringNum == self.config.numRings: 

192 # Northern cap 

193 return self[self._numTracts - 1] 

194 tractNum = self._raToTractNum(coord.getLongitude(), ringNum) 

195 

196 if self._version == 0 and tractNum == 0 and ringNum != 0: 

197 # Account for off-by-one error in getRingIndices 

198 # Note that this means that tract 1 gets duplicated. 

199 ringNum += 1 

200 

201 index = sum(self._ringNums[:ringNum], tractNum + 1) # Allow 1 for south pole 

202 return self[index] 

203 

204 def findAllTracts(self, coord): 

205 """Find all tracts which include the specified coord. 

206 

207 Parameters 

208 ---------- 

209 coord : `lsst.geom.SpherePoint` 

210 ICRS sky coordinate to search for. 

211 

212 Returns 

213 ------- 

214 tractList : `list` of `TractInfo` 

215 The tracts which include the specified coord. 

216 """ 

217 ringNum = self._decToRingNum(coord.getLatitude()) 

218 

219 tractList = list() 

220 # ringNum denotes the closest ring to the specified coord 

221 # I will check adjacent rings which may include the specified coord 

222 for r in [ringNum - 1, ringNum, ringNum + 1]: 

223 if r < 0 or r >= self.config.numRings: 

224 # Poles will be checked explicitly outside this loop 

225 continue 

226 tractNum = self._raToTractNum(coord.getLongitude(), r) 

227 # Adjacent tracts will also be checked. 

228 for t in [tractNum - 1, tractNum, tractNum + 1]: 

229 # Wrap over raStart 

230 if t < 0: 

231 t = t + self._ringNums[r] 

232 elif t > self._ringNums[r] - 1: 

233 t = t - self._ringNums[r] 

234 

235 extra = 0 

236 if self._version == 0 and t == 0 and r != 0: 

237 # Account for off-by-one error in getRingIndices 

238 # Note that this means that tract 1 gets duplicated. 

239 extra = 1 

240 

241 index = sum(self._ringNums[:r + extra], t + 1) # Allow 1 for south pole 

242 tract = self[index] 

243 if tract.contains(coord): 

244 tractList.append(tract) 

245 

246 # Always check tracts at poles 

247 # Southern cap is 0, Northern cap is the last entry in self 

248 for entry in [0, len(self)-1]: 

249 tract = self[entry] 

250 if tract.contains(coord): 

251 tractList.append(tract) 

252 

253 return tractList 

254 

255 def findTractPatchList(self, coordList): 

256 retList = [] 

257 for coord in coordList: 

258 for tractInfo in self.findAllTracts(coord): 

259 patchList = tractInfo.findPatchList(coordList) 

260 if patchList and not (tractInfo, patchList) in retList: 

261 retList.append((tractInfo, patchList)) 

262 return retList 

263 

264 def updateSha1(self, sha1): 

265 """Add subclass-specific state or configuration options to the SHA1.""" 

266 sha1.update(struct.pack("<id", self.config.numRings, self.config.raStart))