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""" 

2This file defines classes that control who ObservationMetaData describes 

3its field of view (i.e. is it a box in RA, Dec, is it a circle in RA, Dec....?) 

4""" 

5from __future__ import division 

6from builtins import str 

7from builtins import object 

8 

9# Hopefully it will be extensible so that we can add different shapes in the 

10# future 

11 

12import numpy as np 

13from future.utils import with_metaclass 

14 

15__all__ = ["SpatialBounds", "CircleBounds", "BoxBounds"] 

16 

17 

18class SpatialBoundsMetaClass(type): 

19 """ 

20 Meta class for fieldOfView. This class builds a registry of all 

21 valid fields of view so that fields can be instantiated from just a 

22 dictionary key. 

23 """ 

24 

25 # Largely this is being copied from the DBObjectMeta class in 

26 # dbConnection.py 

27 

28 def __init__(cls, name, bases, dct): 

29 if not hasattr(cls, 'SBregistry'): 

30 cls.SBregistry = {} 

31 else: 

32 cls.SBregistry[cls.boundType] = cls 

33 

34 return super(SpatialBoundsMetaClass, cls).__init__(name, bases, dct) 

35 

36 

37class SpatialBounds(with_metaclass(SpatialBoundsMetaClass, object)): 

38 """ 

39 Classes inheriting from this class define spatial bounds on the objects 

40 contained within a catalog. They also translate those bounds into 

41 constraints on SQL queries made by the query_columns method in 

42 CatalogDBobject (see dbConnnection.py) 

43 

44 Daughter classes of this class need the following: 

45 

46 self.boundType = a string by which the class is identified in the 

47 registry of FieldOfView classes 

48 

49 __init__() that accepts (in this order) RA, Dec, and characteristic 

50 length. Init should then construct the parameters defining the bound 

51 however is appropriate (e.g. setting self.RAmax and self.RAmin for a box) 

52 

53 to_SQL() = a method that accepts RAcolname and DECcolname (strings denoting 

54 the names of the database columns containing RA and DEc) and which returns 

55 a string that characterizes the bound as an SQL 'WHERE' statement. 

56 """ 

57 

58 def __init__(self, *args): 

59 """ 

60 Accepts a center point and a characteristic length defining the extent of 

61 the bounds 

62 

63 @param[in] ra is the center RA in radians 

64 

65 @param[in] dec is the center Dec in radians 

66 

67 @param[in] length is either a single characteristic length (in radians) 

68 or a list of characteristic lengths defining the shape of the bound 

69 """ 

70 

71 raise NotImplementedError() 

72 

73 def to_SQL(self, *args): 

74 """ 

75 Accepts the names of the columns referring to RA and Dec in the database. 

76 Uses the stored RA, Dec, and length for this object to return an SQL 

77 query that only selects the region of RA and Dec desired 

78 

79 @param[in] RAname a string; the name of the RA column in the database 

80 

81 @param[in] DECname a string; the name of the Dec column in the database 

82 

83 @returns a string; an SQL query that only selects the desired region in RA, Dec 

84 """ 

85 

86 raise NotImplementedError() 

87 

88 def __eq__(self, other): 

89 for param in self.__dict__: 

90 if param not in other.__dict__: 

91 return False 

92 

93 if self.__dict__[param] != other.__dict__[param]: 

94 return False 

95 

96 for param in other.__dict__: 

97 if param not in self.__dict__: 

98 return False 

99 

100 return True 

101 

102 def __ne__(self, other): 

103 return not self.__eq__(other) 

104 

105 @classmethod 

106 def getSpatialBounds(self, name, *args, **kwargs): 

107 if name in self.SBregistry: 

108 return self.SBregistry[name](*args, **kwargs) 

109 else: 

110 raise RuntimeError("There is no SpatialBounds class keyed to %s" % name) 

111 

112 

113class CircleBounds(SpatialBounds): 

114 

115 boundType = 'circle' 

116 

117 def __init__(self, ra, dec, radius): 

118 """ 

119 Accepts a center point and a characteristic length defining the extent of 

120 the bounds 

121 

122 @param[in] ra is the center RA in radians 

123 

124 @param[in] dec is the center Dec in radians 

125 

126 @param[in] length is the radius of the field of view in radians 

127 """ 

128 

129 if not (isinstance(ra, float) or isinstance(ra, np.float)): 

130 try: 

131 ra = np.float(ra) 

132 except: 

133 raise RuntimeError('In CircleBounds, ra must be a float; you have %s' % type(ra)) 

134 

135 if not (isinstance(dec, float) or isinstance(dec, np.float)): 

136 try: 

137 dec = np.float(dec) 

138 except: 

139 raise RuntimeError('In CircleBounds, dec must be a float; you have %s' % type(dec)) 

140 

141 if not (isinstance(radius, float) or isinstance(radius, np.float)): 

142 try: 

143 radius = np.float(radius) 

144 except: 

145 raise RuntimeError('In CircleBounds, radius must be a float; you have %s' % type(radius)) 

146 

147 self.RA = ra 

148 self.DEC = dec 

149 self.radius = radius 

150 

151 self.RAdeg = np.degrees(ra) 

152 self.DECdeg = np.degrees(dec) 

153 self.radiusdeg = np.degrees(radius) 

154 

155 def __eq__(self, other): 

156 return (type(self) == type(other)) and \ 

157 (self.RA == other.RA) and \ 

158 (self.DEC == other.DEC) and \ 

159 (self.radius == other.radius) and \ 

160 (self.RAdeg == other.RAdeg) and \ 

161 (self.DECdeg == other.DECdeg) and \ 

162 (self.radiusdeg == other.radiusdeg) 

163 

164 def to_SQL(self, RAname, DECname): 

165 

166 cosDec = np.cos(self.DEC) 

167 adjusted_radius = np.abs(np.degrees(np.arcsin(np.sin(self.radius)/cosDec))) 

168 

169 if np.abs(cosDec) > 1.0e-20: 

170 RAmax = self.RAdeg + 2.0*adjusted_radius 

171 RAmin = self.RAdeg - 2.0*adjusted_radius 

172 else: 

173 # just in case, for some reason, we are looking at the poles 

174 RAmax = 361.0 

175 RAmin = -361.0 

176 

177 if (np.isnan(RAmax) or np.isnan(RAmin) or 

178 RAmin<adjusted_radius or 

179 RAmax>360.0-adjusted_radius): 

180 

181 RAmax = 361.0 

182 RAmin = -361.0 

183 

184 DECmax = self.DECdeg + self.radiusdeg 

185 DECmin = self.DECdeg - self.radiusdeg 

186 

187 # initially demand that all objects are within a box containing the circle 

188 # set from the DEC1=DEC2 and RA1=RA2 limits of the haversine function 

189 if RAmax-RAmin<361.0: 

190 bound = ("%s between %f and %f and %s between %f and %f " 

191 % (RAname, RAmin, RAmax, DECname, DECmin, DECmax)) 

192 else: 

193 bound = ('%s between %f and %f ' % (DECname, DECmin, DECmax)) 

194 

195 # then use the Haversine function to constrain the angular distance form boresite to be within 

196 # the desired radius. See 

197 # http://en.wikipedia.org/wiki/Haversine_formula 

198 bound = bound + \ 

199 ("and 2 * ASIN(SQRT( POWER(SIN(0.5*(%s - %s) * PI() / 180.0),2)" % 

200 (DECname, self.DECdeg)) 

201 bound = bound + \ 

202 ("+ COS(%s * PI() / 180.0) * COS(%s * PI() / 180.0) " % 

203 (DECname, self.DECdeg)) 

204 bound = bound + \ 

205 ("* POWER(SIN(0.5 * (%s - %s) * PI() / 180.0),2)))" % 

206 (RAname, self.RAdeg)) 

207 bound = bound + (" < %s " % self.radius) 

208 

209 return bound 

210 

211 

212class BoxBounds(SpatialBounds): 

213 

214 boundType = 'box' 

215 

216 def __init__(self, ra, dec, length): 

217 """ 

218 Accepts a center point and a characteristic length defining the extent of 

219 the bounds 

220 

221 @param[in] ra is the center RA in radians 

222 

223 @param[in] dec is the center Dec in radians 

224 

225 @param[in] length is either a single characteristic length (in radians) 

226 or a list of characteristic lengths defining the shape of the bound. 

227 If a single value, the field of view will be a square with side of 2 x length. 

228 If it is a list/tuple/array, the field of view will be a rectangle with side lengths 

229 RA = 2 x length[0] and Dec = 2 x length[1] 

230 """ 

231 

232 if not (isinstance(ra, float) or isinstance(ra, np.float)): 

233 try: 

234 ra = np.float(ra) 

235 except: 

236 raise RuntimeError('In BoxBounds ra must be a float; you have %s' % type(ra)) 

237 

238 if not (isinstance(dec, float) or isinstance(dec, np.float)): 

239 try: 

240 dec = np.float(dec) 

241 except: 

242 raise RuntimeError('In BoxBounds dec must be a float; you have %s' % type(dec)) 

243 

244 self.RA = ra 

245 self.DEC = dec 

246 

247 self.RAdeg = np.degrees(ra) 

248 self.DECdeg = np.degrees(dec) 

249 

250 try: 

251 if hasattr(length, '__len__'): 

252 if len(length) == 1: 

253 lengthRAdeg = np.degrees(length[0]) 

254 lengthDECdeg = np.degrees(length[0]) 

255 else: 

256 lengthRAdeg = np.degrees(length[0]) 

257 lengthDECdeg = np.degrees(length[1]) 

258 else: 

259 length = np.float(length) 

260 lengthRAdeg = np.degrees(length) 

261 lengthDECdeg = np.degrees(length) 

262 

263 except: 

264 raise RuntimeError("BoxBounds is unsure how to handle length %s type: %s" % ( 

265 str(length), type(length))) 

266 

267 self.RAminDeg = self.RAdeg - lengthRAdeg 

268 self.RAmaxDeg = self.RAdeg + lengthRAdeg 

269 self.DECminDeg = self.DECdeg - lengthDECdeg 

270 self.DECmaxDeg = self.DECdeg + lengthDECdeg 

271 

272 self.RAminDeg %= 360.0 

273 self.RAmaxDeg %= 360.0 

274 

275 def __eq__(self, other): 

276 return (type(self) == type(other)) and \ 

277 (self.RA == other.RA) and \ 

278 (self.RAdeg == other.RAdeg) and \ 

279 (self.DEC == other.DEC) and \ 

280 (self.DECdeg == other.DECdeg) and \ 

281 (self.RAminDeg == other.RAminDeg) and \ 

282 (self.RAmaxDeg == other.RAmaxDeg) and \ 

283 (self.DECminDeg == other.DECminDeg) and \ 

284 (self.DECmaxDeg == other.DECmaxDeg) 

285 

286 def to_SQL(self, RAname, DECname): 

287 # KSK: I don't know exactly what we do here. This is in code, but operating 

288 # on a database is it less confusing to work in degrees or radians? 

289 # (RAmin, RAmax, DECmin, DECmax) = map(math.radians, 

290 # (RAmin, RAmax, DECmin, DECmax)) 

291 

292 # Special case where the whole region is selected 

293 if self.RAminDeg < 0 and self.RAmaxDeg > 360.: 

294 bound = "%s between %f and %f" % ( 

295 DECname, self.DECminDeg, self.DECmaxDeg) 

296 return bound 

297 

298 if self.RAminDeg > self.RAmaxDeg: 

299 bound = ("%s not between %f and %f and %s between %f and %f" 

300 % (RAname, self.RAmaxDeg, self.RAminDeg, 

301 DECname, self.DECminDeg, self.DECmaxDeg)) 

302 bound += (" and %s+360.0 not between %f and %f" % 

303 (RAname, self.RAmaxDeg, self.RAminDeg)) 

304 else: 

305 bound = ("%s between %f and %f and %s between %f and %f" 

306 % (RAname, self.RAminDeg, self.RAmaxDeg, DECname, self.DECminDeg, self.DECmaxDeg)) 

307 

308 return bound