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

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

""" 

This file defines classes that control who ObservationMetaData describes 

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

""" 

from __future__ import division 

from builtins import str 

from builtins import object 

 

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

# future 

 

import numpy as np 

from future.utils import with_metaclass 

 

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

 

 

class SpatialBoundsMetaClass(type): 

""" 

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

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

dictionary key. 

""" 

 

# Largely this is being copied from the DBObjectMeta class in 

# dbConnection.py 

 

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

if not hasattr(cls, 'SBregistry'): 

cls.SBregistry = {} 

else: 

cls.SBregistry[cls.boundType] = cls 

 

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

 

 

class SpatialBounds(with_metaclass(SpatialBoundsMetaClass, object)): 

""" 

Classes inheriting from this class define spatial bounds on the objects 

contained within a catalog. They also translate those bounds into 

constraints on SQL queries made by the query_columns method in 

CatalogDBobject (see dbConnnection.py) 

 

Daughter classes of this class need the following: 

 

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

registry of FieldOfView classes 

 

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

length. Init should then construct the parameters defining the bound 

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

 

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

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

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

""" 

 

def __init__(self, *args): 

""" 

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

the bounds 

 

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

 

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

 

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

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

""" 

 

raise NotImplementedError() 

 

def to_SQL(self, *args): 

""" 

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

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

query that only selects the region of RA and Dec desired 

 

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

 

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

 

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

""" 

 

raise NotImplementedError() 

 

def __eq__(self, other): 

for param in self.__dict__: 

if param not in other.__dict__: 

return False 

 

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

return False 

 

for param in other.__dict__: 

if param not in self.__dict__: 

return False 

 

return True 

 

def __ne__(self, other): 

return not self.__eq__(other) 

 

@classmethod 

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

if name in self.SBregistry: 

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

else: 

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

 

 

class CircleBounds(SpatialBounds): 

 

boundType = 'circle' 

 

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

""" 

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

the bounds 

 

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

 

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

 

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

""" 

 

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

try: 

ra = np.float(ra) 

except: 

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

 

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

try: 

dec = np.float(dec) 

except: 

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

 

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

try: 

radius = np.float(radius) 

except: 

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

 

self.RA = ra 

self.DEC = dec 

self.radius = radius 

 

self.RAdeg = np.degrees(ra) 

self.DECdeg = np.degrees(dec) 

self.radiusdeg = np.degrees(radius) 

 

def __eq__(self, other): 

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

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

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

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

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

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

(self.radiusdeg == other.radiusdeg) 

 

def to_SQL(self, RAname, DECname): 

 

cosDec = np.cos(self.DEC) 

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

 

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

RAmax = self.RAdeg + 2.0*adjusted_radius 

RAmin = self.RAdeg - 2.0*adjusted_radius 

else: 

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

RAmax = 361.0 

RAmin = -361.0 

 

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

RAmin<adjusted_radius or 

RAmax>360.0-adjusted_radius): 

 

RAmax = 361.0 

RAmin = -361.0 

 

DECmax = self.DECdeg + self.radiusdeg 

DECmin = self.DECdeg - self.radiusdeg 

 

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

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

if RAmax-RAmin<361.0: 

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

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

else: 

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

 

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

# the desired radius. See 

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

bound = bound + \ 

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

(DECname, self.DECdeg)) 

bound = bound + \ 

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

(DECname, self.DECdeg)) 

bound = bound + \ 

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

(RAname, self.RAdeg)) 

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

 

return bound 

 

 

class BoxBounds(SpatialBounds): 

 

boundType = 'box' 

 

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

""" 

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

the bounds 

 

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

 

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

 

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

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

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

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

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

""" 

 

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

try: 

ra = np.float(ra) 

except: 

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

 

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

try: 

dec = np.float(dec) 

except: 

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

 

self.RA = ra 

self.DEC = dec 

 

self.RAdeg = np.degrees(ra) 

self.DECdeg = np.degrees(dec) 

 

try: 

if hasattr(length, '__len__'): 

if len(length) == 1: 

lengthRAdeg = np.degrees(length[0]) 

lengthDECdeg = np.degrees(length[0]) 

else: 

lengthRAdeg = np.degrees(length[0]) 

lengthDECdeg = np.degrees(length[1]) 

else: 

length = np.float(length) 

lengthRAdeg = np.degrees(length) 

lengthDECdeg = np.degrees(length) 

 

except: 

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

str(length), type(length))) 

 

self.RAminDeg = self.RAdeg - lengthRAdeg 

self.RAmaxDeg = self.RAdeg + lengthRAdeg 

self.DECminDeg = self.DECdeg - lengthDECdeg 

self.DECmaxDeg = self.DECdeg + lengthDECdeg 

 

self.RAminDeg %= 360.0 

self.RAmaxDeg %= 360.0 

 

def __eq__(self, other): 

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

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

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

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

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

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

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

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

(self.DECmaxDeg == other.DECmaxDeg) 

 

def to_SQL(self, RAname, DECname): 

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

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

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

# (RAmin, RAmax, DECmin, DECmax)) 

 

# Special case where the whole region is selected 

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

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

DECname, self.DECminDeg, self.DECmaxDeg) 

return bound 

 

if self.RAminDeg > self.RAmaxDeg: 

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

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

DECname, self.DECminDeg, self.DECmaxDeg)) 

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

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

else: 

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

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

 

return bound