1
|
"""module for coordinate manipulation (conversions, calculations etc.)
|
2
|
|
3
|
(c) 2007-2012 Matt Hilton
|
4
|
|
5
|
(c) 2013 Matt Hilton & Steven Boada
|
6
|
|
7
|
U{http://astlib.sourceforge.net}
|
8
|
|
9
|
"""
|
10
|
|
11
|
import sys
|
12
|
import math
|
13
|
import numpy
|
14
|
import string
|
15
|
from PyWCSTools import wcscon
|
16
|
|
17
|
|
18
|
def hms2decimal(RAString, delimiter):
|
19
|
"""Converts a delimited string of Hours:Minutes:Seconds format into decimal
|
20
|
degrees.
|
21
|
|
22
|
@type RAString: string
|
23
|
@param RAString: coordinate string in H:M:S format
|
24
|
@type delimiter: string
|
25
|
@param delimiter: delimiter character in RAString
|
26
|
@rtype: float
|
27
|
@return: coordinate in decimal degrees
|
28
|
|
29
|
"""
|
30
|
|
31
|
if delimiter == "":
|
32
|
RABits = str(RAString).split()
|
33
|
else:
|
34
|
RABits = str(RAString).split(delimiter)
|
35
|
if len(RABits) > 1:
|
36
|
RAHDecimal = float(RABits[0])
|
37
|
if len(RABits) > 1:
|
38
|
RAHDecimal = RAHDecimal+(float(RABits[1])/60.0)
|
39
|
if len(RABits) > 2:
|
40
|
RAHDecimal = RAHDecimal+(float(RABits[2])/3600.0)
|
41
|
RADeg = (RAHDecimal/24.0)*360.0
|
42
|
else:
|
43
|
RADeg = float(RAString)
|
44
|
|
45
|
return RADeg
|
46
|
|
47
|
|
48
|
def dms2decimal(decString, delimiter):
|
49
|
"""Converts a delimited string of Degrees:Minutes:Seconds format into
|
50
|
decimal degrees.
|
51
|
|
52
|
@type decString: string
|
53
|
@param decString: coordinate string in D:M:S format
|
54
|
@type delimiter: string
|
55
|
@param delimiter: delimiter character in decString
|
56
|
@rtype: float
|
57
|
@return: coordinate in decimal degrees
|
58
|
|
59
|
"""
|
60
|
|
61
|
if delimiter == "":
|
62
|
decBits = str(decString).split()
|
63
|
else:
|
64
|
decBits = str(decString).split(delimiter)
|
65
|
if len(decBits) > 1:
|
66
|
decDeg = float(decBits[0])
|
67
|
if decBits[0].find("-") != -1:
|
68
|
if len(decBits) > 1:
|
69
|
decDeg = decDeg-(float(decBits[1])/60.0)
|
70
|
if len(decBits) > 2:
|
71
|
decDeg = decDeg-(float(decBits[2])/3600.0)
|
72
|
else:
|
73
|
if len(decBits) > 1:
|
74
|
decDeg = decDeg+(float(decBits[1])/60.0)
|
75
|
if len(decBits) > 2:
|
76
|
decDeg = decDeg+(float(decBits[2])/3600.0)
|
77
|
else:
|
78
|
decDeg = float(decString)
|
79
|
|
80
|
return decDeg
|
81
|
|
82
|
|
83
|
def decimal2hms(RADeg, delimiter):
|
84
|
"""Converts decimal degrees to string in Hours:Minutes:Seconds format with
|
85
|
user specified delimiter.
|
86
|
|
87
|
@type RADeg: float
|
88
|
@param RADeg: coordinate in decimal degrees
|
89
|
@type delimiter: string
|
90
|
@param delimiter: delimiter character in returned string
|
91
|
@rtype: string
|
92
|
@return: coordinate string in H:M:S format
|
93
|
|
94
|
"""
|
95
|
hours = (RADeg/360.0)*24
|
96
|
|
97
|
if 1 <= hours < 10:
|
98
|
sHours = "0"+str(hours)[0]
|
99
|
elif hours >= 10:
|
100
|
sHours = str(hours)[:2]
|
101
|
elif hours < 1:
|
102
|
sHours = "00"
|
103
|
|
104
|
if str(hours).find(".") == -1:
|
105
|
mins = float(hours)*60.0
|
106
|
else:
|
107
|
mins = float(str(hours)[str(hours).index("."):])*60.0
|
108
|
|
109
|
if 1 <= mins<10:
|
110
|
sMins = "0"+str(mins)[:1]
|
111
|
elif mins >= 10:
|
112
|
sMins = str(mins)[:2]
|
113
|
elif mins < 1:
|
114
|
sMins = "00"
|
115
|
|
116
|
secs = (hours-(float(sHours)+float(sMins)/60.0))*3600.0
|
117
|
|
118
|
if 0.001 < secs < 10:
|
119
|
sSecs = "0"+str(secs)[:str(secs).find(".")+4]
|
120
|
elif secs < 0.0001:
|
121
|
sSecs = "00.001"
|
122
|
else:
|
123
|
sSecs = str(secs)[:str(secs).find(".")+4]
|
124
|
if len(sSecs) < 5:
|
125
|
sSecs = sSecs+"00"
|
126
|
|
127
|
if float(sSecs) == 60.000:
|
128
|
sSecs = "00.00"
|
129
|
sMins = str(int(sMins)+1)
|
130
|
if int(sMins) == 60:
|
131
|
sMins = "00"
|
132
|
sDeg = str(int(sDeg)+1)
|
133
|
|
134
|
return sHours+delimiter+sMins+delimiter+sSecs
|
135
|
|
136
|
|
137
|
def decimal2dms(decDeg, delimiter):
|
138
|
"""Converts decimal degrees to string in Degrees:Minutes:Seconds format
|
139
|
with user specified delimiter.
|
140
|
|
141
|
@type decDeg: float
|
142
|
@param decDeg: coordinate in decimal degrees
|
143
|
@type delimiter: string
|
144
|
@param delimiter: delimiter character in returned string
|
145
|
@rtype: string
|
146
|
@return: coordinate string in D:M:S format
|
147
|
|
148
|
"""
|
149
|
|
150
|
if decDeg > 0:
|
151
|
|
152
|
if 1 <= decDeg < 10:
|
153
|
sDeg = "0"+str(decDeg)[0]
|
154
|
elif decDeg >= 10:
|
155
|
sDeg = str(decDeg)[:2]
|
156
|
elif decDeg < 1:
|
157
|
sDeg = "00"
|
158
|
|
159
|
if str(decDeg).find(".") == -1:
|
160
|
mins = float(decDeg)*60.0
|
161
|
else:
|
162
|
mins = float(str(decDeg)[str(decDeg).index("."):])*60
|
163
|
|
164
|
if 1 <= mins < 10:
|
165
|
sMins = "0"+str(mins)[:1]
|
166
|
elif mins >= 10:
|
167
|
sMins = str(mins)[:2]
|
168
|
elif mins < 1:
|
169
|
sMins = "00"
|
170
|
|
171
|
secs = (decDeg-(float(sDeg)+float(sMins)/60.0))*3600.0
|
172
|
|
173
|
if 0 < secs < 10:
|
174
|
sSecs = "0"+str(secs)[:str(secs).find(".")+3]
|
175
|
elif secs < 0.001:
|
176
|
sSecs = "00.00"
|
177
|
else:
|
178
|
sSecs = str(secs)[:str(secs).find(".")+3]
|
179
|
if len(sSecs) < 5:
|
180
|
sSecs = sSecs+"0"
|
181
|
|
182
|
if float(sSecs) == 60.00:
|
183
|
sSecs = "00.00"
|
184
|
sMins = str(int(sMins)+1)
|
185
|
if int(sMins) == 60:
|
186
|
sMins = "00"
|
187
|
sDeg = str(int(sDeg)+1)
|
188
|
|
189
|
return "+"+sDeg+delimiter+sMins+delimiter+sSecs
|
190
|
|
191
|
else:
|
192
|
|
193
|
if -10 < decDeg <= -1:
|
194
|
sDeg = "-0"+str(decDeg)[1]
|
195
|
elif decDeg <= -10:
|
196
|
sDeg = str(decDeg)[:3]
|
197
|
elif decDeg > -1:
|
198
|
sDeg = "-00"
|
199
|
|
200
|
if str(decDeg).find(".") == -1:
|
201
|
mins = float(decDeg)*-60.0
|
202
|
else:
|
203
|
mins = float(str(decDeg)[str(decDeg).index("."):])*60
|
204
|
|
205
|
if 1 <= mins < 10:
|
206
|
sMins = "0"+str(mins)[:1]
|
207
|
elif mins >= 10:
|
208
|
sMins = str(mins)[:2]
|
209
|
elif mins < 1:
|
210
|
sMins = "00"
|
211
|
|
212
|
secs = (decDeg-(float(sDeg)-float(sMins)/60.0))*3600.0
|
213
|
|
214
|
|
215
|
if -10 < secs < 0:
|
216
|
sSecs = "0"+str(secs)[1:str(secs).find(".")+3]
|
217
|
elif secs > -0.001:
|
218
|
sSecs = "00.00"
|
219
|
else:
|
220
|
sSecs = str(secs)[1:str(secs).find(".")+3]
|
221
|
if len(sSecs) < 5:
|
222
|
sSecs = sSecs+"0"
|
223
|
|
224
|
if float(sSecs) == 60.00:
|
225
|
sSecs = "00.00"
|
226
|
sMins = str(int(sMins)+1)
|
227
|
if int(sMins) == 60:
|
228
|
sMins = "00"
|
229
|
sDeg = str(int(sDeg)-1)
|
230
|
|
231
|
return sDeg+delimiter+sMins+delimiter+sSecs
|
232
|
|
233
|
|
234
|
def calcAngSepDeg(RA1, dec1, RA2, dec2):
|
235
|
"""Calculates the angular separation of two positions on the sky (specified
|
236
|
in decimal degrees) in decimal degrees, assuming a tangent plane projection
|
237
|
(so separation has to be <90 deg). Note that RADeg2, decDeg2 can be numpy
|
238
|
arrays.
|
239
|
|
240
|
@type RADeg1: float
|
241
|
@param RADeg1: R.A. in decimal degrees for position 1
|
242
|
@type decDeg1: float
|
243
|
@param decDeg1: dec. in decimal degrees for position 1
|
244
|
@type RADeg2: float or numpy array
|
245
|
@param RADeg2: R.A. in decimal degrees for position 2
|
246
|
@type decDeg2: float or numpy array
|
247
|
@param decDeg2: dec. in decimal degrees for position 2
|
248
|
@rtype: float or numpy array, depending upon type of RADeg2, decDeg2
|
249
|
@return: angular separation in decimal degrees
|
250
|
|
251
|
***** Updates added by Alessandro Nastasi - 26/06/2014 *****
|
252
|
|
253
|
Now RA and DEC can be entered indifferently as decimal degrees or as
|
254
|
hh:mm:ss.s or hh mm ss.s (provided that they are passed as STRING
|
255
|
in the latter two cases). An internal routine recognizes the correct format
|
256
|
and converts them into decimal degrees.
|
257
|
|
258
|
In addition, the tangent plane approximation restriction (i.e., dist < 90 deg)
|
259
|
has been removed and the complete formula is now implemented.
|
260
|
Pythagoras approximation is applied only for dist < 1 arcsec.
|
261
|
"""
|
262
|
|
263
|
|
264
|
|
265
|
|
266
|
|
267
|
|
268
|
|
269
|
|
270
|
|
271
|
|
272
|
|
273
|
|
274
|
|
275
|
|
276
|
|
277
|
|
278
|
|
279
|
RA1 = str(RA1).strip()
|
280
|
dec1 = str(dec1).strip()
|
281
|
RA2 = str(RA2).strip()
|
282
|
dec2 = str(dec2).strip()
|
283
|
|
284
|
inp_param = [str(RA1), dec1, RA2, dec2]
|
285
|
newinp = []
|
286
|
for x in inp_param:
|
287
|
newinp.append(string.replace(x, ":", " "))
|
288
|
|
289
|
if str(newinp[0]).find(' ') >= 0:
|
290
|
RADeg1 = hms2decimal(newinp[0], ' ')
|
291
|
decDeg1 = dms2decimal(newinp[1], ' ')
|
292
|
else:
|
293
|
RADeg1 = float(newinp[0])
|
294
|
decDeg1 = float(newinp[1])
|
295
|
|
296
|
if str(newinp[2]).find(' ') >= 0:
|
297
|
RADeg2 = hms2decimal(newinp[2], ' ')
|
298
|
decDeg2 = dms2decimal(newinp[3], ' ')
|
299
|
else:
|
300
|
RADeg2 = float(newinp[2])
|
301
|
decDeg2 = float(newinp[3])
|
302
|
|
303
|
cRA = numpy.radians(RADeg1)
|
304
|
cDec = numpy.radians(decDeg1)
|
305
|
|
306
|
gRA = numpy.radians(RADeg2)
|
307
|
gDec = numpy.radians(decDeg2)
|
308
|
|
309
|
x=numpy.cos(cRA)*numpy.cos(cDec)*numpy.cos(gRA)*numpy.cos(gDec)
|
310
|
y=numpy.sin(cRA)*numpy.cos(cDec)*numpy.sin(gRA)*numpy.cos(gDec)
|
311
|
z=numpy.sin(cDec)*numpy.sin(gDec)
|
312
|
|
313
|
rad=numpy.degrees(numpy.arccos(round(x+y+z,10)))
|
314
|
|
315
|
dRA = cRA-gRA
|
316
|
dDec = gDec-cDec
|
317
|
|
318
|
|
319
|
|
320
|
|
321
|
|
322
|
|
323
|
|
324
|
|
325
|
if rad<0.000004848:
|
326
|
rad=numpy.degrees(numpy.sqrt((numpy.cos(cDec)*dRA)**2+dDec**2))
|
327
|
|
328
|
return rad
|
329
|
|
330
|
|
331
|
def calcAngSepDegPythagoras(RADeg1, decDeg1, RADeg2, decDeg2):
|
332
|
|
333
|
|
334
|
cRA = numpy.radians(RADeg1)
|
335
|
cDec = numpy.radians(decDeg1)
|
336
|
|
337
|
gRA = numpy.radians(RADeg2)
|
338
|
gDec = numpy.radians(decDeg2)
|
339
|
|
340
|
dRA = cRA-gRA
|
341
|
dDec = gDec-cDec
|
342
|
cosC = ((numpy.sin(gDec)*numpy.sin(cDec)) +
|
343
|
(numpy.cos(gDec)*numpy.cos(cDec) * numpy.cos(gRA-cRA)))
|
344
|
x = (numpy.cos(cDec)*numpy.sin(gRA-cRA))/cosC
|
345
|
y = (((numpy.cos(gDec)*numpy.sin(cDec)) - (numpy.sin(gDec) *
|
346
|
numpy.cos(cDec)*numpy.cos(gRA-cRA)))/cosC)
|
347
|
r = numpy.degrees(numpy.sqrt(x*x+y*y))
|
348
|
|
349
|
return r
|
350
|
|
351
|
|
352
|
def shiftRADec(ra1, dec1, deltaRA, deltaDec):
|
353
|
"""Computes new right ascension and declination shifted from the original
|
354
|
by some delta RA and delta DEC. Input position is decimal degrees. Shifts
|
355
|
(deltaRA, deltaDec) are arcseconds, and output is decimal degrees. Based on
|
356
|
an IDL routine of the same name.
|
357
|
|
358
|
@param ra1: float
|
359
|
@type ra1: R.A. in decimal degrees
|
360
|
@param dec1: float
|
361
|
@type dec1: dec. in decimal degrees
|
362
|
@param deltaRA: float
|
363
|
@type deltaRA: shift in R.A. in arcseconds
|
364
|
@param deltaDec: float
|
365
|
@type deltaDec: shift in dec. in arcseconds
|
366
|
@rtype: float [newRA, newDec]
|
367
|
@return: shifted R.A. and dec.
|
368
|
|
369
|
"""
|
370
|
|
371
|
d2r = math.pi/180.
|
372
|
as2r = math.pi/648000.
|
373
|
|
374
|
|
375
|
rara1 = ra1*d2r
|
376
|
dcrad1 = dec1*d2r
|
377
|
shiftRArad = deltaRA*as2r
|
378
|
shiftDCrad = deltaDec*as2r
|
379
|
|
380
|
|
381
|
deldec2 = 0.0
|
382
|
sindis = math.sin(shiftRArad / 2.0)
|
383
|
sindelRA = sindis / math.cos(dcrad1)
|
384
|
delra = 2.0*math.asin(sindelRA) / d2r
|
385
|
|
386
|
|
387
|
ra2 = ra1+delra
|
388
|
dec2 = dec1 +deltaDec / 3600.0
|
389
|
|
390
|
return ra2, dec2
|
391
|
|
392
|
|
393
|
def convertCoords(inputSystem, outputSystem, coordX, coordY, epoch):
|
394
|
"""Converts specified coordinates (given in decimal degrees) between J2000,
|
395
|
B1950, and Galactic.
|
396
|
|
397
|
@type inputSystem: string
|
398
|
@param inputSystem: system of the input coordinates (either "J2000",
|
399
|
"B1950" or "GALACTIC")
|
400
|
@type outputSystem: string
|
401
|
@param outputSystem: system of the returned coordinates (either "J2000",
|
402
|
"B1950" or "GALACTIC")
|
403
|
@type coordX: float
|
404
|
@param coordX: longitude coordinate in decimal degrees, e.g. R. A.
|
405
|
@type coordY: float
|
406
|
@param coordY: latitude coordinate in decimal degrees, e.g. dec.
|
407
|
@type epoch: float
|
408
|
@param epoch: epoch of the input coordinates
|
409
|
@rtype: list
|
410
|
@return: coordinates in decimal degrees in requested output system
|
411
|
|
412
|
"""
|
413
|
|
414
|
if inputSystem=="J2000" or inputSystem=="B1950" or inputSystem=="GALACTIC":
|
415
|
if outputSystem=="J2000" or outputSystem=="B1950" or \
|
416
|
outputSystem=="GALACTIC":
|
417
|
|
418
|
outCoords=wcscon.wcscon(wcscon.wcscsys(inputSystem),
|
419
|
wcscon.wcscsys(outputSystem), 0, 0, coordX, coordY, epoch)
|
420
|
|
421
|
return outCoords
|
422
|
|
423
|
raise Exception("inputSystem and outputSystem must be 'J2000', 'B1950'"
|
424
|
"or 'GALACTIC'")
|
425
|
|
426
|
|
427
|
def calcRADecSearchBox(RADeg, decDeg, radiusSkyDeg):
|
428
|
"""Calculates minimum and maximum RA, dec coords needed to define a box
|
429
|
enclosing a circle of radius radiusSkyDeg around the given RADeg, decDeg
|
430
|
coordinates. Useful for freeform queries of e.g. SDSS, UKIDSS etc.. Uses
|
431
|
L{calcAngSepDeg}, so has the same limitations.
|
432
|
|
433
|
@type RADeg: float
|
434
|
@param RADeg: RA coordinate of centre of search region
|
435
|
@type decDeg: float
|
436
|
@param decDeg: dec coordinate of centre of search region
|
437
|
@type radiusSkyDeg: float
|
438
|
@param radiusSkyDeg: radius in degrees on the sky used to define search
|
439
|
region
|
440
|
@rtype: list
|
441
|
@return: [RAMin, RAMax, decMin, decMax] - coordinates in decimal degrees
|
442
|
defining search box
|
443
|
|
444
|
"""
|
445
|
|
446
|
tolerance = 1e-5
|
447
|
targetHalfSizeSkyDeg = radiusSkyDeg
|
448
|
funcCalls = ["calcAngSepDeg(RADeg, decDeg, guess, decDeg)",
|
449
|
"calcAngSepDeg(RADeg, decDeg, guess, decDeg)",
|
450
|
"calcAngSepDeg(RADeg, decDeg, RADeg, guess)",
|
451
|
"calcAngSepDeg(RADeg, decDeg, RADeg, guess)"]
|
452
|
coords = [RADeg, RADeg, decDeg, decDeg]
|
453
|
signs = [1.0, -1.0, 1.0, -1.0]
|
454
|
results = []
|
455
|
for f, c, sign in zip(funcCalls, coords, signs):
|
456
|
|
457
|
maxGuess = sign*targetHalfSizeSkyDeg*10.0
|
458
|
minGuess = sign*targetHalfSizeSkyDeg/10.0
|
459
|
guessStep = (maxGuess-minGuess)/10.0
|
460
|
guesses = numpy.arange(minGuess+c, maxGuess+c, guessStep)
|
461
|
for i in range(50):
|
462
|
minSizeDiff = 1e6
|
463
|
bestGuess = None
|
464
|
for guess in guesses:
|
465
|
sizeDiff = abs(eval(f)-targetHalfSizeSkyDeg)
|
466
|
if sizeDiff < minSizeDiff:
|
467
|
minSizeDiff = sizeDiff
|
468
|
bestGuess = guess
|
469
|
if minSizeDiff < tolerance:
|
470
|
break
|
471
|
else:
|
472
|
guessRange = abs((maxGuess-minGuess))
|
473
|
maxGuess = bestGuess+guessRange/4.0
|
474
|
minGuess = bestGuess-guessRange/4.0
|
475
|
guessStep = (maxGuess-minGuess)/10.0
|
476
|
guesses = numpy.arange(minGuess, maxGuess, guessStep)
|
477
|
results.append(bestGuess)
|
478
|
|
479
|
RAMax = results[0]
|
480
|
RAMin = results[1]
|
481
|
decMax = results[2]
|
482
|
decMin = results[3]
|
483
|
|
484
|
return [RAMin, RAMax, decMin, decMax]
|