|
| 1 | +from flask import Flask, render_template, send_from_directory, url_for, request, redirect |
| 2 | +import ephem |
| 3 | +from pylab import * |
| 4 | +import matplotlib.pyplot as plt |
| 5 | +import matplotlib.dates as mdates |
| 6 | +import numpy as np |
| 7 | +import mpld3 |
| 8 | +from json import JSONEncoder |
| 9 | +import logging |
| 10 | + |
| 11 | +#from io import BytesIO |
| 12 | +#import base64 |
| 13 | +''' |
| 14 | +import bokeh.plotting as plt |
| 15 | +from bokeh.embed import components |
| 16 | +from bokeh.resources import CDN |
| 17 | +from bokeh.embed import components |
| 18 | +''' |
| 19 | +app = Flask(__name__) |
| 20 | +logger = logging.getLogger(__name__) |
| 21 | + |
| 22 | +@app.route('/otool/calculate/', methods=['POST', 'GET']) |
| 23 | +def calculate(): |
| 24 | + """Main method.""" |
| 25 | + |
| 26 | + fightml = "" |
| 27 | + if request.method == 'POST': |
| 28 | + |
| 29 | + lofar = ephem.Observer() |
| 30 | + lofar.lon = '6.869882' |
| 31 | + lofar.lat = '52.915129' |
| 32 | + lofar.elevation = 15. |
| 33 | + lofar.pressure = 0. # no refraction at horizon |
| 34 | + lofar.horizon = '-0:34' # idem |
| 35 | + dateTokens = [] |
| 36 | + if request.form['date'] != None and request.form['date'] != "": |
| 37 | + dateTokens = request.form['date'].split('/') |
| 38 | + lofar.date = str(dateTokens[2] + '/' + dateTokens[0] + '/' + dateTokens[1]) |
| 39 | + endOfDay = lofar.date |
| 40 | + endOfDay += 1. |
| 41 | + |
| 42 | + targets = [] |
| 43 | + aTeam = [] |
| 44 | + solSystem = [] |
| 45 | + calibrators = [] |
| 46 | + time = [] |
| 47 | + lst = [] |
| 48 | + targetList = "" |
| 49 | + minElevation = 0. |
| 50 | + |
| 51 | + if request.form['elevation'] != None and request.form['elevation'] != "": |
| 52 | + minElevation = np.double(request.form['elevation']) |
| 53 | + |
| 54 | + if request.form['targetList'] != None and request.form['targetList'] != "": |
| 55 | + targetList = request.form['targetList'] |
| 56 | + |
| 57 | + if request.form['names'] != None and request.form['names'] != "" and request.form['ra'] != None and request.form['ra'] != "" and request.form['dec'] != None and request.form['dec'] != "": |
| 58 | + for name, ra, dec in zip(request.form['names'].split(","), request.form['ra'].split(","), request.form['dec'].split(",")): |
| 59 | + target = Target() |
| 60 | + target.name = name |
| 61 | + target.elevation = [] |
| 62 | + target.body = createFixedBody(str(ra), str(dec), '2000') |
| 63 | + target.body.compute(lofar) |
| 64 | + target.note = "</br>" + name + " and " |
| 65 | + targets.append(target) |
| 66 | + |
| 67 | + if request.form.get('CygA') != None: |
| 68 | + cygA = Target() |
| 69 | + cygA.name = 'CygA' |
| 70 | + cygA.elevation = [] |
| 71 | + cygA.body = createFixedBody('19:59:28:3566', '+40:44:02.096', '2000') |
| 72 | + cygA.body.compute(lofar) |
| 73 | + for target in targets: |
| 74 | + target.note += "<li>Cyg A: " + str(ephem.separation(target.body, cygA.body)) + "</li>" |
| 75 | + aTeam.append(cygA) |
| 76 | + |
| 77 | + if request.form.get('CasA') != None: |
| 78 | + casA = Target() |
| 79 | + casA.name = 'CasA' |
| 80 | + casA.elevation = [] |
| 81 | + casA.body = createFixedBody('23:23:27.94', '+58:48:42.4', '2000') |
| 82 | + casA.body.compute(lofar) |
| 83 | + for target in targets: |
| 84 | + target.note += "<li>Cas A: " + str(ephem.separation(target.body, casA.body)) + "</li>" |
| 85 | + aTeam.append(casA) |
| 86 | + |
| 87 | + if request.form.get('TauA') != None: |
| 88 | + tauA = Target() |
| 89 | + tauA.name = 'TauA' |
| 90 | + tauA.elevation = [] |
| 91 | + tauA.body = createFixedBody('05:34:31.971', '+22:00:52.06', '2000') |
| 92 | + tauA.body.compute(lofar) |
| 93 | + for target in targets: |
| 94 | + target.note += "<li>Tau A: " + str(ephem.separation(target.body, tauA.body)) + "</li>" |
| 95 | + aTeam.append(tauA) |
| 96 | + |
| 97 | + if request.form.get('VirA') != None: |
| 98 | + virA = Target() |
| 99 | + virA.name = 'VirA' |
| 100 | + virA.elevation = [] |
| 101 | + virA.body = createFixedBody('12:30:49.4233', '+12:23:28.043', '2000') |
| 102 | + virA.body.compute(lofar) |
| 103 | + for target in targets: |
| 104 | + target.note += "<li>Vir A: " + str(ephem.separation(target.body, virA.body)) + "</li>" |
| 105 | + aTeam.append(virA) |
| 106 | + |
| 107 | + if request.form.get('Sun') != None: |
| 108 | + sun = Target() |
| 109 | + sun.name = 'Sun' |
| 110 | + sun.elevation = [] |
| 111 | + sun.body = ephem.Sun() |
| 112 | + sun.body.compute(lofar) |
| 113 | + for target in targets: |
| 114 | + target.note += "<li>Sun: " + str(ephem.separation(target.body, sun.body)) + "</li>" |
| 115 | + solSystem.append(sun) |
| 116 | + |
| 117 | + if request.form.get('Jupiter') != None: |
| 118 | + jup = Target() |
| 119 | + jup.name = 'Jupiter' |
| 120 | + jup.elevation = [] |
| 121 | + jup.body = ephem.Jupiter() |
| 122 | + jup.body.compute(lofar) |
| 123 | + for target in targets: |
| 124 | + target.note += "<li>Jupiter: " + str(ephem.separation(target.body, jup.body)) + "</li>" |
| 125 | + solSystem.append(jup) |
| 126 | + |
| 127 | + if request.form.get('Saturn') != None: |
| 128 | + sat = Target() |
| 129 | + sat.name = 'Saturn' |
| 130 | + sat.elevation = [] |
| 131 | + sat.body = ephem.Saturn() |
| 132 | + sat.body.compute(lofar) |
| 133 | + for target in targets: |
| 134 | + target.note += "<li>Saturn: " + str(ephem.separation(target.body, sat.body)) + "</li>" |
| 135 | + solSystem.append(sat) |
| 136 | + |
| 137 | + if request.form.get('3C48') != None: |
| 138 | + c48 = Target() |
| 139 | + c48.name = '3C 48' |
| 140 | + c48.elevation = [] |
| 141 | + c48.body = createFixedBody('01:37:41.2994', '+33:09:35.134', '2000') |
| 142 | + c48.body.compute(lofar) |
| 143 | + for target in targets: |
| 144 | + target.note += "<li>3C 48: " + str(ephem.separation(target.body, c48.body)) + "</li>" |
| 145 | + calibrators.append(c48) |
| 146 | + |
| 147 | + if request.form.get('3C147') != None: |
| 148 | + c147 = Target() |
| 149 | + c147.name = '3C 147' |
| 150 | + c147.elevation = [] |
| 151 | + c147.body = createFixedBody('05:42:36.1379', '+49:51:07.234', '2000') |
| 152 | + c147.body.compute(lofar) |
| 153 | + for target in targets: |
| 154 | + target.note += "<li>3C 147: " + str(ephem.separation(target.body, c147.body)) + "</li>" |
| 155 | + calibrators.append(c147) |
| 156 | + |
| 157 | + if request.form.get('3C295') != None: |
| 158 | + c295 = Target() |
| 159 | + c295.name = '3C 295' |
| 160 | + c295.elevation = [] |
| 161 | + c295.body = createFixedBody('14:11:20.519', '+52:12:09.97', '2000') |
| 162 | + c295.body.compute(lofar) |
| 163 | + for target in targets: |
| 164 | + target.note += "<li>3C 295: " + str(ephem.separation(target.body, c295.body)) + "</li>" |
| 165 | + calibrators.append(c295) |
| 166 | + |
| 167 | + if request.form.get('3C196') != None: |
| 168 | + c196 = Target() |
| 169 | + c196.name = '3C196' |
| 170 | + c196.elevation = [] |
| 171 | + c196.body = createFixedBody('08:13:36.033', '+48:13:02.56', '2000') |
| 172 | + c196.body.compute(lofar) |
| 173 | + for target in targets: |
| 174 | + target.note += "<li>3C 196: " + str(ephem.separation(target.body, c196.body)) + "</li>" |
| 175 | + calibrators.append(c196) |
| 176 | + |
| 177 | + if request.form.get('3C380') != None: |
| 178 | + c380 = Target() |
| 179 | + c380.name = '3C 380' |
| 180 | + c380.elevation = [] |
| 181 | + c380.body = createFixedBody('18:29:31.7809', '+48:44:46.160', '2000') |
| 182 | + c380.body.compute(lofar) |
| 183 | + for target in targets: |
| 184 | + target.note += "<li>3C 380: " + str(ephem.separation(target.body, c380.body)) + "</li>" |
| 185 | + calibrators.append(c380) |
| 186 | + |
| 187 | + if request.form.get('3C286') != None: |
| 188 | + c286 = Target() |
| 189 | + c286.name = '3C 286' |
| 190 | + c286.elevation = [] |
| 191 | + c286.body = createFixedBody('13:31:08.28', '+30:30:32.9', '2000') |
| 192 | + c286.body.compute(lofar) |
| 193 | + for target in targets: |
| 194 | + target.note += "<li>3C 286: " + str(ephem.separation(target.body, c286.body)) + "</li>" |
| 195 | + calibrators.append(c286) |
| 196 | + |
| 197 | + if request.form.get('CTD93') != None: |
| 198 | + c93 = Target() |
| 199 | + c93.name = 'CTD 93' |
| 200 | + c93.elevation = [] |
| 201 | + c93.body = createFixedBody('16:09:13.32', '+26:41:29.0', '2000') |
| 202 | + c93.body.compute(lofar) |
| 203 | + for target in targets: |
| 204 | + target.note += "<li>CTD 93: " + str(ephem.separation(target.body, c93.body)) + "</li>" |
| 205 | + calibrators.append(c93) |
| 206 | + |
| 207 | + previous = None |
| 208 | + lock = 0 |
| 209 | + |
| 210 | + while lofar.date < endOfDay - ephem.minute: |
| 211 | + lofar.date += ephem.minute |
| 212 | + |
| 213 | + for target in targets: |
| 214 | + target.body.compute(lofar) |
| 215 | + target.elevation.append(ephem.degrees(target.body.alt) * (180. / math.pi)) |
| 216 | + |
| 217 | + for at in aTeam: |
| 218 | + at.body.compute(lofar) |
| 219 | + at.elevation.append(ephem.degrees(at.body.alt) * (180. / math.pi)) |
| 220 | + |
| 221 | + for calibrator in calibrators: |
| 222 | + calibrator.body.compute(lofar) |
| 223 | + calibrator.elevation.append(ephem.degrees(calibrator.body.alt) * (180. / math.pi)) |
| 224 | + |
| 225 | + for sol in solSystem: |
| 226 | + sol.body.compute(lofar) |
| 227 | + sol.elevation.append(ephem.degrees(sol.body.alt) * (180. / math.pi)) |
| 228 | + |
| 229 | + time.append(lofar.date.datetime()) |
| 230 | + |
| 231 | + if previous > lofar.sidereal_time(): |
| 232 | + lock = 1 |
| 233 | + |
| 234 | + if lock: |
| 235 | + lst.append(ephem.Date(str(lofar.date).split(' ')[0].split("/")[0] + "/" + str(lofar.date).split(' ')[0].split("/")[1] + "/" + str(int(str(lofar.date).split(' ')[0].split("/")[2]) + 1) + ' ' + str(lofar.sidereal_time())).datetime()) |
| 236 | + else: |
| 237 | + lst.append(ephem.Date(str(lofar.date).split(' ')[0] + ' ' + str(lofar.sidereal_time())).datetime()) |
| 238 | + |
| 239 | + previous = lofar.sidereal_time() |
| 240 | + |
| 241 | + fig, ax = plt.subplots(2, 1, figsize=(15, 10),sharex=False, sharey=False) |
| 242 | + fig.canvas.draw() |
| 243 | + fig.subplots_adjust(hspace=0.5) |
| 244 | + |
| 245 | + ax[0].set_yticks([20, 60, 80]) |
| 246 | + |
| 247 | + for target in targets: |
| 248 | + ax[0].plot(lst, target.elevation, '--', label=target.name) |
| 249 | + |
| 250 | + sun = ephem.Sun() |
| 251 | + sun.compute(lofar) |
| 252 | + |
| 253 | + ax[0].hlines(minElevation, lst[0], lst[len(lst) - 1], colors='k', linestyles='solid', lw=2) |
| 254 | + |
| 255 | + |
| 256 | + # Does not work with mpld3 |
| 257 | + #axis1.axvspan(ephem.Date(lofar.previous_rising(sun) - ephem.hour).datetime(), ephem.Date(lofar.previous_rising(sun) + ephem.hour).datetime(), facecolor='0.5', alpha=0.5) |
| 258 | + #trans = transforms.blended_transform_factory(axis1.transData, axis1.transAxes) |
| 259 | + #rect = patches.Rectangle((ephem.Date(lofar.previous_rising(sun) - ephem.hour).datetime(), 20), width=ephem.Date(ephem.hour).datetime(), height=90, transform=trans, color='yellow', alpha=0.5) |
| 260 | + |
| 261 | + #axis1.add_patch(rect) |
| 262 | + |
| 263 | + ax[0].set_ylabel(r'Elevation [degrees]', fontsize=18, fontweight='bold', color='#000000') |
| 264 | + ax[0].set_title('LOFAR target visibility', fontsize=20, fontweight='bold') |
| 265 | + |
| 266 | + time = np.array(time) |
| 267 | + cnt = 1 |
| 268 | + |
| 269 | + for target in targets: |
| 270 | + ax[1].plot(time, cnt * produceSegments(np.array(target.elevation), minElevation), '--', solid_capstyle='round', lw=5) |
| 271 | + cnt+=1 |
| 272 | + |
| 273 | + for calibrator in calibrators: |
| 274 | + ax[0].plot(lst, calibrator.elevation, label=calibrator.name) |
| 275 | + cnt+=1 |
| 276 | + ax[1].plot(time, cnt * produceSegments(np.array(calibrator.elevation), minElevation), solid_capstyle='round', lw=5) |
| 277 | + |
| 278 | + for at in aTeam: |
| 279 | + ax[0].plot(lst, at.elevation, label=at.name) |
| 280 | + cnt+=1 |
| 281 | + ax[1].plot(time, cnt * produceSegments(np.array(at.elevation), minElevation), solid_capstyle='round', lw=5) |
| 282 | + |
| 283 | + for sol in solSystem: |
| 284 | + ax[0].plot(lst, sol.elevation, label=sol.name) |
| 285 | + cnt+=1 |
| 286 | + ax[1].plot(time, cnt * produceSegments(np.array(sol.elevation), minElevation), solid_capstyle='round', lw=5) |
| 287 | + |
| 288 | + sep_message = "The angular distance on the sky (dd:mm:ss.s) between: " |
| 289 | + |
| 290 | + for target in targets: |
| 291 | + sep_message+=target.note |
| 292 | + sep_message+= "<ul>" |
| 293 | + for calibrator in calibrators: |
| 294 | + sep_message+=calibrator.note |
| 295 | + for at in aTeam: |
| 296 | + sep_message+=at.note |
| 297 | + for sol in solSystem: |
| 298 | + sep_message+=sol.note |
| 299 | + sep_message+= "</ul>" |
| 300 | + |
| 301 | + ax[1].vlines((lofar.previous_rising(sun)).datetime(), 0, cnt + 1, colors='r', linestyles='dashed', lw=2) |
| 302 | + ax[1].vlines((lofar.previous_setting(sun)).datetime(), 0, cnt + 1, colors='r', linestyles='dashed', lw=2) |
| 303 | + |
| 304 | + ax[1].vlines(ephem.Date(lofar.previous_rising(sun) - ephem.hour).datetime(), 0, cnt + 1, colors='k', linestyles='dashdot', lw=2) |
| 305 | + ax[1].vlines(ephem.Date(lofar.previous_rising(sun) + ephem.hour).datetime(), 0, cnt + 1, colors='k', linestyles='dashdot', lw=2) |
| 306 | + |
| 307 | + ax[1].vlines(ephem.Date(lofar.previous_setting(sun) - ephem.hour).datetime(), 0, cnt + 1, colors='k', linestyles='dashdot', lw=2) |
| 308 | + ax[1].vlines(ephem.Date(lofar.previous_setting(sun) + ephem.hour).datetime(), 0, cnt + 1, colors='k', linestyles='dashdot', lw=2) |
| 309 | + |
| 310 | + ax[1].text(ephem.Date(lofar.previous_rising(sun) - 2. * ephem.hour).datetime(), cnt + 1.5, "Sunrise +/- 1hr") |
| 311 | + ax[1].text(ephem.Date(lofar.previous_setting(sun) - 2. * ephem.hour).datetime(), cnt + 1.5, "Sunset +/- 1hr") |
| 312 | + |
| 313 | + ax[1].set_ylabel(r'Sources', fontsize=18, fontweight='bold', color='#000000', labelpad=30) |
| 314 | + plt.ylim(0, cnt + 3) |
| 315 | + |
| 316 | + ax[0].grid() |
| 317 | + ax[0].set_xlabel('LST') |
| 318 | + ax[0].xaxis.labelpad = 20 |
| 319 | + ax[0].yaxis.labelpad = 20 |
| 320 | + |
| 321 | + ax[1].axes.get_yaxis().set_ticks([]) |
| 322 | + ax[1].set_xlabel('UT') |
| 323 | + ax[1].xaxis.tick_top() |
| 324 | + ax[1].xaxis.set_label_position('top') |
| 325 | + ax[1].xaxis.labelpad = 20 |
| 326 | + |
| 327 | + legend1 = ax[0].legend(loc='upper center', shadow=True, labelspacing=0.01, ncol=4, bbox_to_anchor=(0.5, 1.)) |
| 328 | + |
| 329 | + fightml = mpld3.fig_to_html(fig) |
| 330 | + |
| 331 | + logger.info("Finished computations, rendering...") |
| 332 | + return render_template('otool.html', form=request.form, plot=fightml, info=sep_message) |
| 333 | + |
| 334 | +# Array masks don't plot with mpld3 |
| 335 | +def produceSegments(elevArray, minElevation): |
| 336 | + """Replace entries in the elevation array with NaNs if they are below the elevation selected by the user.""" |
| 337 | + targetVisibility = np.ones(len(elevArray)) |
| 338 | + for i, elev in enumerate(elevArray): |
| 339 | + if elev < minElevation: |
| 340 | + targetVisibility[i] = np.nan |
| 341 | + |
| 342 | + return targetVisibility |
| 343 | + |
| 344 | +def createFixedBody(ra, dec, epoch): |
| 345 | + """Fixed body adapted to us.""" |
| 346 | + fixedBody = ephem.FixedBody() |
| 347 | + fixedBody._ra = ra |
| 348 | + fixedBody._dec = dec |
| 349 | + fixedBody._epoch = epoch |
| 350 | + return fixedBody |
| 351 | + |
| 352 | +class Target(object): |
| 353 | + """Encapsulates celestial bodies.""" |
| 354 | + def __init__(self, name="", body=None, elevation=[], note = ""): |
| 355 | + self.name = name |
| 356 | + self.body = body |
| 357 | + self.elevation = elevation |
| 358 | + self.note = note |
| 359 | + |
| 360 | +@app.route('/otool/static/<path:path>') |
| 361 | +def send_js(path): |
| 362 | + return send_from_directory('static', path) |
| 363 | + |
| 364 | +# Home page |
| 365 | +@app.route('/otool/') |
| 366 | +def otool(): |
| 367 | + """The landing page""" |
| 368 | + return render_template('otool.html') |
| 369 | + |
| 370 | +if __name__ == "__main__": |
| 371 | + logging.basicConfig(level=logging.INFO) |
| 372 | + app.run(debug=True) |
| 373 | + logger.info("Server running.") |
| 374 | + #app.run() |
| 375 | + #app.run(host='0.0.0.0') |
0 commit comments